Example #1
0
/**
 * Used to post messages to EXISTING stream
 * $_REQUEST shall contain the content of the message. Also may include 'streamNames' 
 * field which is an array of additional names of the streams to post message to.
 *
 * @param string $params 
 *   publisher id and stream name of existing stream shall be supplied
 * @return {void}
 */
function Streams_message_post()
{
    $user = Users::loggedInUser(true);
    $publisherId = Streams::requestedPublisherId(true);
    $streamName = Streams::requestedName(true);
    // check if type is allowed
    $streams = Streams::fetch($user->id, $publisherId, $streamName);
    if (empty($streams)) {
        throw new Streams_Exception_NoSuchStream();
    }
    $stream = reset($streams);
    if (empty($_REQUEST['type'])) {
        throw new Q_Exception_RequiredField(array('field' => 'type'), 'type');
    }
    $type = $_REQUEST['type'];
    if (!Q_Config::get("Streams", "types", $stream->type, "messages", $type, 'post', false)) {
        throw new Q_Exception("This app doesn't support directly posting messages of type '{$type}' for streams of type '{$stream->type}'");
    }
    $result = Streams_Message::post($user->id, $publisherId, $streamName, $_REQUEST);
    if (is_array($result)) {
        Streams::$cache['messages'] = $result;
    } else {
        Streams::$cache['message'] = $result;
    }
}
Example #2
0
function Broadcast_control_response_content($params)
{
    $user = Users::loggedInUser(true);
    $organizations = Broadcast_Agreement::select('a.userId, a.publisherId, u.organization_title, u.organization_domain', 'a')->join(Broadcast_User::table() . ' u', array('a.publisherId' => 'u.userId'))->where(array('a.userId' => $user->id))->fetchAll(PDO::FETCH_ASSOC);
    foreach ($organizations as $k => $org) {
        $messages = Streams_Message::select('content')->where(array('publisherId' => $org['publisherId'], 'streamName' => 'Broadcast/main'))->orderBy('sentTime')->fetchAll(PDO::FETCH_ASSOC);
        $organizations[$k]['messages'] = array();
        foreach ($messages as $msg) {
            $content = json_decode($msg['content'], true);
            if (isset($content['link'])) {
                $ch = curl_init();
                $timeout = 5;
                curl_setopt($ch, CURLOPT_URL, $content['link']);
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
                curl_setopt($ch, CURLOPT_HEADER, 0);
                curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; U; Linux i686; cs-CZ; rv:1.7.12) Gecko/20050929");
                $page_contents = curl_exec($ch);
                curl_close($ch);
                preg_match('/<title>([^<]+)<\\/title>/', $page_contents, $matches);
                if (isset($matches[1])) {
                    $content['link_title'] = $matches[1];
                }
            }
            $organizations[$k]['messages'][] = $content;
        }
    }
    Q_Config::set('Q', 'response', 'Broadcast', 'layout_html', 'Broadcast/layout/canvas.php');
    Q_Response::addStylesheet('css/canvas.css');
    Q_Response::addScript('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js');
    Q_Response::addScript('js/canvas.js');
    return Q::view('Broadcast/content/control.php', compact('organizations'));
}
function Streams_after_Users_Contact_removeExecute($params)
{
    // Update avatar as viewed by everyone who was in that contacts list
    $contacts = Streams::$cache['contacts_removed'];
    foreach ($contacts as $contact) {
        Streams::updateAvatar($contact->contactUserId, $contact->userId);
    }
    Streams_Message::post(null, $contact->userId, 'Streams/contacts', array('type' => 'Streams/contacts/removed', 'instructions' => array('contacts' => Db::exportArray($contacts))), true);
}
function Streams_after_Users_Label_saveExecute($params)
{
    // The icon or title might have been modified
    $modifiedFields = $params['modifiedFields'];
    $label = $params['row'];
    $updates = Q::take($modifiedFields, array('icon', 'title'));
    $updates['userId'] = $label->userId;
    $updates['label'] = $label->label;
    return Streams_Message::post(null, $label->userId, "Streams/labels", array('type' => 'Streams/labels/updated', 'instructions' => compact('updates')), true);
}
function Streams_after_Users_Contact_saveExecute($params)
{
    $inserted = $params['inserted'];
    $modifiedFields = $params['modifiedFields'];
    $contact = $params['row'];
    if ($inserted) {
        Streams_Message::post(null, $contact->userId, 'Streams/contacts', array('type' => 'Streams/contacts/inserted', 'instructions' => array('contact' => $contact->exportArray())), true);
    } else {
        $updates = Q::take($modifiedFields, array('nickname'));
        $updates = array_merge($contact->toArray(), $updates);
        Streams_Message::post(null, $contact->userId, 'Streams/contacts', array('type' => 'Streams/contacts/updated', 'instructions' => compact('updates')), true);
    }
}
function Streams_after_Users_Label_saveExecute($params)
{
    // The icon or title might have been modified
    $inserted = $params['inserted'];
    $modifiedFields = $params['modifiedFields'];
    $label = $params['row'];
    if ($inserted) {
        Streams_Message::post(null, $label->userId, 'Streams/labels', array('type' => 'Streams/labels/inserted', 'instructions' => array('label' => $label->exportArray())), true);
    } else {
        $updates = Q::take($modifiedFields, array('icon', 'title'));
        $updates = array_merge($label->toArray(), $updates);
        Streams_Message::post(null, $label->userId, "Streams/labels", array('type' => 'Streams/labels/updated', 'instructions' => compact('updates')), true);
    }
}
function Streams_after_Users_Label_removeExecute($params)
{
    $label = $params['row'];
    Streams_Message::post(null, $label->userId, 'Streams/labels', array('type' => 'Streams/labels/removed', 'instructions' => array('label' => $label->toArray())), true);
}
Example #8
0
 /**
  * Fetch messages of the stream.
  * @method getMessages
  * @param {array} [options=array()] An array of options determining how messages will be fetched, which can include:
  *   "min" => Minimum ordinal of the message to select from (inclusive). Defaults to minimum ordinal of existing messages (if any).
  *   "max" => Maximum ordinal of the message to select to (inclusive). Defaults to maximum ordinal of existing messages (if any).
  *   Can also be negative, then the value will be substracted from maximum number of existing messages and +1 will be added
  *   to guarantee that $max = -1 means highest message ordinal.
  *   "limit" => Number of the messages to be selected. Defaults to 1000.
  *   "ascending" => Sorting of fetched messages by ordinal. If true, sorting is ascending, if false - descending.
  *   Defaults to true, but in case if 'min' option not given and only 'max' and 'limit' are given, we assuming
  *   fetching in reverse order, so 'ascending' will default to false.
  *   "type" => Optional string specifying the particular type of messages to get
  */
 function getMessages($options)
 {
     // preparing default query
     $criteria = array('publisherId' => $this->publisherId, 'streamName' => $this->name);
     if (!empty($options['type'])) {
         $criteria['type'] = $options['type'];
     }
     $q = Streams_Message::select('*')->where($criteria);
     // getting $min and $max
     $result = Streams_Message::select("MIN(ordinal) AS min, MAX(ordinal) AS max")->where($criteria)->fetchAll(PDO::FETCH_ASSOC);
     if (!$result[0]) {
         return array();
     }
     $min = (int) $result[0]['min'];
     $max = (int) $result[0]['max'];
     // default sorting is 'ORDER BY `ordinal` ASC', but it can be changed depending on options
     $ascending = true;
     if (!isset($options['min'])) {
         $options['min'] = $min;
         // if 'min' is not given, assume 'reverse' fetching, so $ascending is false
         $ascending = false;
     }
     if (!isset($options['max'])) {
         $options['max'] = $max;
     } else {
         if ($options['max'] < 0) {
             // if 'max' is negative, substract value from existing maximum
             $options['max'] = $max + $options['max'] + 1;
         }
     }
     if (empty($options['limit'])) {
         $options['limit'] = self::getConfigField($this->type, 'getMessagesLimit', 100);
     }
     if ($options['min'] > $options['max']) {
         return array();
     }
     $q->where(array('ordinal >=' => $options['min'], 'ordinal <=' => $options['max']));
     $q->limit($options['limit']);
     $q->orderBy('ordinal', isset($options['ascending']) ? $options['ascending'] : $ascending);
     return $q->fetchDbRows(null, '', 'ordinal');
 }
