Example #1
0
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);
        }
    }
}
Example #2
0
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'));
}
Example #3
0
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'));
}
Example #4
0
 /**
  * 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']));
     //		}
 }
Example #5
0
 /**
  * @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;
     }
 }
Example #6
0
/**
 * 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;
}
Example #7
0
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;
}
Example #8
0
 /**
  * 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;
 }
Example #9
0
 /**
  * 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);
 }
Example #10
0
 /**
  * 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);
 }
Example #11
0
/**
 * 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));
    }
}
Example #12
0
 /**
  * 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']);
 }
Example #13
0
/**
 * 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);
}
Example #14
0
 /**
  * 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;
 }
Example #15
0
/**
 * 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;
}
Example #16
0
 /**
  * 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;
 }