/** * 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'); }
/** * Send e-mail message * @method sendMessage * @param {string} $subject * The subject. May contain variable references to members * of the $fields array. * @param {string} $view * The name of a view for the body. Fields are passed to it. * @param {array} $fields=array() * The fields referenced in the subject and/or view * @param {array} $options=array() * Array of options. Can include:<br/> * "html" => Defaults to false. Whether to send as HTML email.<br/> * "name" => A human-readable name in addition to the address.<br/> * "from" => An array of (emailAddress, human_readable_name)<br/> * "delay" => A delay, in milliseconds, to wait until sending email. Only works if Node server is listening. */ function sendMessage($subject, $view, $fields = array(), $options = array()) { /** * @event Users/email/sendMessage {before} * @param {string} subject * @param {string} view * @param {array} fields * @param {array} options * @return {boolean} */ $result = Q::event('Users/email/sendMessage', compact('subject', 'view', 'fields', 'options'), 'before'); if (isset($result)) { return $result; } if (!Q_Valid::email($this->address, $emailAddress)) { throw new Q_Exception_WrongType(array('field' => '$this->address', 'type' => 'email address', 'emailAddress' => $this->address)); } $app = Q_Config::expect('Q', 'app'); $subject = Q_Handlebars::renderSource($subject, $fields); $body = Q::view($view, $fields); if (!Q_Config::get('Users', 'email', 'smtp', 'sendmail')) { Q_Response::setNotice("Q/email", "Please set up SMTP in Users/email/smtp as in docs.", false); return true; } $overrideLog = Q::event('Users/email/log', compact('emailAddress', 'subject', 'body'), 'before'); if (!isset($overrideLog) and $key = Q_Config::get('Users', 'email', 'log', 'key', null)) { Q::log("\nSent email message to {$emailAddress}:\n{$subject}\n{$body}", $key); } $from = Q::ifset($options, 'from', Q_Config::get('Users', 'email', 'from', null)); if (!isset($from)) { // deduce from base url $url_parts = parse_url(Q_Request::baseUrl()); $domain = $url_parts['host']; $from = array("email@{$domain}", $domain); } if (!is_array($from)) { throw new Q_Exception_WrongType(array('field' => '$options["from"]', 'type' => 'array')); } $sent = false; if (!empty($options['delay'])) { // Try to use Node.js to send the message $sent = Q_Utils::sendToNode(array("Q/method" => "Users/sendMessage", "delay" => $options['delay'], "emailAddress" => $emailAddress, "subject" => $subject, "body" => $body, "options" => $options)); } if (!$sent) { // Set up the default mail transport $smtp = Q_Config::get('Users', 'email', 'smtp', array('host' => 'sendmail')); $host = Q::ifset($smtp, 'host', 'sendmail'); if ($host === 'sendmail') { $transport = new Zend_Mail_Transport_Sendmail('-f' . reset($from)); } else { if (is_array($smtp)) { $host = $smtp['host']; unset($smtp['host']); } else { if (is_string($smtp)) { $host = $smtp; $smtp = null; } } $transport = new Zend_Mail_Transport_Smtp($host, $smtp); } $mail = new Zend_Mail(); $mail->setFrom(reset($from), next($from)); if (isset($options['name'])) { $mail->addTo($emailAddress, $options['name']); } else { $mail->addTo($emailAddress); } $mail->setSubject($subject); if (empty($options['html'])) { $mail->setBodyText($body); } else { $mail->setBodyHtml($body); } try { $mail->send($transport); } catch (Exception $e) { throw new Users_Exception_EmailMessage(array('error' => $e->getMessage())); } } /** * @event Users/email/sendMessage {after} * @param {string} subject * @param {string} view * @param {array} fields * @param {array} options * @param {string} mail */ Q::event('Users/email/sendMessage', compact('subject', 'view', 'fields', 'options', 'mail', 'app'), 'after'); return true; }
/** * Returns the canonical url of the stream, if any * @return {string|null|false} */ function url() { $uri = self::getConfigField($this->type, 'uri', null); if (!$uri) { return null; } $uriString = Q_Handlebars::renderSource($uri, array('publisherId' => $this->publisherId, 'streamName' => explode('/', $this->name), 'name' => $this->name)); return Q_Uri::from($uriString)->toUrl(); }