Example #9
0
 /**
  * Updates the weight on a relation
  * @param {string} $asUserId
  *  The id of the user on whose behalf the app will be updating the relation
  * @param {string} $toPublisherId
  *  The publisher of the stream on the 'to' end of the reltion
  * @param {string} $toStreamName
  *  The name of the stream on the 'to' end of the relation
  * @param {string} $type
  *  The type of the relation
  * @param {string} $fromPublisherId
  *  The publisher of the stream on the 'from' end of the reltion
  * @param {string} $fromStreamName
  *  The name of the stream on the 'from' end of the reltion
  * @param {double} $weight
  *  The new weight
  * @param {double} $adjustWeights=null
  *  The amount to move the other weights by, to make room for this one
  * @param {array} $options=array()
  *  An array of options that can include:
  *  "skipAccess" => Defaults to false. If true, skips the access checks and just updates the weight on the relation
  * @return {array|boolean}
  *  Returns false if the operation was canceled by a hook
  *  Otherwise returns array with key "to" and value of type Streams_Message
  */
 static function updateRelation($asUserId, $toPublisherId, $toStreamName, $type, $fromPublisherId, $fromStreamName, $weight, $adjustWeights = null, $options = array())
 {
     self::getRelation($asUserId, $toPublisherId, $toStreamName, $type, $fromPublisherId, $fromStreamName, $relatedTo, $relatedFrom, $category, $stream, $options);
     if (!$relatedTo->retrieve()) {
         throw new Q_Exception_MissingRow(array('table' => 'relatedTo', 'criteria' => 'with those fields'), array('publisherId', 'name', 'type', 'toPublisherId', 'to_name'));
     }
     //		if (!$relatedFrom->retrieve()) {
     //			throw new Q_Exception_MissingRow(
     //				array('table' => 'relatedFrom', 'criteria' => 'those fields'),
     //				array('publisherId', 'name', 'type', 'fromPublisherId', 'from_name')
     //			);
     //		}
     if (empty($options['skipAccess'])) {
         if (!$category->testWriteLevel('relations')) {
             throw new Users_Exception_NotAuthorized();
         }
     }
     /**
      * @event Streams/updateRelation/$streamType {before}
      * @param {string} relatedTo
      * @param {string} relatedFrom
      * @param {string} asUserId
      * @param {double} weight
      * @param {double} previousWeight
      */
     $previousWeight = $relatedTo->weight;
     $adjustWeightsBy = $weight < $previousWeight ? $adjustWeights : -$adjustWeights;
     if (Q::event("Streams/updateRelation/{$stream->type}", compact('relatedTo', 'relatedFrom', 'type', 'weight', 'previousWeight', 'adjustWeightsBy', 'asUserId'), 'before') === false) {
         return false;
     }
     if (!empty($adjustWeights) and is_numeric($adjustWeights) and $weight !== $previousWeight) {
         $criteria = array('toPublisherId' => $toPublisherId, 'toStreamName' => $toStreamName, 'type' => $type, 'weight' => $weight < $previousWeight ? new Db_Range($weight, true, false, $previousWeight) : new Db_Range($previousWeight, false, true, $weight));
         Streams_RelatedTo::update()->set(array('weight' => new Db_Expression("weight + " . $adjustWeightsBy)))->where($criteria)->execute();
     }
     $relatedTo->weight = $weight;
     $relatedTo->save();
     // Send Streams/updatedRelateTo message to the category stream
     // node server will be notified by Streams_Message::post
     $message = Streams_Message::post($asUserId, $toPublisherId, $toStreamName, array('type' => 'Streams/updatedRelateTo', 'instructions' => Q::json_encode(compact('fromPublisherId', 'fromStreamName', 'type', 'weight', 'previousWeight', 'adjustWeightsBy', 'asUserId'))), true);
     // TODO: We are not yet sending Streams/updatedRelateFrom message to the other stream
     // because we might be changing a lot of weights, and we'd have to message a lot of streams.
     // This is better done in the background using Node.js after selecting using $criteria
     // When we implement this, we can introduce weight again in the relatedFrom table.
     /**
      * @event Streams/updateRelation/$streamType {after}
      * @param {string} relatedTo
      * @param {string} relatedFrom
      * @param {string} asUserId
      * @param {double} weight
      * @param {double} previousWeight
      */
     Q::event("Streams/updateRelation/{$stream->type}", compact('relatedTo', 'relatedFrom', 'type', 'weight', 'previousWeight', 'adjustWeightsBy', 'asUserId'), 'after');
     return $message;
 }
