/** * Make the stream a member of category or other aggregating stream, * First parameter set - where to add, Second parameter set - what to add * NOTE: this currently only works when fromPublisherId and toPublisherId are on same Q cluster * @method relate * @static * @param {string} $asUserId * The user who is making aggreagtor operation on the stream (add stream to category) * @param {string} $toPublisherId * The user who has published the category stream * @param {string|array} $toStreamName * The name of the category stream. Pass an array of strings to relate a single stream * to multiple categories, but in that case make sure fromStreamName is a string. * @param {string} $type * The type of the relation * @param {string} $fromPublisherId * The user who has published the member stream * @param {string} $fromStreamName * The name of the member stream. Pass an array of strings to relate multiple streams * to a single category, but in that case make sure toStreamName is a string. * @param {array} $options=array() * An array of options that can include: * @param {boolean} [$options.skipAccess=false] If true, skips the access checks and just relates the stream to the category * @param {double|string} [$options.weight] Pass a numeric value here, or something like "max+1" to make the weight 1 greater than the current MAX(weight) * @return {array|boolean} * Returns false if the operation was canceled by a hook * Returns true if relation was already there * Otherwise returns array with keys "messagesFrom" and "messageTo" and values of type Streams_Message */ static function relate($asUserId, $toPublisherId, $toStreamName, $type, $fromPublisherId, $fromStreamName, $options = array()) { self::getRelations($asUserId, $toPublisherId, $toStreamName, $type, $fromPublisherId, $fromStreamName, $relatedToArray, $relatedFromArray, $categories, $streams, $arrayField, $options); // calculate weights $calculateWeights = null; foreach (${$arrayField} as $sn) { if (isset($relatedToArray[$sn])) { continue; } if (!isset($relatedToArray[$sn])) { // at least one new relation will be inserted if (isset($options['weight'])) { $parts = explode('+', "{$options['weight']}"); if (count($parts) > 1) { $calculateWeights = $parts[1]; break; } } } } if (isset($calculateWeights)) { if (!is_numeric($calculateWeights) or $calculateWeights <= 0) { throw new Q_Exception_WrongValue(array('field' => 'weight', 'range' => 'a positive numeric value'), 'weight'); } $rows = Streams_RelatedTo::select('toStreamName, MAX(weight) w')->where(compact('toPublisherId', 'toStreamName', 'type'))->groupBy('toStreamName')->ignoreCache()->fetchAll(PDO::FETCH_ASSOC); $maxWeights = array(); foreach ($rows as $r) { $maxWeights[$r['toStreamName']] = $r['w']; } } $newRT = array(); $newRF = array(); $weights2 = array(); foreach (${$arrayField} as $sn) { if (isset($relatedToArray[$sn])) { continue; } $category = $arrayField === 'toStreamName' ? $categories[$sn] : reset($categories); $stream = $arrayField === 'fromStreamName' ? $streams[$sn] : reset($streams); /** * @event Streams/relateTo/$categoryType {before} * @param {array} relatedTo * @param {array} relatedFrom * @param {string} asUserId * @param {Streams_Stream} category * @param {Streams_Stream} stream * @return {false} To cancel further processing */ if (false === Q::event("Streams/relateTo/{$category->type}", compact('asUserId', 'category', 'stream'), 'before')) { continue; } /** * @event Streams/relateFrom/$streamType {before} * @param {array} relatedTo * @param {array} relatedFrom * @param {string} asUserId * @param {Streams_Stream} category * @param {Streams_Stream} stream * @return {false} To cancel further processing */ if (false === Q::event("Streams/relateFrom/{$stream->type}", compact('asUserId', 'category', 'stream'), 'before')) { continue; } $tsn = $arrayField === 'toStreamName' ? $sn : $toStreamName; $newRT[$sn] = $newRF[$sn] = compact('toPublisherId', 'type', 'fromPublisherId'); if ($calculateWeights) { if (!isset($weights2[$tsn])) { $weights2[$tsn] = isset($maxWeights[$tsn]) ? $maxWeights[$tsn] : 0; } $weights2[$tsn] += $calculateWeights; $newRT[$sn]['weight'] = $weights2[$tsn]; } else { if (isset($options['weight'])) { $weights2[$tsn] = $newRT[$sn]['weight'] = $options['weight']; } } foreach (array('toStreamName', 'fromStreamName') as $f) { $newRT[$sn][$f] = $newRF[$sn][$f] = $f === $arrayField ? $sn : ${$f}; } } // Save all the relatedTo Streams_RelatedTo::insertManyAndExecute($newRT); Streams_RelatedFrom::insertManyAndExecute($newRF); foreach (${$arrayField} as $sn) { $category = $arrayField === 'toStreamName' ? $categories[$sn] : reset($categories); $stream = $arrayField === 'fromStreamName' ? $streams[$sn] : reset($streams); $weight = isset($options['weight']) && is_numeric($options['weight']) ? $options['weight'] : null; $weight = Q::ifset($weights2, $category->name, $weight); $fromUrl = $stream->url(); $fromIcon = $stream->icon; $fromTitle = $stream->title; $fromType = $stream->type; $fromDisplayType = Streams_Stream::displayType($fromType); if (!$category) { continue; } $toUrl = $category->url(); $toIcon = $category->icon; $toTitle = $category->title; $toType = $category->type; $toDisplayType = Streams_Stream::displayType($toType); $parts = explode('/', $type); $displayType = substr(end($parts), 0, -1); $categoryName = explode('/', $category->name); $streamName = explode('/', $stream->name); $params = compact('relatedTo', 'relatedFrom', 'asUserId', 'category', 'stream', 'fromUrl', 'fromIcon', 'fromTitle', 'fromType', 'fromDisplayType', 'toUrl', 'toIcon', 'toTitle', 'toType', 'toDisplayType', 'displayType', 'categoryName', 'streamName'); if ($u = Streams_Stream::getConfigField($category->type, array('relatedTo', $type, 'uri'), Streams_Stream::getConfigField($category->type, array('relatedTo', '*', 'uri', null)))) { $fromUrl = Q_Uri::url(Q_Handlebars::renderSource($u, $params)); } if ($u = Streams_Stream::getConfigField($stream->type, array('relatedFrom', $type, 'uri'), Streams_Stream::getConfigField($stream->type, array('relatedFrom', '*', 'uri', null)))) { $toUrl = Q_Uri::url(Q_Handlebars::renderSource($u, $params)); } $description = Q_Handlebars::renderSource(Streams_Stream::getConfigField($category->type, array('relatedTo', $type, 'description'), Streams_Stream::getConfigField($category->type, array('relatedTo', '*', 'description'), "New {$fromDisplayType} added")), $params); // Send Streams/relatedTo message to a stream // node server will be notified by Streams_Message::post // DISTRIBUTED: in the future, the publishers may be on separate domains // so posting this message may require internet communication. $instructions = compact('fromPublisherId', 'type', 'weight', 'displayType', 'fromUrl', 'toUrl', 'fromIcon', 'fromTitle', 'fromType', 'fromDisplayType', 'description'); $instructions['fromStreamName'] = $stream->name; $relatedTo_messages[$toPublisherId][$category->name][] = array('type' => 'Streams/relatedTo', 'instructions' => $instructions); $description = Q_Handlebars::renderSource(Streams_Stream::getConfigField($stream->type, array('relatedFrom', $type, 'description'), Streams_Stream::getConfigField($stream->type, array('relatedFrom', '*', 'description'), "Added to {{toDisplayType}} as {$displayType}")), $params); // Send Streams/relatedFrom message to a stream // node server will be notified by Streams_Message::post // DISTRIBUTED: in the future, the publishers may be on separate domains // so posting this message may require internet communication. $instructions = compact('toPublisherId', 'type', 'weight', 'displayType', 'fromUrl', 'toUrl', 'toIcon', 'toTitle', 'toType', 'toDisplayType', 'description'); $instructions['toStreamName'] = $category->name; $relatedFrom_messages[$fromPublisherId][$stream->name][] = array('type' => 'Streams/relatedFrom', 'instructions' => $instructions); /** * @event Streams/relateFrom/$streamType {after} * @param {string} relatedTo * @param {string} relatedFrom * @param {string} asUserId * @param {Streams_Stream} category * @param {Streams_Stream} stream */ Q::event("Streams/relate/{$stream->type}", compact('relatedTo', 'relatedFrom', 'asUserId', 'category', 'stream'), 'after'); /** * @event Streams/relateTo/$categoryType {after} * @param {string} relatedTo * @param {string} relatedFrom * @param {string} asUserId * @param {Streams_Stream} category * @param {Streams_Stream} stream */ Q::event("Streams/relateTo/{$category->type}", compact('relatedTo', 'relatedFrom', 'asUserId', 'category', 'stream'), 'after'); } list($messagesTo, $s) = Streams_Message::postMessages($asUserId, $relatedTo_messages, true); list($messagesFrom, $s) = Streams_Message::postMessages($asUserId, $relatedFrom_messages, true); return compact('messagesTo', 'messagesFrom'); }