/** * Subscribe to one or more streams, to start receiving notifications. * Posts "Streams/subscribe" message to the streams. * Also posts "Streams/subscribed" messages to user's "Streams/participating" stream. * If options are not given check the subscription templates: * 1. generic publisher id and generic user * 2. exact publisher id and generic user * 3. generic publisher id and exact user * default is to subscribe to ALL messages. * If options are supplied - skip templates and use options. * Using subscribe if subscription is already active will modify existing * subscription - change type(s) or modify notifications * @method subscribe * @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 {array} [$options.filter] optional array with two keys * @param {array} [$options.filter.types] array of message types, if this is empty then subscribes to all types * @param {array} [$options.filter.notifications=0] limit number of notifications, 0 means no limit * @param {datetime} [$options.untilTime=null] time limit, if any for subscription * @param {array} [$options.rule=array()] optionally override the rule for new subscriptions * @param {array} [$options.rule.deliver=array('to'=>'default')] under "to" key, * named the field under Streams/rules/deliver config, which will contain the names of destinations, * which can include "email", "mobile", "email+pending", "mobile+pending" * @param {datetime} [$options.rule.readyTime] time from which user is ready to receive notifications again * @param {array} [$options.rule.filter] optionally set a filter for the rules to add * @param {boolean} [$options.skipRules] if true, do not attempt to create rules for new subscriptions * @param {boolean} [$options.skipAccess] if true, skip access check for whether user can join and subscribe * @return {array} An array of Streams_Participant rows from the database. */ static function subscribe($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'); } $participants = Streams::join($asUserId, $publisherId, $streams2, array('subscribed' => true, 'noVisit' => true, 'skipAccess' => Q::ifset($options, 'skipAccess', false))); $shouldUpdate = false; if (isset($options['filter'])) { $filter = Q::json_encode($options['filter']); $shouldUpdate = true; } $db = Streams_Subscription::db(); if (isset($options['untilTime'])) { $untilTime = $db->toDateTime($options['untilTime']); $shouldUpdate = true; } $subscriptions = array(); $rows = Streams_Subscription::select('*')->where(array('publisherId' => $publisherId, 'streamName' => $streamNames, 'ofUserId' => $asUserId))->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { $sn = $row['streamName']; $subscriptions[$sn] = $row; } $messages = array(); $pMessages = array(); $streamNamesMissing = array(); $streamNamesUpdate = array(); foreach ($streamNames as $sn) { $messages[$publisherId][$sn] = array('type' => 'Streams/subscribe'); $pMessages[] = array('type' => 'Streams/subscribed', 'instructions' => array('publisherId' => $publisherId, 'streamName' => $sn)); if (empty($subscriptions[$sn])) { $streamNamesMissing[] = $sn; continue; } if ($shouldUpdate) { $streamNamesUpdate[] = $sn; } } if ($streamNamesUpdate) { Streams_Subscription::update()->set(compact('filter', 'untilTime'))->where(array('publisherId' => $publisherId, 'streamName' => $streamNamesUpdate, 'ofUserId' => $asUserId))->execute(); } $rules = array(); if ($streamNamesMissing) { $types = array(); foreach ($streamNamesMissing as $sn) { $stream = $streams2[$sn]; $types[$stream->type][] = $sn; } $subscriptionRows = array(); $ruleRows = array(); foreach ($types as $type => $sns) { // insert subscriptions if (!isset($filter) or !isset($untilTime)) { $templates = Streams_Subscription::select('*')->where(array('publisherId' => array('', $publisherId), 'streamName' => $type . '/', 'ofUserId' => array('', $asUserId)))->fetchAll(PDO::FETCH_ASSOC); $template = null; foreach ($templates as $t) { if (!$template or $template['publisherId'] == '' and $t['publisherId'] !== '' or $template['userId'] == '' and $t['userId'] !== '') { $template = $t; } } } if (!isset($filter)) { $filter = Q::json_encode($template ? Q::json_decode($template['filter']) : Streams_Stream::getConfigField($type, array('subscriptions', 'filter'), array("types" => array("^(?!(Users/)|(Streams/)).*/", "Streams/relatedTo", "Streams/chat/message"), "notifications" => 0))); } if (!isset($untilTime)) { $untilTime = ($template and $template['duration'] > 0) ? new Db_Expression("CURRENT_TIMESTAMP + INTERVAL {$template['duration']} SECOND") : null; } foreach ($sns as $sn) { $subscriptions[$sn] = $subscriptionRows[] = new Streams_Subscription(array('publisherId' => $publisherId, 'streamName' => $sn, 'ofUserId' => $asUserId, 'untilTime' => $untilTime, 'filter' => $filter)); } if (!empty($options['skipRules'])) { continue; } // insert up to one rule per subscription $rule = null; if (isset($options['rule'])) { $rule = $options['rule']; if (isset($rule['readyTime'])) { $rule['readyTime'] = $db->toDateTime($rule['readyTime']); } if (isset($rule['filter']) and is_array($rule['filter'])) { $rule['filter'] = Q::json_encode($rule['filter']); } if (isset($rule['deliver']) and is_array($rule['deliver'])) { $rule['deliver'] = Q::json_encode($rule['deliver']); } } if (!isset($rule)) { $templates = Streams_Rule::select('*')->where(array('ofUserId' => array('', $asUserId), 'publisherId' => array('', $publisherId), 'streamName' => $type . '/', 'ordinal' => 1))->fetchAll(PDO::FETCH_ASSOC); foreach ($templates as $t) { if (!$rule or $rule['userId'] == '' and $t['userId'] !== '' or $rule['publisherId'] == '' and $t['publisherId'] !== '') { $rule = $t; } } } if (!isset($rule)) { $rule = array('deliver' => '{"to": "default"}', 'filter' => '{"types": [], "labels": []}'); } if ($rule) { $rule['ofUserId'] = $asUserId; $rule['publisherId'] = $publisherId; if (empty($rule['readyTime'])) { $rule['readyTime'] = new Db_Expression("CURRENT_TIMESTAMP"); } foreach ($sns as $sn) { $row = $rule; $row['streamName'] = $sn; $row['ordinal'] = 1; $row['filter'] = ''; $rules[$sn] = $ruleRows[] = $row; $messages[$publisherId][$sn]['instructions'] = Q::json_encode(array('rule' => $row)); } } } Streams_Subscription::insertManyAndExecute($subscriptionRows); Streams_Rule::insertManyAndExecute($ruleRows); } foreach ($streamNames as $sn) { $subscription = $subscriptions[$sn]; $stream = $streams2[$sn]; // skip error testing for rule save BUT inform node. // Node can notify user to check the rules Q_Utils::sendToNode(array("Q/method" => "Streams/Stream/subscribe", "subscription" => Q::json_encode($subscription), "stream" => Q::json_encode($stream->toArray()), "rule" => isset($rules[$sn]) ? Q::json_encode($rules[$sn]) : null)); } Streams_Message::postMessages($asUserId, $messages, true); Streams_Message::postMessages($asUserId, array($asUserId => array('Streams/participating' => $pMessages)), true); return $participants; }
/** * If the user is not participating in the stream yet, * inserts a participant record and posts a "Streams/join" or "Streams/visit" type message * to the stream, depending on whether the user is already participating in the stream. * Otherwise updates the participant record's timestamp and other things. * Also a posts "Streams/joined" or "Streams/visited" message on the user's * "Streams/participating" stream. * @method join * @param $options=array() {array} An associative array of options. * @param {boolean} [$options.subscribed] If true, the user is set as subscribed * @param {boolean} [$options.posted] If true, the user is set as subscribed * @param {array} [$options.extra] Any extra information for the message * @param {string} [$options.userId] The user who is joining the stream. Defaults to the logged-in user. * @param {boolean} [$options.noVisit] If user is already participating, don't post a "Streams/visited" message * @param {boolean} [$options.skipAccess] If true, skip access check for whether user can join * @return {Streams_Participant|null} */ function join($options = array(), &$participant = null) { $userId = $this->_verifyUser($options); $participants = Streams::join($userId, $this->publisherId, array($this->name), $options); return $participants ? reset($participants) : null; }