Example #10
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 #11
0
 /**
  * Send credits, as the logged-in user, to another user
  * @method send
  * @static
  * @param {integer} $amount The amount of credits to send.
  * @param {string} $toUserId The id of the user to whom you will send the credits
  * @param {array} $more An array supplying more info, including
  *  "reason" => Identifies the reason for sending, if any
  */
 static function send($amount, $toUserId, $more = array())
 {
     if (!is_int($amount) or $amount <= 0) {
         throw new Q_Exception_WrongType(array('field' => 'amount', 'type' => 'integer'));
     }
     $user = Users::loggedInUser(true);
     $from_stream = new Streams_Stream();
     $from_stream->publisherId = $user->id;
     $from_stream->name = 'Awards/credits';
     if (!$from_stream->retrieve()) {
         $from_stream = self::createStream($user);
     }
     $existing_amount = $from_stream->getAttribute('amount');
     if ($existing_amount < $amount) {
         throw new Awards_Exception_NotEnoughCredits(array('missing' => $amount - $existing_amount));
     }
     $to_user = Users_User::fetch($toUserId, true);
     $to_stream = new Streams_Stream();
     $to_stream->publisherId = $toUserId;
     $to_stream->name = 'Awards/credits';
     if (!$to_stream->retrieve()) {
         $to_stream = self::createStream($to_user);
     }
     $to_stream->setAttribute('amount', $to_stream->getAttribute('amount') - $amount);
     $to_stream->save();
     // NOTE: we are not doing transactions here mainly because of sharding.
     // If if we reached this point without exceptions, that means everything worked.
     // But if the following statement fails, then someone will get free credits.
     $from_stream->setAttribute('amount', $from_stream->getAttribute('amount') - $amount);
     $from_stream->save();
     $instructions_json = Q::json_encode(array_merge(array('app' => Q_Config::expect('Q', 'app')), $more));
     Streams_Message::post($user->id, $userId, array('type' => 'Awards/credits/sent', 'content' => $amount, 'instructions' => $instructions_json));
     Streams_Message::post($user->id, $toUserId, array('type' => 'Awards/credits/received', 'content' => $amount, 'instructions' => $instructions_json));
 }
