function Users_before_Q_response_notices() { $from_parts = explode(' ', Q_Request::special('fromSuccess', false)); $from = reset($from_parts); if ($from === 'Users/activate') { $user = Q_Session::id() ? Users::loggedInUser() : null; $notice = $user ? "You've completed the activation." : "You've completed the activation. Try logging in now."; Q_Response::setNotice('Users/activate', $notice, true); } else { if ($from === 'Users/resend') { $notice = 'Your activation message has been re-sent. You should get it in a moment.'; Q_Response::setNotice('Users/resend', $notice, true); } } }
function Users_identifier_tool($options) { $defaults = array('uri' => 'Users/identifier', 'omit' => array(), 'fields' => array(), 'title' => "Contact Info", 'collapsed' => false, 'toggle' => false, 'editing' => true, 'complete' => true, 'inProcess' => false, 'prompt' => "In order for things to work, we must be able to reach you.", 'button_content' => 'OK'); extract(array_merge($defaults, $options)); $default_fields = array('emailAddress' => array('type' => 'text', 'label' => 'Email')); $fields = array_merge($default_fields, $fields); $user = Users::loggedInUser(true); $email = null; if (isset($user->emailAddress)) { $fields['emailAddress']['value'] = $user->emailAddress; } else { if ($user->emailAddressPending) { $link = Q_Html::a('#resend', array('class' => 'Users_idenfitier_tool_resend'), "You can re-send the activation email"); $email = new Users_Email(); $email->address = $user->emailAddressPending; if ($email->retrieve()) { switch ($email->state) { case 'active': if ($email->userId == $user->id) { $message = "Please confirm this email address.<br>{$link}"; } else { $message = "This email seems to belong to another user"; } break; case 'suspended': $message = "This address has been suspended."; break; case 'unsubscribed': $message = "The owner of this address has unsubscribed"; break; case 'unverified': default: $message = "Not verified yet.<br>{$link}"; break; } $fields['emailAddress']['value'] = $email->address; $fields['emailAddress']['message'] = $message; } else { // something went wrong, so we'll try to correct it $user->emailAddressPending = ""; $user->save(); } } } $onSuccess = Q_Request::special('onSuccess', Q_Request::url()); $form = $static = compact('fields'); return Q::tool('Q/panel', compact('uri', 'onSuccess', 'form', 'static', 'title', 'collapsed', 'toggle', 'complete', 'editing', 'inProcess', 'setSlots')); }
function Users_account_tool($options) { $uri = 'Users/account'; $omit = array(); $fields = array(); $title = "Basic Info"; $editing = true; $complete = true; $inProcess = false; $collapsed = false; $toggle = false; $omit = array(); $setSlots = null; extract($options, EXTR_OVERWRITE); $default_fields = array('username' => array('type' => 'text', 'label' => 'Choose Username'), 'gender' => array('type' => 'select', 'label' => 'I am', 'options' => array('male' => 'a man', 'female' => 'a woman')), 'orientation' => array('type' => 'select', 'label' => 'Orientation', 'options' => array('straight' => 'straight', 'gay' => 'gay', 'bi' => 'bi')), 'relationship_status' => array('type' => 'select', 'label' => 'Status', 'options' => array('single' => "I'm single", 'open' => "I'm in an open relationship", 'relationship' => "I'm in a relationship", 'engaged' => "I'm engaged", 'married' => "I'm married", 'complicated' => "It's complicated", 'widowed' => "I'm widowed")), 'birthday' => array('type' => 'date', 'label' => 'My Birthday', 'options' => array('year_from' => '1920', 'year_to' => date('Y') - 16)), 'zipcode' => array('type' => 'text', 'label' => 'Zipcode', 'attributes' => array('maxlength' => 5))); $fields = array_merge($default_fields, $fields); $user = Users::loggedInUser(true); if (isset($user->gender)) { $fields['gender']['value'] = $user->gender; } if (isset($user->desired_gender)) { if ($user->desired_gender == 'either') { $fields['orientation']['value'] = 'bi'; } else { if (isset($user->gender)) { $fields['orientation']['value'] = $user->gender != $user->desired_gender ? 'straight' : 'gay'; } } } if (isset($user->relationship_status)) { $fields['relationship_status']['value'] = $user->relationship_status; } if (isset($user->birthday)) { $fields['birthday']['value'] = date("Y-m-d", Users::db()->fromDate($user->birthday)); } if (isset($user->zipcode)) { $fields['zipcode']['value'] = $user->zipcode; } if (isset($user->username)) { $fields['username']['value'] = $user->username; } foreach ($omit as $v) { unset($fields[$v]); } $onSuccess = Q_Request::special('onSuccess', Q_Request::url()); $form = $static = compact('fields'); return Q::tool('Q/panel', compact('uri', 'title', 'form', 'static', 'onSuccess', 'complete', 'collapsed', 'toggle', 'editing', 'inProcess', 'static', 'setSlots')); }
/** * Sends asynchronous internal message to Node.js * If "Q.clientId" is in $_REQUEST, adds it into the data * @method sendToNode * @static * @param {array} $data Associative array of data of the message to send. * It should contain the key "Q/method" so Node can decide what to do with the message. * @param {string|array} [$url=null] and url to query. Default to 'Q/nodeInternal' config value and path '/Q/node' * @param {boolean} [$throwIfRefused=false] Pass true here to throw an exception whenever Node process is not running or refuses the request */ static function sendToNode($data, $url = null, $throwIfRefused = false) { if (!is_array($data)) { throw new Q_Exception_WrongType(array('field' => 'data', 'type' => 'array')); } if (empty($data['Q/method'])) { throw new Q_Exception_RequiredField(array('field' => 'Q/method')); } $ssid = Q_Request::special('clientId', null); if (isset($ssid)) { $data['Q.clientId'] = $ssid; } // The following hook may modify the url /** * @event Q/Utils/sendToNode {before} * @param {array} data * @param {string|array} 'url' */ Q::event('Q/Utils/sendToNode', array('data' => $data, 'url' => $url), 'before'); if (!$url) { $nodeh = Q_Config::get('Q', 'nodeInternal', 'host', null); $nodep = Q_Config::get('Q', 'nodeInternal', 'port', null); $url = $nodep && $nodeh ? "http://{$nodeh}:{$nodep}/Q/node" : false; } if (!$url) { $result = false; } else { // Should we switch to sending JSON over TCP? $result = Q_Utils::postAsync($url, self::sign($data), null, Q_UTILS_INTERNAL_TIMEOUT, $throwIfRefused); } return $result; // if (!$result) { // throw new Q_Exception_SendingToNode(array('method' => $data['Q/method'])); // } }
/** * @method respond * @static * @param {string} $json */ static function respond($json) { if (null !== Q_Request::special('callback', null)) { header('Content-Type: application/x-javascript'); echo 'Metrics.callback(' . Q_Request::special('callback', null) . ", {$json});"; } else { echo $json; } }
/** * Used by HTTP clients to create a new stream in the system. * @class Streams-stream * @method post * @param {array} [$params] Parameters that can come from the request * @param {string} $params.publisherId Required. The id of the user to publish the stream. * @param {string} $params.type Required. The type of the stream. * @param {string} [$params.Q_Streams_related_publisherId] Optionally indicate the publisher of the stream to relate the newly created to. Used together with the related.streamName option. * @param {string} [$params.Q_Streams_related_streamName] Optionally indicate the name of a stream to relate the newly crated stream to. This is often necessary in order to obtain permissions to create the stream. * @param {bool} [$params.dontSubscribe=false] Pass 1 or true here in order to skip auto-subscribing to the newly created stream. * @param {array} [$params.icon] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.icon.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.icon.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.icon.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.icon.merge=""] path under web dir for an optional image to use as a background * @param {string} [$params.icon.crop] array with keys "x", "y", "w", "h" to crop the original image * @param {string} [$params.icon.save=array("x" => "")] array of $size => $basename pairs * where the size is of the format "WxH", and either W or H can be empty. * @param {array} [$params.file] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.file.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.file.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.file.subpath=""] subpath that should follow the path, to save the file under * @param {string} [$params.file.name] override name of the file, after the subpath */ function Streams_stream_post($params = array()) { $user = Users::loggedInUser(true); $publisherId = Streams::requestedPublisherId(); if (empty($publisherId)) { $publisherId = $_REQUEST['publisherId'] = $user->id; } $req = array_merge($_REQUEST, $params); $type = Streams::requestedType(true); $types = Q_Config::expect('Streams', 'types'); if (!array_key_exists($type, $types)) { throw new Q_Exception("This app doesn't support streams of type {$type}", 'type'); } if (empty($types[$type]['create'])) { throw new Q_Exception("This app doesn't support directly creating streams of type {$type}", 'type'); } // Should this stream be related to another stream? $relate = array(); $relate['streamName'] = Q_Request::special("Streams.related.streamName", null, $req); if (isset($relate['streamName'])) { $relate['publisherId'] = Q_Request::special("Streams.related.publisherId", $publisherId, $req); $relate['type'] = Q_Request::special("Streams.related.type", "", $req); $relate['weight'] = "+1"; // TODO: introduce ways to have "1" and "+1" for some admins etc. } // Hold on to any icon that was posted $icon = null; if (!empty($req['icon']) and is_array($req['icon'])) { $icon = $req['icon']; unset($req['icon']); } // Hold on to any file that was posted $file = null; if (!empty($req['file']) and is_array($req['file'])) { $file = $req['file']; unset($req['file']); } // Check if client can set the name of this stream if (!empty($req['name'])) { if ($user->id !== $publisherId or !Q_Config::get('Streams', 'possibleUserStreams', $req['name'], false)) { throw new Users_Exception_NotAuthorized(); } } // Create the stream $allowedFields = array_merge(array('publisherId', 'type', 'icon', 'file'), Streams::getExtendFieldNames($type, $user->id === $publisherId)); $fields = Q::take($req, $allowedFields); $stream = Streams::create($user->id, $publisherId, $type, $fields, $relate, $result); Q_Response::setSlot('messageTo', $result['messageTo']->exportArray()); // Process any icon that was posted if ($icon === true) { $icon = array(); } if (is_array($icon)) { if (empty($icon['path'])) { $icon['path'] = 'uploads/Streams'; } if (empty($icon['subpath'])) { $icon['subpath'] = "{$publisherId}/{$stream->name}/icon/" . time(); } Q_Response::setSlot('icon', Q::event("Q/image/post", $icon)); } // Process any file that was posted if ($file === true) { $file = array(); } if ($file) { if (empty($file['path'])) { $file['path'] = 'uploads/Streams'; } if (empty($file['subpath'])) { $file['subpath'] = "{$publisherId}/{$stream->name}/file/" . time(); } Q_Response::setSlot('file', Q::event("Q/file/post", $file)); } $file = Q::ifset($fieldNames, 'file', null); if (is_array($file)) { unset($fieldNames['file']); Q_Response::setSlot('file', Q::event("Q/file/post", $icon)); } // Re-fetch the stream object from the Streams::fetch cache, // since it might have been retrieved and modified to be different // from what is currently in $stream. // This also calculates the access levels on the stream. $stream = Streams::fetchOne($user->id, $publisherId, $stream->name); if (empty($req['dontSubscribe'])) { // autosubscribe to streams you yourself create, using templates $stream->subscribe(); } Streams::$cache['stream'] = $stream; }
function Streams_before_Q_objects() { $token = Q_Request::special('Streams.token', null); if ($token === null) { return; } $invite = Streams_Invite::fromToken($token); if (!$invite) { throw new Q_Exception_MissingRow(array('table' => 'invite', 'criteria' => "token = '{$token}"), 'token'); } // did invite expire? $ts = Streams_Invite::db()->select("CURRENT_TIMESTAMP")->fetchAll(PDO::FETCH_NUM); if (isset($invite->expireTime) and $invite->expireTime < $ts[0][0]) { $invite->state = 'expired'; $invite->save(); } // is invite still pending? if ($invite->state !== 'pending') { switch ($invite->state) { case 'expired': $exception = new Streams_Exception_AlreadyExpired(null, 'token'); break; case 'accepted': $exception = new Streams_Exception_AlreadyAccepted(null, 'token'); break; case 'declined': $exception = new Streams_Exception_AlreadyDeclined(null, 'token'); break; case 'forwarded': $exception = new Streams_Exception_AlreadyForwarded(null, 'token'); break; default: $exception = new Q_Exception("This invite has already been " . $invite->state, 'token'); break; } $shouldThrow = Q::event('Streams/objects/inviteException', compact('invite', 'exception'), 'before'); if ($shouldThrow === null) { Q_Response::setNotice('Streams/objects', $exception->getMessage(), true); } else { if ($shouldThrow === true) { throw $exception; } } } // now process the invite $invitedUser = Users_User::fetch($invite->userId, true); $stream = Streams::fetchOne($invitedUser->id, $invite->publisherId, $invite->streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId = '{$invite->publisherId}', name = '{$invite->streamName}'")); } $byUser = Users_User::fetch($invite->invitingUserId, true); $byStream = Streams::fetchOne($byUser->id, $invite->publisherId, $invite->streamName); if (!$byStream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId = '{$invite->publisherId}', name = '{$invite->streamName}'")); } $access = new Streams_Access(); $access->publisherId = $byStream->publisherId; $access->streamName = $byStream->name; $access->ofUserId = $invite->userId; $specified_access = false; foreach (array('readLevel', 'writeLevel', 'adminLevel') as $level_type) { $access->{$level_type} = -1; if (empty($invite->{$level_type})) { continue; } // Give access level from the invite. // However, if inviting user has a lower access level now, // then give that level instead, unless it is lower than // what the invited user would have had otherwise. $min = min($invite->{$level_type}, $byStream->get($level_type, 0)); if ($min > $stream->get($level_type, 0)) { $access->{$level_type} = $min; $specified_access = true; } } if ($specified_access) { $access->save(true); } // now log invited user in $user = Users::loggedInUser(); if (empty($user) or $user->id !== $invite->userId) { $user = new Users_User(); $user->id = $invite->userId; if (!$user->retrieve()) { // The user who was invited doesn't exist // This shouldn't happen. We just silently log it and return. Q::log("Sanity check failed: invite with {$invite->token} pointed to nonexistent user"); return; } Users::setLoggedInUser($user); } // accept invite and autosubscribe if first time if ($invite->accept() and !$stream->subscription($user->id)) { $stream->subscribe(); } // retain the invite object for further processing Streams::$followedInvite = $invite; }
/** * Get the stream field from the request, if it can't be deduced throws error * @method requestedField * @static * @param {string} $field * The fiels name * @param {boolean} $throwIfMissing=false * Optional. If true, throws an exception if the stream field cannot be deduced * @param {mixed} $default=null * Is returned if field is not set * @return {string} * The value of the field * @throws {Q_Exception_RequiredField} * If the field value can't be deduced, this is thrown */ static function requestedField($field, $throwIfMissing = false, $default = null) { $uri = Q_Dispatcher::uri(); if (isset($_REQUEST[$field])) { return $_REQUEST[$field]; } else { if (isset($uri->{$field})) { if (is_array($uri->{$field})) { return implode('/', $uri->{$field}); } return $uri->{$field}; } else { if ($field = Q_Request::special("Streams.{$field}", $default)) { return $field; } } } if ($throwIfMissing) { throw new Q_Exception_RequiredField(array('field' => "stream {$field}"), $field); } return $default; }
/** * Post (potentially) multiple messages to multiple streams. * With one call to this function you can post at most one message per stream. * @static * @param {string} $asUserId * The user to post the message as * @param {string} $messages * Array indexed as follows: * array($publisherId => array($streamName => $message)) * where $message are either Streams_Message objects, * or arrays containing all the fields of messages that will need to be posted. * @param {booleam} $skipAccess=false * If true, skips the access checks and just posts the message. * @return {array} * Returns an array(array(Streams_Message), array(Streams_Stream)) */ static function postMessages($asUserId, $messages, $skipAccess = false) { if (!isset($asUserId)) { $asUserId = Users::loggedInUser(); if (!$asUserId) { $asUserId = ""; } } if ($asUserId instanceof Users_User) { $asUserId = $asUserId->id; } // Build arrays we will need foreach ($messages as $publisherId => $arr) { if (!is_array($arr)) { throw new Q_Exception_WrongType(array('field' => "messages", 'type' => 'array of publisherId => streamName => message')); } foreach ($arr as $streamName => &$message) { if (!is_array($message)) { if (!$message instanceof Streams_Message) { throw new Q_Exception_WrongType(array('field' => "message under {$publisherId} => {$streamName}", 'type' => 'array or Streams_Message')); } $message = $message->fields; } } } // Start posting messages, publisher by publisher $eventParams = array(); $posted = array(); $streams = array(); $messages2 = array(); $totals2 = array(); $clientId = Q_Request::special('clientId', ''); $sendToNode = true; foreach ($messages as $publisherId => $arr) { $streamNames = array_keys($messages[$publisherId]); $streams[$publisherId] = $fetched = Streams::fetch($asUserId, $publisherId, $streamNames, '*', array('refetch' => true, 'begin' => true)); foreach ($arr as $streamName => $message) { $p =& $posted[$publisherId][$streamName]; $p = false; $type = isset($message['type']) ? $message['type'] : 'text/small'; $content = isset($message['content']) ? $message['content'] : ''; $instructions = isset($message['instructions']) ? $message['instructions'] : ''; $weight = isset($message['weight']) ? $message['weight'] : 1; if (!isset($message['byClientId'])) { $message['byClientId'] = $clientId ? substr($clientId, 0, 255) : ''; } if (is_array($instructions)) { $instructions = Q::json_encode($instructions); } $byClientId = $message['byClientId']; // Get the Streams_Stream object if (!isset($fetched[$streamName])) { $p = new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId {$publisherId} and name {$streamName}")); continue; } $stream = $fetched[$streamName]; // Make a Streams_Message object $message = new Streams_Message(); $message->publisherId = $publisherId; $message->streamName = $streamName; $message->insertedTime = new Db_Expression("CURRENT_TIMESTAMP"); $message->sentTime = new Db_Expression("CURRENT_TIMESTAMP"); $message->byUserId = $asUserId; $message->byClientId = $byClientId ? substr($byClientId, 0, 31) : ''; $message->type = $type; $message->content = $content; $message->instructions = $instructions; $message->weight = $weight; $message->ordinal = $stream->messageCount + 1; // thanks to transaction // Set up some parameters for the event hooks $eventParams[$publisherId][$streamName] = array('publisherId' => $publisherId, 'message' => $message, 'skipAccess' => $skipAccess, 'sendToNode' => &$sendToNode, 'stream' => $stream); $params = $eventParams[$publisherId][$streamName]; /** * @event Streams/post/$streamType {before} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message * @return {false} To cancel further processing */ if (Q::event("Streams/post/{$stream->type}", $params, 'before') === false) { $results[$stream->name] = false; continue; } /** * @event Streams/message/$messageType {before} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message * @return {false} To cancel further processing */ if (Q::event("Streams/message/{$type}", $params, 'before') === false) { $results[$stream->name] = false; continue; } if (!$skipAccess && !$stream->testWriteLevel('post')) { $p = new Users_Exception_NotAuthorized(); /** * @event Streams/notAuthorized {before} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message */ Q::event("Streams/notAuthorized", $params, 'after'); continue; } // if we are still here, mark the message as "in the database" $message->wasRetrieved(true); $posted[$publisherId][$streamName] = $message; // build the arrays of rows to insert $messages2[] = $mf = $message->fields; $totals2[] = array('publisherId' => $mf['publisherId'], 'streamName' => $mf['streamName'], 'messageType' => $mf['type'], 'messageCount' => 1); } } if ($totals2) { Streams_Total::insertManyAndExecute($totals2, array('onDuplicateKeyUpdate' => array('messageCount' => new Db_Expression('messageCount + 1')))); } if ($messages2) { Streams_Message::insertManyAndExecute($messages2); } // time to update the stream rows and commit the transaction // on all the shards where the streams were fetched. Streams_Stream::update()->set(array('messageCount' => new Db_Expression("messageCount+1")))->where(array('publisherId' => $publisherId, 'name' => $streamNames))->commit()->execute(); // handle all the events for successfully posting foreach ($posted as $publisherId => $arr) { foreach ($arr as $streamName => $m) { $message = $posted[$publisherId][$streamName]; $params =& $eventParams[$publisherId][$streamName]; /** * @event Streams/message/$messageType {after} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message */ Q::event("Streams/message/{$message->type}", $params, 'after', false); /** * @event Streams/post/$streamType {after} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message */ Q::event("Streams/post/{$stream->type}", $params, 'after', false); } } /** * @event Streams/postMessages {after} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} posted */ Q::event("Streams/postMessages", array('streams' => $streams, 'messages' => $messages, 'skipAccess' => $skipAccess, 'posted' => $posted), 'after', false); if ($sendToNode) { Q_Utils::sendToNode(array("Q/method" => "Streams/Message/postMessages", "posted" => Q::json_encode($messages2), "streams" => Q::json_encode($streams))); } return array($posted, $streams); }
/** * Renders Q-specific information for a form * @method formInfo * @static * @param {string} $onSuccess The URI or URL to redirect to in case of success * If you put "true" here, it uses Q_Request::special('onSuccess', $uri), * or if it's not there, then `Q_Dispatcher::uri()` * @param {string} [$onErrors=null] The URI or URL to redirect to in case of errors * If you put "true" here, it uses Q_Request::special('onErrors', $uri), * or if it's not there, then `Q_Dispatcher::uri()` * @param {string} [$sessionNonceField=null] The name of the nonce field to use in the session. * If the config parameter "Q"/"session"/"nonceField" is set, uses that. * @return {string} The generated markup */ static function formInfo($onSuccess, $onErrors = null, $sessionNonceField = null) { $uri = Q_Dispatcher::uri(); if ($onSuccess === true) { $onSuccess = Q_Request::special('onSuccess', $uri); } if ($onErrors === true) { $onErrors = Q_Request::special('onErrors', $uri); } $hiddenFields = array(); if (isset($onSuccess)) { $hiddenFields['Q.onSuccess'] = Q_Uri::url($onSuccess); } if (isset($onErrors)) { $hiddenFields['Q.onErrors'] = Q_Uri::url($onErrors); } if (!isset($sessionNonceField)) { $sessionNonceField = Q_Config::get('Q', 'session', 'nonceField', 'nonce'); } if (isset($sessionNonceField)) { if (!isset($_SESSION['Q'][$sessionNonceField])) { $_SESSION['Q'][$sessionNonceField] = uniqid(); } $hiddenFields['Q.nonce'] = $_SESSION['Q'][$sessionNonceField]; } return self::hidden($hiddenFields); }
/** * The default implementation. */ function Q_errors($params) { extract($params); /** * @var Exception $exception * @var boolean $startedResponse */ if (!empty($exception)) { Q_Response::addError($exception); } $errors = Q_Response::getErrors(); $errors_array = Q_Exception::toArray($errors); // Simply return the errors, if this was an AJAX request if ($is_ajax = Q_Request::isAjax()) { try { $errors_json = @Q::json_encode($errors_array); } catch (Exception $e) { $errors_array = array_slice($errors_array, 0, 1); unset($errors_array[0]['trace']); $errors_json = @Q::json_encode($errors_array); } $json = "{\"errors\": {$errors_json}}"; $callback = Q_Request::callback(); switch (strtolower($is_ajax)) { case 'iframe': if (!Q_Response::$batch) { header("Content-type: text/html"); } echo <<<EOT <!doctype html><html lang=en> <head><meta charset=utf-8><title>Q Result</title></head> <body> <script type="text/javascript"> window.result = function () { return {$json} }; </script> </body> </html> EOT; break; case 'json': default: header("Content-type: " . ($callback ? "application/javascript" : "application/json")); echo $callback ? "{$callback}({$json})" : $json; } return; } // Forward internally, if it was requested if ($onErrors = Q_Request::special('onErrors', null)) { $uri1 = Q_Dispatcher::uri(); $uri2 = Q_Uri::from($onErrors); $url2 = $uri2->toUrl(); if (!isset($uri2)) { throw new Q_Exception_WrongValue(array('field' => 'onErrors', 'range' => 'an internal URI reachable from a URL')); } if ($uri1->toUrl() !== $url2) { Q_Dispatcher::forward($uri2); return; // we don't really need this, but it's here anyway } } $params2 = compact('errors', 'exception', 'errors_array', 'exception_array'); if (Q::eventStack('Q/response')) { // Errors happened while rendering response. Just render errors view. return Q::view('Q/errors.php', $params2); } if (!$startedResponse) { try { // Try rendering the response, expecting it to // display the errors along with the rest. $ob = new Q_OutputBuffer(); Q::event('Q/response', $params2); $ob->endFlush(); return; } catch (Exception $e) { if (get_class($e) === 'Q_Exception_DispatcherForward') { throw $e; // if forwarding was requested, do it // for all other errors, continue trying other things } $output = $ob->getClean(); } } if ($errors) { // Try rendering the app's errors response, if any. $app = Q::app(); if (Q::canHandle("{$app}/errors/response/content")) { Q_Dispatcher::forward("{$app}/errors"); } else { echo Q::view("Q/errors.php", compact('errors')); } } if (!empty($e)) { return Q::event('Q/exception', array('exception' => $e)); } }
/** * Use this to determine what method to treat the request as. * @method method * @static * @return {string} Returns an uppercase string such as "GET", "POST", "PUT", "DELETE" * * See [Request methods](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods) */ static function method() { if (isset(self::$method_override)) { return self::$method_override; } static $result; if (!isset($result)) { /** * @event Q/request/method {before} * @return {string} */ $result = Q::event('Q/request/method', array(), 'before'); if ($result) { return $result; } } if (null !== Q_Request::special('method', null)) { return strtoupper(Q_Request::special('method', null)); } if (!isset($_SERVER['REQUEST_METHOD'])) { return 'GET'; } return strtoupper($_SERVER['REQUEST_METHOD']); }
/** * Default Q/response handler. * 1. Gets some slots, depending on what was requested. * 2. Renders them in a layout * The layout expects "title", "dashboard" and "contents" slots to be filled. */ function Q_response($params) { extract($params); /** * @var Exception $exception * @var array $errors */ if (empty($errors)) { $errors = Q_Response::getErrors(); } if (!empty($_GET['Q_ct'])) { Q_Response::setCookie('Q_ct', $_GET['Q_ct']); } // If output is set, use that $output = Q_Response::output(); if (isset($output)) { if ($output === true) { return; } if (is_string($output)) { echo $output; } return; } // Redirect to success page, if requested. $isAjax = Q_Request::isAjax(); if (empty($errors) and empty($exception)) { if (!$isAjax and null !== Q_Request::special('onSuccess', null)) { $onSuccess = Q_Request::special('onSuccess', null); if (Q_Config::get('Q', 'response', 'onSuccessShowFrom', true)) { $onSuccess = Q_Uri::url($onSuccess . '?Q.fromSuccess=' . Q_Dispatcher::uri()); } Q_Response::redirect($onSuccess); return; } } // Get the requested module $uri = Q_Dispatcher::uri(); if (!isset($module)) { $module = $uri->module; if (!isset($module)) { $module = 'Q'; Q_Dispatcher::uri()->module = 'Q'; } } if (!$isAjax || Q_Request::isLoadExtras()) { Q::event('Q/responseExtras', array(), 'before'); } // Get the main module (the app) $app = Q_Config::expect('Q', 'app'); $action = $uri->action; if (Q::canHandle("{$module}/{$action}/response")) { if (false === Q::event("{$module}/{$action}/response") and !$isAjax) { return; } } $slotNames = Q_Request::slotNames(true); $idPrefixes = array(); if ($temp = Q_Request::special('idPrefixes', null)) { foreach (explode(',', $temp) as $i => $prefix) { if (!isset($slotNames[$i])) { throw new Q_Exception("More id prefixes than slot names", "Q.idPrefixes"); } $idPrefixes[$slotNames[$i]] = $prefix; } } // What to do if this is an AJAX request if ($isAjax) { $to_encode = array(); if (Q_Response::$redirected) { // We already called Q_Response::redirect $to_encode['redirect']['url'] = Q_Uri::url(Q_Response::$redirected); try { $to_encode['redirect']['uri'] = Q_Uri::from(Q_Response::$redirected)->toArray(); } catch (Exception $e) { // couldn't get internal URI } } else { if (is_array($slotNames)) { foreach ($slotNames as $slotName) { Q_Response::fillSlot($slotName, 'default', Q::ifset($idPrefixes, $slotName, null)); } // Go through the slots again, because other handlers may have overwritten // their contents using Q_Response::setSlot() foreach ($slotNames as $sn) { Q_Response::fillSlot($sn, 'default', Q::ifset($idPrefixes, $slotName, null)); } if (Q_Response::$redirected) { // While rendering the slots we called Q_Redirect $to_encode['redirect']['url'] = Q_Uri::url(Q_Response::$redirected); try { $to_encode['redirect']['uri'] = Q_Uri::from(Q_Response::$redirected)->toArray(); } catch (Exception $e) { // couldn't get internal URI } } else { if (Q_Request::isLoadExtras()) { $to_encode['slots'] = Q_Response::slots(true); // add stylesheets, stylesinline, scripts, scriptlines, scriptdata, templates foreach (array_merge(array(''), $slotNames) as $slotName) { $temp = Q_Response::stylesheetsArray($slotName); if ($temp) { $to_encode['stylesheets'][$slotName] = $temp; } $temp = Q_Response::stylesInline($slotName); if ($temp) { $to_encode['stylesInline'][$slotName] = $temp; } $temp = Q_Response::scriptsArray($slotName); if ($temp) { $to_encode['scripts'][$slotName] = $temp; } $temp = Q_Response::scriptLines($slotName, true, "\n", false); if ($temp) { $to_encode['scriptLines'][$slotName] = $temp; } $temp = Q_Response::scriptData($slotName); if ($temp) { $to_encode['scriptData'][$slotName] = $temp; } $temp = Q_Response::templateData($slotName); if ($temp) { $to_encode['templates'][$slotName] = $temp; } } } else { $to_encode['slots'] = Q_Response::slots(true); // add stylesinline, scriptlines, scriptdata, templates foreach (array_merge(array(''), $slotNames) as $slotName) { $temp = Q_Response::stylesInline($slotName); if ($temp) { $to_encode['stylesInline'][$slotName] = $temp; } $temp = Q_Response::scriptData($slotName); if ($temp) { $to_encode['scriptData'][$slotName] = $temp; } $temp = Q_Response::scriptLines($slotName, true, "\n", false); if ($temp) { $to_encode['scriptLines'][$slotName] = $temp; } } } } } } $to_encode['timestamp'] = microtime(true); $echo = Q_Request::contentToEcho(); if (isset($echo)) { $to_encode['echo'] = $echo; } $json = Q::json_encode(Q::cutoff($to_encode)); $callback = Q_Request::callback(); switch (strtolower($isAjax)) { case 'iframe': if (!Q_Response::$batch) { header("Content-type: text/html"); } echo <<<EOT <!doctype html><html lang=en> <head><meta charset=utf-8><title>Q Result</title></head> <body> <script type="text/javascript"> window.result = function () { return {$json} }; </script> </body> </html> EOT; break; case 'json': default: if (!Q_Response::$batch) { header("Content-type: " . ($callback ? "application/javascript" : "application/json")); } echo $callback ? "{$callback}({$json})" : $json; } return; } // If this is a request for a regular webpage, // fill the usual slots and render a layout. if (Q_Response::$redirected) { return; // If already set a redirect header, simply return -- no reason to output all this HTML } static $added_Q_init = false; if (!$added_Q_init) { Q_Response::addScriptLine("\n// Now, initialize Q\nQ.init();\n", null, 'Q'); $added_Q_init = true; } // Get all the usual slots for a webpage $slots = array(); foreach ($slotNames as $sn) { Q_Response::fillSlot($sn, 'default', Q::ifset($idPrefixes, $sn, null)); } // Go through the slots again, because other handlers may have overwritten // their contents using Q_Response::setSlot() foreach ($slotNames as $sn) { Q_Response::fillSlot($sn, 'default', Q::ifset($idPrefixes, $sn, null)); } $output = Q_Response::output(); if (isset($output)) { if ($output === true) { return; } if (is_string($output)) { echo $output; } return; } if (!$isAjax or Q_Request::isLoadExtras()) { Q::event('Q/responseExtras', array(), 'after'); } $slots = Q_Response::slots(false); // Render a full HTML layout $layout_view = Q_Response::layoutView(); echo Q::view($layout_view, $slots); }
/** * Validates the signature of the request (from Q_Request::special('sig', null)) * @method signature * @static * @param {boolean} [$throwIfInvalid=false] If true, throws an exception if the nonce is invalid. * @return {boolean} Whether the phone number seems like it could be valid * @throws {Q_Exception_FailedValidation} */ static function signature($throwIfInvalid = false) { $secret = Q_Config::get('Q', 'internal', 'secret', null); if (!isset($secret)) { return true; } $sgf = Q_Config::get('Q', 'internal', 'sigField', 'sig'); $invalid = false; if (!Q_Request::special($sgf, null)) { $invalid = true; } else { $req = $_REQUEST; unset($req["Q.{$sgf}"]); unset($req["Q_{$sgf}"]); if (Q_Utils::signature($req, $secret) !== Q_Request::special($sgf, null)) { $invalid = true; } } if (!$invalid) { return true; } if ($throwIfInvalid) { header("HTTP/1.0 403 Forbidden"); $message = Q_Config::get('Q', 'internal', 'sigMessage', "The signature did not match."); throw new Q_Exception_FailedValidation(compact('message'), array("Q.{$sgf}", "_[{$sgf}]")); } return false; }
/** * Used by HTTP clients to create a new stream in the system. * @class HTTP Streams stream * @method post * @param {array} [$params] Parameters that can come from the request * @param {string} $params.publisherId Required. The id of the user to publish the stream. * @param {string} $params.type Required. The type of the stream. * @param {string} [$params.Q_Streams_related_publisherId] Optionally indicate the publisher of the stream to relate the newly created to. Used together with the related.streamName option. * @param {string} [$params.Q_Streams_related_streamName] Optionally indicate the name of a stream to relate the newly crated stream to. This is often necessary in order to obtain permissions to create the stream. * @param {bool} [$params.dontSubscribe=false] Pass 1 or true here in order to skip auto-subscribing to the newly created stream. * @param {array} [$params.icon] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.icon.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.icon.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.icon.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.icon.merge=""] path under web dir for an optional image to use as a background * @param {string} [$params.icon.crop] array with keys "x", "y", "w", "h" to crop the original image * @param {string} [$params.icon.save=array("x" => "")] array of $size => $basename pairs * where the size is of the format "WxH", and either W or H can be empty. * @param {array} [$params.file] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.file.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.file.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.file.subpath=""] subpath that should follow the path, to save the file under * @param {string} [$params.file.name] override name of the file, after the subpath */ function Streams_stream_post($params = array()) { $user = Users::loggedInUser(true); $publisherId = Streams::requestedPublisherId(); if (empty($publisherId)) { $publisherId = $_REQUEST['publisherId'] = $user->id; } $req = array_merge($_REQUEST, $params); $type = Streams::requestedType(true); $types = Q_Config::expect('Streams', 'types'); if (!array_key_exists($type, $types)) { throw new Q_Exception("This app doesn't support streams of type {$type}", 'type'); } $create = Streams_Stream::getConfigField($type, 'create', false); if (!$create) { throw new Q_Exception("This app doesn't let clients directly create streams of type {$type}", 'type'); } // Should this stream be related to another stream? $relate = array(); $relate['streamName'] = Q_Request::special("Streams.related.streamName", null, $req); if (isset($relate['streamName'])) { $relate['publisherId'] = Q_Request::special("Streams.related.publisherId", $publisherId, $req); $relate['type'] = Q_Request::special("Streams.related.type", "", $req); $relate['weight'] = "+1"; // TODO: introduce ways to have "1" and "+1" for some admins etc. } // Split the id for saving files in the filesystem $splitId = Q_Utils::splitId($publisherId); // Hold on to any icon that was posted $icon = null; if (!empty($req['icon']) and is_array($req['icon'])) { $icon = $req['icon']; unset($req['icon']); } // Hold on to any file that was posted $file = null; if (!empty($req['file']) and is_array($req['file'])) { $file = $req['file']; unset($req['file']); } // Check if the user owns the stream if ($user->id === $publisherId) { $asOwner = true; } else { $streamTemplate = Streams_Stream::getStreamTemplate($publisherId, $type, 'Streams_Stream'); $asOwner = $streamTemplate ? $streamTemplate->testAdminLevel('own') : false; } // Check if client can set the name of this stream if (isset($req['name'])) { $possible = Q_Config::get('Streams', 'possibleUserStreams', $req['name'], false); if (!$asOwner or !$possible) { throw new Users_Exception_NotAuthorized(); } } // Get allowed fields $allowedFields = array_merge(array('publisherId', 'name', 'type', 'icon', 'file'), Streams::getExtendFieldNames($type, $asOwner)); $fields = Q::take($req, $allowedFields); // Prevent setting restricted fields if (is_array($create)) { $restrictedFields = array_diff($allowedFields, $create); foreach ($restrictedFields as $fieldName) { if (in_array($fieldName, array('publisherId', 'type'))) { continue; } if (isset($req[$fieldName])) { throw new Users_Exception_NotAuthorized(); } } } // Create the stream $stream = Streams::create($user->id, $publisherId, $type, $fields, $relate, $result); $messageTo = false; if (isset($result['messagesTo'])) { $messageTo = reset($result['messagesTo']); $messageTo = reset($messageTo); if (is_array($messageTo)) { $messageTo = reset($messageTo); } $messageTo = $messageTo->exportArray(); } Q_Response::setSlot('messageTo', $messageTo); // Process any icon that was posted if ($icon === true) { $icon = array(); } if (is_array($icon)) { if (empty($icon['path'])) { $icon['path'] = 'uploads/Streams'; } if (empty($icon['subpath'])) { $icon['subpath'] = "{$splitId}/{$stream->name}/icon/" . time(); } Q_Response::setSlot('icon', Q::event("Q/image/post", $icon)); // the Streams/after/Q_image_save hook saves some attributes } // Process any file that was posted if ($file === true) { $file = array(); } if (is_array($file)) { if (empty($file['path'])) { $file['path'] = 'uploads/Streams'; } if (empty($file['subpath'])) { $file['subpath'] = "{$splitId}/{$stream->name}/file/" . time(); } Q_Response::setSlot('file', Q::event("Q/file/post", $file)); // the Streams/after/Q_file_save hook saves some attributes } // Re-fetch the stream object from the Streams::fetch cache, // since it might have been retrieved and modified to be different // from what is currently in $stream. // This also calculates the access levels on the stream. $stream = Streams::fetchOne($user->id, $publisherId, $stream->name); if (empty($req['dontSubscribe'])) { // autosubscribe to streams you yourself create, using templates $stream->subscribe(); } Streams::$cache['stream'] = $stream; }
/** * Validates the signature of the request (from Q_Request::special('sig', null)) * @method signature * @static * @param {boolean} [$throwIfInvalid=false] If true, throws an exception if the nonce is invalid. * @param {array} [$data=$_REQUEST] The data to check the signature of * @param {array|string} [$fieldKeys] Path of the key under which to save signature * @return {boolean} Whether the phone number seems like it could be valid * @throws {Q_Exception_FailedValidation} */ static function signature($throwIfInvalid = false, $data = null, $fieldKeys = null) { if (!isset($data)) { $data = $_REQUEST; } $secret = Q_Config::get('Q', 'internal', 'secret', null); if (!isset($secret)) { return true; } $invalid = true; if (is_array($fieldKeys)) { $ref =& $data; foreach ($fieldKeys as $k) { if (!isset($k)) { break; } $ref2 =& $ref; $ref =& $ref[$k]; } if ($ref) { $signature = $ref; unset($ref2[$k]); $calculated = Q_Utils::signature($data, $secret); if ($calculated === $signature) { $invalid = false; } else { // try with null $ref2[$k] = null; $calculated = Q_Utils::signature($data, $secret); if ($calculated === $signature) { $invalid = false; } } } } else { if (is_string($fieldKeys)) { $signature = $fieldKeys; } else { $sgf = Q_Config::get('Q', 'internal', 'sigField', 'sig'); $signature = Q_Request::special($sgf, null, $data); } if ($signature) { $invalid = false; $req = $data; unset($req["Q.{$sgf}"]); unset($req["Q_{$sgf}"]); if (Q_Utils::signature($req, $secret) !== $signature) { $invalid = true; } } } if (!$invalid) { return true; } if ($throwIfInvalid) { header("HTTP/1.0 403 Forbidden"); $message = Q_Config::get('Q', 'internal', 'sigMessage', "The signature did not match."); throw new Q_Exception_FailedValidation(compact('message'), array("Q.{$sgf}", "_[{$sgf}]")); } return false; }