/** * Assigns ordinal and readyTime * @method beforeSave * @param {array} $value * The row beind saved * @return {array} */ function beforeSave($value) { if (!$this->retrieved) { $max = Streams_Rule::select("MAX(ordinal) + 1")->where(array('ofUserId' => $this->ofUserId, 'publisherId' => $this->publisherId, 'streamName' => $this->streamName))->ignoreCache()->fetchAll(PDO::FETCH_COLUMN); $value['ordinal'] = $this->ordinal = isset($max[0]) ? $max[0] : 1; if (!isset($this->readyTime)) { $value['readyTime'] = $this->readyTime = new Db_Expression('CURRENT_TIMESTAMP'); } } return parent::beforeSave($value); }
/** * Subscription tool * @param array $options * "publisherId" => the id of the user who is publishing the stream * "streamName" => the name of the stream for which to edit access levels */ function Streams_subscription_tool($options) { $subscribed = 'no'; extract($options); $user = Users::loggedInUser(true); if (!isset($publisherId)) { $publisherId = Streams::requestedPublisherId(true); } if (!isset($streamName)) { $streamName = Streams::requestedName(); } $stream = Streams::fetchOne($user->id, $publisherId, $streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => compact('publisherId', 'streamName'))); } $streams_participant = new Streams_Participant(); $streams_participant->publisherId = $publisherId; $streams_participant->streamName = $streamName; $streams_participant->userId = $user->id; if ($streams_participant->retrieve()) { $subscribed = $streams_participant->subscribed; } $types = Q_Config::get('Streams', 'types', $stream->type, 'messages', array()); $messageTypes = array(); foreach ($types as $type => $msg) { $name = Q::ifset($msg, 'title', $type); /* * group by name */ foreach ($messageTypes as $msgType) { if ($msgType['name'] == $name) { continue 2; } } $messageTypes[] = array('value' => $type, 'name' => $name); } $usersFetch = array('userId' => $user->id, 'state' => 'active'); $devices = array(); $emails = Users_Email::select('address')->where($usersFetch)->fetchAll(PDO::FETCH_COLUMN); $mobiles = Users_Mobile::select('number')->where($usersFetch)->fetchAll(PDO::FETCH_COLUMN); foreach ($emails as $email) { $devices[] = array('value' => Q::json_encode(array('email' => $email)), 'name' => 'my email'); } foreach ($mobiles as $mobile) { $devices[] = array('value' => Q::json_encode(array('mobile' => $mobile)), 'name' => 'my mobile'); } $items = array(); $rules = Streams_Rule::select('deliver, filter')->where(array('ofUserId' => $user->id, 'publisherId' => $publisherId, 'streamName' => $streamName))->fetchAll(PDO::FETCH_ASSOC); while ($rule = array_pop($rules)) { $filter = json_decode($rule['filter']); /* * group by name */ foreach ($rules as $val) { if (json_decode($val['filter'])->labels == $filter->labels) { continue 2; } } $items[] = array('deliver' => json_decode($rule['deliver']), 'filter' => $filter); } Q_Response::addScript("plugins/Streams/js/Streams.js"); Q_Response::addScript("plugins/Streams/js/tools/subscription.js"); Q_Response::setToolOptions(compact('items', 'subscribed', 'messageTypes', 'devices', 'publisherId', 'streamName')); }
/** * Create or update subscription */ function Streams_subscription_put($params) { $items = array(); $subscribed = 'no'; $updateTemplate = true; $streamName = Streams::requestedName(); $publisherId = Streams::requestedPublisherId(true); $user = Users::loggedInUser(true); extract($_REQUEST); $items = json_decode($items, true); $stream = Streams::fetchOne($user->id, $publisherId, $streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => compact('publisherId', 'streamName'))); } $rules = Streams_Rule::select('*')->where(array('ofUserId' => $user->id, 'publisherId' => $publisherId, 'streamName' => $streamName))->fetchDbRows(null, '', 'ordinal'); $types = Q_Config::get('Streams', 'types', $stream->type, 'messages', array()); if ($subscribed !== 'no') { // update rules while ($item = array_pop($items)) { // join "grouped" message types to $items foreach ($types as $type => $msg) { if ($msg['title'] == $item['filter']->labels and $type != $item['filter']->types) { $items[] = (object) array('deliver' => $item->deliver, 'filter' => array('types' => $type, 'labels' => $msg['title'], 'notifications' => $item['filter']->notifications)); } } if (!($rule = array_pop($rules))) { $rule = new Streams_Rule(); $rule->ofUserId = $user->id; $rule->publisherId = $publisherId; $rule->streamName = $streamName; $rule->relevance = 1; } $rule->filter = Q::json_encode($item['filter']); $rule->deliver = Q::json_encode($item['deliver']); $rule->save(); } } foreach ($rules as $rule) { $rule->remove(); } $streams_subscription = new Streams_Subscription(); $streams_subscription->streamName = $streamName; $streams_subscription->publisherId = $publisherId; $streams_subscription->ofUserId = $user->id; $streams_subscription->filter = Q::json_encode(array()); $streams_subscription->retrieve(); $streams_participant = new Streams_Participant(); $streams_participant->publisherId = $publisherId; $streams_participant->streamName = $streamName; $streams_participant->userId = $user->id; $streams_participant->state = 'participating'; $streams_participant->reason = ''; $streams_participant->retrieve(); $streams_participant->subscribed = $subscribed; $streams_participant->save(); if ($subscribed === 'yes') { $stream->subscribe(array('skipRules' => true)); } else { $stream->unsubscribe(); } }
/** * Subscribe to the stream's messages<br/> * If options are not given check the subscription templates: * 1. exact stream name and exact user id * 2. generic stream name and exact user id * 3. exact stream name and generic user * 4. generic stream name and generic user * default is to subscribe to ALL messages. * If options supplied - skip templates and use options<br/><br/> * Using subscribe if subscription is already active will modify existing * subscription - change type(s) or modify notifications * @method subscribe * @param $options=array() {array} * @param {array} [$options.types] array of message types, if this is empty then subscribes to all types * @param {integer} [$options.notifications=0] limit number of notifications, 0 means no limit * @param {datetime} [$options.untilTime=null] time limit, if any for subscription * @param {datetime} [$options.readyTime] time from which user is ready to receive notifications again * @param {string} [$options.userId] the user subscribing to the stream. Defaults to the logged in user. * @param {array} [$options.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 {boolean} [$options.skipRules] if true, do not attempt to create rules * @param {boolean} [$options.skipAccess] if true, skip access check for whether user can subscribe * @return {Streams_Subscription|false} */ function subscribe($options = array()) { $stream = $this->fetchAsUser($options, $userId, $user); if (empty($options['skipAccess']) and !$stream->testReadLevel('messages')) { if (!$stream->testReadLevel('see')) { throw new Streams_Exception_NoSuchStream(); } throw new Users_Exception_NotAuthorized(); } // first make user a participant $stream->join(array("userId" => $userId, "subscribed" => true, "noVisit" => true, "skipAccess" => Q::ifset($options, 'skipAccess', false))); // check for 'messages' level $s = new Streams_Subscription(); $s->publisherId = $stream->publisherId; $s->streamName = $stream->name; $s->ofUserId = $userId; $s->retrieve(); $type = null; if ($template = $stream->getSubscriptionTemplate('Streams_Subscription', $userId, $type)) { $filter = json_decode($template->filter, true); } else { $filter = array('types' => array(), 'notifications' => 0); } if (isset($options['types'])) { $filter['types'] = !empty($options['types']) ? $options['types'] : $filter['types']; } if (isset($options['notifications'])) { $filter['notifications'] = $options['notifications']; } $s->filter = Q::json_encode($filter); if (isset($options['untilTime'])) { $s->untilTime = $options['untilTime']; } else { if ($type > 0 and $template and $template->duration > 0) { $s->untilTime = date("c", time() + $template->duration); } } if (!$s->save(true)) { return false; } if (empty($options['skipRules'])) { // Now let's handle rules $type2 = null; $template = $stream->getSubscriptionTemplate('Streams_Rule', $userId, $type2); $ruleSuccess = true; if ($type2 !== 0) { $rule = new Streams_Rule(); $rule->ofUserId = $userId; $rule->publisherId = $stream->publisherId; $rule->streamName = $stream->name; if (empty($template) and $rule->retrieve()) { $ruleSuccess = false; } else { $rule->readyTime = isset($options['readyTime']) ? $options['readyTime'] : new Db_Expression('CURRENT_TIMESTAMP'); $rule->filter = !empty($template->filter) ? $template->filter : '{"types":[],"labels":[]}'; $rule->relevance = !empty($template->relevance) ? $template->relevance : 1; $rule->deliver = !empty($template->deliver) ? $template->deliver : Q::json_encode(Q::ifset($options, 'deliver', array('to' => 'default'))); $ruleSuccess = !!$rule->save(); } } } // 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($s->toArray()), "stream" => Q::json_encode($stream->toArray()), "success" => Q::json_encode($ruleSuccess))); // Post Streams/subscribe message to the stream $stream->post($userId, array('type' => 'Streams/subscribe'), true); // Now post Streams/subscribed message to Streams/participating Streams_Message::post($userId, $userId, 'Streams/participating', array('type' => 'Streams/subscribed', 'instructions' => Q::json_encode(array('publisherId' => $stream->publisherId, 'streamName' => $stream->name))), true); return $s; }
/** * 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; }