Example #12
0
 /**
  * Unsubscribe from one or more streams, to stop receiving notifications.
  * Pooststs "Streams/unsubscribe" message to the streams.
  * Also posts "Streams/unsubscribed" messages to user's "Streams/participating" stream.
  * Does not change the actual subscription, but only the participant row.
  * (When subscribing again, the existing subscription will be used.)
  * @method unsubscribe
  * @static
  * @param {string} $asUserId The id of the user that is joining. Pass null here to use the logged-in user's id.
  * @param {string} $publisherId The id of the user publishing all the streams
  * @param {array} $streams An array of Streams_Stream objects or stream names
  * @param {array} [$options=array()]
  * @param {boolean} [$options.leave] set to true to also leave the streams
  * @param {boolean} [$options.skipAccess] if true, skip access check for whether user can join and subscribe
  * @return {array} Returns an array of Streams_Participant rows, if any were in the database.
  */
 static function unsubscribe($asUserId, $publisherId, $streams, $options = array())
 {
     $streams2 = self::_getStreams($asUserId, $publisherId, $streams);
     $streamNames = array();
     foreach ($streams2 as $s) {
         $streamNames[] = $s->name;
     }
     if (empty($options['skipAccess'])) {
         self::_accessExceptions($streams2, $streamNames, 'join');
     }
     $skipAccess = Q::ifset($options, 'skipAccess', false);
     if (empty($options['leave'])) {
         $criteria = array('publisherId' => $publisherId, 'streamName' => $streamNames, 'userId' => $asUserId);
         Streams_Participant::update()->set(array('subscribed' => 'no'))->where($criteria)->execute();
         $participants = Streams_Participant::select('*')->where($criteria)->fetchDbRows();
     } else {
         $participants = Streams::leave($asUserId, $publisherId, $streams2, compact('skipAccess'));
     }
     $messages = array();
     $pMessages = array();
     foreach ($streamNames as $sn) {
         $stream = $streams2[$sn];
         if ($participant = Q::ifset($participants, $sn, null)) {
             if ($participant instanceof Streams_Participant) {
                 $participant = $participant->toArray();
             }
         }
         // Send a message to Node
         Q_Utils::sendToNode(array("Q/method" => "Streams/Stream/unsubscribe", "participant" => Q::json_encode($participant), "stream" => Q::json_encode($stream->toArray())));
         // Stream messages to post
         $messages[$publisherId][$sn] = array('type' => 'Streams/unsubscribe');
         $pMessages[] = array('type' => 'Streams/unsubscribed', 'instructions' => array('publisherId' => $publisherId, 'streamName' => $sn));
     }
     Streams_Message::postMessages($asUserId, $messages, true);
     Streams_Message::postMessages($asUserId, array($asUserId => array('Streams/participating' => $pMessages)), true);
     return $participants;
 }