function Users_device_post() { $user = Users::loggedInUser(true); $token = isset($_REQUEST['token']) ? $_REQUEST['token'] : null; $platform = Q_Request::platform(); $version = Q_Request::OSVersion(); $formFactor = Q_Request::isMobile() ? 'mobile' : (Q_Request::isTablet() ? 'tablet' : null); $device = new Users_Device(); $device->userId = $user->id; $device->deviceId = $token; $device->platform = $platform; $device->version = $version; $device->formFactor = $formFactor; $device->sessionId = Q_Session::id(); $_SESSION['Users']['deviceId'] = $token; Q_Response::setSlot('data', !!$device->save(true)); Q_Utils::sendToNode(array("Q/method" => "Users/device", "userId" => $user->id, "deviceId" => $token)); }
function Users_after_Q_session_write($params) { Q::$state['session'] = true; if (!$params['changed']) { return; } // Q::autoload('Db'); // Q::autoload('Db_Mysql'); // Q::autoload('Db_Result'); // Q::autoload('Db_Expression'); // Q::autoload('Db_Query'); // Q::autoload('Db_Query_Mysql'); // Q::autoload('Db_Row'); // Q::autoload('Base_Users_Session'); // Q::autoload('Base_Users'); // Q::autoload('Users'); Q::autoload('Q_Utils'); Q::autoload('Q_Config'); Q::autoload('Q_Session'); $id = Q_Session::id(); if (!$id) { return; } $parts = explode('-', $id); $duration = count($parts) > 1 ? $parts[0] : 0; $content = Q::json_encode($_SESSION, JSON_FORCE_OBJECT); if (Users::$loggedOut) { Q_Utils::sendToNode(array("Q/method" => "Users/session", "sessionId" => $id, "content" => null, "duration" => $duration)); } else { if (Q_Session::id() and !empty($_SERVER['HTTP_HOST'])) { try { Q_Utils::sendToNode(array("Q/method" => "Users/session", "sessionId" => $id, "content" => $content, "duration" => $duration)); } catch (Exception $e) { // don't throw here, it would only result in a mysterious fatal error } } } }
function Users_after_Q_session_destroy($params) { Q::$state['session'] = true; // Q::autoload('Db'); // Q::autoload('Db_Mysql'); // Q::autoload('Db_Result'); // Q::autoload('Db_Expression'); // Q::autoload('Db_Query'); // Q::autoload('Db_Query_Mysql'); // Q::autoload('Db_Row'); // Q::autoload('Base_Users_Session'); // Q::autoload('Base_Users'); // Q::autoload('Users'); Q::autoload('Q_Utils'); Q::autoload('Q_Config'); Q::autoload('Q_Session'); $id = Q_Session::id(); if (!$id) { return; } $content = Q::json_encode($_SESSION, JSON_FORCE_OBJECT); Q_Utils::sendToNode(array("Q/method" => "Users/session", "sessionId" => $id, "content" => null, "updatedTime" => null, "destroyed" => true)); }
/** * 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; }
/** * Logs a user out * @method logout * @static */ static function logout() { // Access the session, if we haven't already. $user = self::loggedInUser(); $sessionId = Q_Session::id(); // One last chance to do something. // Hooks shouldn't be able to cancel the logout. /** * @event Users/logout {before} * @param {Users_User} user */ Q::event('Users/logout', compact('user'), 'before'); $deviceId = isset($_SESSION['Users']['deviceId']) ? $_SESSION['Users']['deviceId'] : null; if ($user) { Q_Utils::sendToNode(array("Q/method" => "Users/logout", "sessionId" => Q_Session::id(), "userId" => $user->id, "deviceId" => $deviceId)); // forget the device for this user/session Users_Device::delete()->where(array('userId' => $user->id, 'sessionId' => $sessionId))->execute(); } // Destroy the current session, which clears the $_SESSION and all notices, etc. Q_Session::destroy(); }
/** * Unsubcsribe from all or specific stream's messages * @method unsubscribe * @param $options=array() {array} * "userId": The user who is unsubscribing from the stream. Defaults to the logged-in user. * "skipAccess": if true, skip access check for whether user can unsubscribe * @return {boolean} */ function unsubscribe($options = array()) { $stream = $this->fetchAsUser($options, $userId); if (empty($options['skipAccess']) and !$stream->testReadLevel('messages')) { if (!$stream->testReadLevel('see')) { throw new Streams_Exception_NoSuchStream(); } throw new Users_Exception_NotAuthorized(); } $participant = $stream->join(array("userId" => $userId, 'subscribed' => false, 'noVisit' => true, "skipAccess" => Q::ifset($options, 'skipAccess', false))); Q_Utils::sendToNode(array("Q/method" => "Streams/Stream/unsubscribe", "stream" => Q::json_encode($stream->toArray()), "participant" => Q::json_encode($participant), "success" => Q::json_encode(!!$participant))); // Post Streams/unsubscribe message to the stream $stream->post($userId, array('type' => 'Streams/unsubscribe'), true); // Now post Streams/unsubscribed message to Streams/participating Streams_Message::post($userId, $userId, 'Streams/participating', array('type' => 'Streams/unsubscribed', 'instructions' => Q::json_encode(array('publisherId' => $stream->publisherId, 'streamName' => $stream->name))), true); return !!$participant; }
/** * 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); }
/** * @method sendMessage * @param {string} $view * The name of a view for the message. Fields are passed to this array. * @param {array} $fields=array() * The fields referenced in the subject and/or view * @param {array} $options=array() * Array of options. Can include:<br/> * "delay" => A delay, in milliseconds, to wait until sending email. Only works if Node server is listening. * @return {boolean} * @throws {Q_Exception_WrongType} * If phone number is invalid */ function sendMessage($view, $fields = array(), $options = array()) { /** * @event Users/sms/sendMessage {before} * @param {string} view * @param {array} fields * @param {array} options * @return {boolean} */ $result = Q::event('Users/sms/sendMessage', compact('view', 'fields', 'options'), 'before'); if (isset($result)) { return $result; } if (!Q_Valid::phone($this->number, $number)) { throw new Q_Exception_WrongType(array('field' => '$this->number', 'type' => 'mobile number', 'mobileNumber' => $this->number)); } $app = Q_Config::expect('Q', 'app'); $body = Q::view($view, $fields); $overrideLog = Q::event('Users/mobile/log', compact('mobileNumber', 'body'), 'before'); if (is_null($overrideLog) and $key = Q_Config::get('Users', 'mobile', 'log', 'key', null)) { Q::log("\nSent mobile message to {$this->number}:\n{$body}", $key); } $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'], "mobileNumber" => $number, "body" => $body, "options" => $options)); } if (!$sent) { $from = Q::ifset($options, 'from', Q_Config::get('Users', 'mobile', 'from', null)); if (!isset($from)) { // deduce from base url $url_parts = parse_url(Q_Request::baseUrl()); $domain = $url_parts['host']; $from = array("notifications@{$domain}", $domain); } $sid = Q_Config::get('Users', 'mobile', 'twilio', 'sid', null); $token = Q_Config::get('Users', 'mobile', 'twilio', 'token', null); if ($sid and $token) { $client = new Services_Twilio($sid, $token); $message = $client->account->sms_messages->create($from, $number, Q::view($view, $fields)); } else { if (!Q_Config::get('Users', 'email', 'smtp', null)) { Q_Response::setNotice("Q/mobile", "Please set up transport in Users/mobile/twilio as in docs", false); return true; } if (!is_array($from)) { $from = array($from, "{$app} activation"); } // Set up the default mail transport $host = Q_Config::get('Users', 'email', 'smtp', 'host', 'sendmail'); if ($host === 'sendmail') { $transport = new Zend_Mail_Transport_Sendmail('-f' . reset($from)); } else { if (is_array($host)) { $smtp = $host; $host = $smtp['host']; unset($smtp['host']); } else { $smtp = null; } $transport = new Zend_Mail_Transport_Smtp($host, $smtp); } $mail = new Zend_Mail(); $from_name = reset($from); $mail->setFrom(next($from), $from_name); $gateways = Q_Config::get('Users', 'mobile', 'gateways', array('at&t' => 'txt.att.net', 'sprint' => 'messaging.sprintpcs.com', 'verizon' => 'vtext.com', 't-mobile' => 'tmomail.net')); $number2 = substr($this->number, 2); foreach ($gateways as $k => $v) { $mail->addTo($number2 . '@' . $v); } $mail->setBodyText($body); try { $mail->send($transport); } catch (Exception $e) { throw new Users_Exception_MobileMessage(array('error' => $e->getMessage())); } } } /** * @event Users/sms/sendMessage {after} * @param {string} view * @param {array} fields * @param {array} options * @param {string} mail */ Q::event('Users/email/sendMessage', compact('view', 'fields', 'options', 'mail', 'app'), 'after'); return true; }
/** * Executes a query against the database and returns the result set. * @method excecute * @param {boolean} [$prepareStatement=false] If true, a PDO statement will be prepared * from the query before it is executed. It is also saved for future invocations to use. * Do this only if the statement will be executed many times with * different parameters. Basically you would use ->bind(...) between * invocations of ->execute(). * @return {Db_Result} The Db_Result object containing the PDO statement that resulted from the query. */ function execute($prepareStatement = false) { if (class_exists('Q')) { /** * @event Db/query/execute {before} * @param {Db_Query_Mysql} query * @return {Db_Result} */ $result = Q::event('Db/query/execute', array('query' => $this), 'before'); } if (isset($result)) { return $result; } $stmts = array(); // make sure SQL template will be ready for sharding. reallyConnect will add new values unset($this->replacements['{$dbname}']); unset($this->replacements['{$prefix}']); $this->startedTime = Q::milliseconds(true); if ($prepareStatement) { // Prepare the query into a SQL statement // this takes two round-trips to the database // Preparing the statement if it wasn't yet set if (!isset($this->statement)) { if ($q = $this->build()) { $pdo = $this->reallyConnect(); $this->statement = $pdo->prepare($q); if ($this->statement === false) { if (!isset($sql)) { $sql = $this->getSQL(); } if (class_exists('Q_Exception_DbQuery')) { throw new Exception("query could not be prepared"); } throw new Exception("query could not be prepared [query was: {$sql} ]", -1); } } } // Bind the parameters foreach ($this->parameters as $key => $value) { $this->statement->bindValue($key, $value); } } $sql_template = $this->getSQL(null, true); $queries = $this->shard(); $connection = $this->db->connectionName(); if (!empty($queries["*"])) { $shard_names = Q_Config::get('Db', 'connections', $connection, 'shards', array('' => '')); $q = $queries["*"]; foreach ($shard_names as $k => $v) { $queries[$k] = $q; } unset($queries['*']); } foreach ($queries as $shard_name => $query) { $upcoming = Q_Config::get('Db', 'upcoming', $connection, false); if ($query->type !== Db_Query::TYPE_SELECT && $query->type !== Db_Query::TYPE_RAW) { if (!empty($upcoming['block']) && $shard_name === $upcoming['shard']) { throw new Db_Exception_Blocked(compact('shard_name', 'connection')); } } $query->startedTime = Q::milliseconds(true); $pdo = $query->reallyConnect($shard_name); $connInfo = Db::getConnection($connection); $dsn = $connInfo['dsn']; $nt =& self::$nestedTransactions[$dsn]; if (!isset($nt)) { self::$nestedTransactions[$dsn] = 0; $nt =& self::$nestedTransactions[$dsn]; } $sql = $query->getSQL(); try { if (!empty($query->clauses["BEGIN"])) { if (++$nt == 1) { $pdo->beginTransaction(); } } else { if (!empty($query->clauses["ROLLBACK"])) { $pdo->rollBack(); $nt = 0; } } if ($query->type !== Db_Query::TYPE_ROLLBACK) { if ($prepareStatement) { // Execute the statement try { $query->statement->execute(); $stmt = $query->statement; } catch (Exception $e) { if (class_exists('Q_Exception_DbQuery')) { throw $e; } throw new Exception($e->getMessage() . "\n... Query was: {$sql}", -1); } } else { // Obtain the full SQL code ourselves // and send to the database, without preparing it there. if ($sql) { $stmt = $pdo->query($sql); } else { $stmt = true; } } $stmts[] = $stmt; if (!empty($query->clauses["COMMIT"]) && $nt) { // we commit only if no error occurred - warnings are permitted if (!$stmt or $stmt !== true and !in_array(substr($stmt->errorCode(), 0, 2), array('00', '01'))) { $err = $pdo->errorInfo(); throw new Exception($err[0], $err[1]); } if (--$nt == 0) { $pdo->commit(); } } } } catch (Exception $exception) { if ($nt) { $pdo->rollBack(); $nt = 0; } break; } $this->nestedTransactionCount = $nt; if (class_exists('Q') && isset($sql)) { // log query if shard split process is active // all activities will be done by node.js switch ($this->type) { case Db_Query::TYPE_SELECT: // SELECT queries don't need to be logged // SELECT queries don't need to be logged case Db_Query::TYPE_RAW: // Raw queries are run on shard '' - i.e. main db only // actually, raw query may get here only on initial sharding // when sharding has started raw queries are never run on shard break; default: if (!$upcoming or $shard_name !== $upcoming['shard']) { break; } $table = $this->table; foreach ($this->replacements as $k => $v) { $table = str_replace($k, $v, $table); } if ($table !== $upcoming['dbTable']) { break; } // node will determine new shard(s) names using // new sharding config which is available within split process $timestamp = $pdo->query("SELECT CURRENT_TIMESTAMP")->fetchAll(PDO::FETCH_COLUMN, 0); if ($timestamp === false || !isset($timestamp[0])) { $timestamp = date("Y-m-d H:i:s"); // backup solution } else { $timestamp = $timestamp[0]; } $sql_template = str_replace('CURRENT_TIMESTAMP', "'{$timestamp}'", $sql_template); $transaction = !empty($this->clauses['COMMIT']) ? 'COMMIT' : (!empty($this->clauses['BEGIN']) ? 'START TRANSACTION' : (!empty($this->clauses['ROLLBACK']) ? 'ROLLBACK' : '')); $upcoming_shards = array_keys($query->shard($upcoming['indexes'][$upcoming['table']])); $logServer = Q_Config::get('Db', 'internal', 'sharding', 'logServer', null); if (!empty($transaction) && $transaction !== 'COMMIT') { Q_Utils::sendToNode(array('Q/method' => 'Db/Shards/log', 'shards' => $upcoming_shards, 'sql' => "{$transaction};"), Q_Config::get('Db', 'internal', 'sharding', 'logServer', null)); } Q_Utils::sendToNode(array('Q/method' => 'Db/Shards/log', 'shards' => $upcoming_shards, 'sql' => trim(str_replace("\n", ' ', $sql_template))), Q_Config::get('Db', 'internal', 'sharding', 'logServer', null)); if (!empty($transaction) && $transaction === 'COMMIT') { Q_Utils::sendToNode(array('Q/method' => 'Db/Shards/log', 'shards' => $upcoming_shards, 'sql' => "{$transaction};"), $logServer, true); } } $query->endedTime = Q::milliseconds(true); } } $this->endedTime = Q::milliseconds(true); if (!empty($exception)) { /** * @event Db/query/exception {after} * @param {Db_Query_Mysql} query * @param {array} queries * @param {string} sql * @param {Exception} exception */ Q::event('Db/query/exception', compact('query', 'queries', 'sql', 'exception'), 'after'); if (!class_exists('Q_Exception_DbQuery')) { throw $exception; } throw new Q_Exception_DbQuery(array('sql' => $sql, 'message' => $exception->getMessage() . "[query was: {$sql}]")); } /** * @event Db/query/execute {after} * @param {Db_Query_Mysql} query * @param {array} queries * @param {string} sql */ Q::event('Db/query/execute', compact('query', 'queries', 'sql'), 'after'); return new Db_Result($stmts, $this); }
/** * Adds a device to the system, after sending a test notification to it * @param {array} $device * @param {string} $device.userId * @param {string} $device.deviceId * @param {string} [$device.formFactor] * @param {string} [$device.platform] * @param {string} [$device.version] * @param {string} [$device.sessionId] * @param {boolean} [$device.sandbox] * @param {string} [$device.passphrase] * @param {boolean} [$skipNotification=false] if true, skips sending notification * @return {Users_Device} */ static function add($device, $skipNotification = false) { Q_Valid::requireFields(array('userId', 'deviceId'), $device, true); $userId = $device['userId']; $deviceId = $device['deviceId']; if (!$skipNotification) { $app = Q::app(); $sandbox = Q::ifset($device, 'sandbox', null); if (!isset($sandbox)) { $sandbox = Q_Config::get($app, "cordova", "ios", "sandbox", false); } $env = $sandbox ? ApnsPHP_Abstract::ENVIRONMENT_SANDBOX : ApnsPHP_Abstract::ENVIRONMENT_PRODUCTION; $s = $sandbox ? 'sandbox' : 'production'; $cert = APP_LOCAL_DIR . DS . 'Users' . DS . 'certs' . DS . $app . DS . $s . DS . 'bundle.pem'; $authority = USERS_PLUGIN_FILES_DIR . DS . 'Users' . DS . 'certs' . DS . 'EntrustRootCA.pem'; $logger = new Users_ApnsPHP_Logger(); $push = new ApnsPHP_Push($env, $cert); $push->setLogger($logger); $push->setRootCertificationAuthority($authority); if (isset($device['passphrase'])) { $push->setProviderCertificatePassphrase($device['passphrase']); } $push->connect(); $message = new ApnsPHP_Message($deviceId); $message->setCustomIdentifier('Users_Device-adding'); $message->setBadge(0); $message->setText(Q_Config::get($app, "cordova", "ios", "device", "text", "Notifications have been enabled")); $message->setCustomProperty('userId', $userId); $message->setExpiry(5); $push->add($message); $push->send(); $push->disconnect(); $errors = $push->getErrors(); if (!empty($errors)) { $result = reset($errors); throw new Users_Exception_DeviceNotification($result['ERRORS'][0]); } } $sessionId = Q_Session::id(); $user = Users::loggedInUser(); $info = array_merge(Q_Request::userAgentInfo(), array('sessionId' => $sessionId, 'userId' => $user ? $user->id : null, 'deviceId' => null)); $device2 = Q::take($device, $info); $d = new Users_Device($device2); $d->save(true); if ($sessionId) { $s = new Users_Session(); $s->id = $sessionId; if (!$s->retrieve()) { $s->deviceId = $deviceId; } } $_SESSION['Users']['deviceId'] = $deviceId; $device2['Q/method'] = 'Users/device'; Q_Utils::sendToNode($device2); return $d; }
/** * 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; }
/** * @method afterRemoveExcecute * @param {Db_Result} $result * @param {Db_Query} $query * @return {Db_Result} */ function afterRemoveExecute($result, $query) { $stream = $this; // if the above call threw an exception, then we will not be doing the following. Q_Utils::sendToNode(array("Q/method" => "Streams/Stream/remove", "stream" => Q::json_encode($stream->toArray()))); /** * @event Streams/remove/$streamType {after} * @param {Streams_Stream} stream * @param {string} asUserId */ Q::event("Streams/remove/{$stream->type}", compact('stream', 'result'), 'after'); if ($this->name !== 'Streams/user/firstName' and $this->name !== 'Streams/user/lastName') { return $result; } // Update all avatars corresponding to access rows for this stream $taintedAccess = Streams_Access::select('*')->where(array('publisherId' => $this->publisherId, 'streamName' => $this->name))->fetchDbRows(); Streams::updateAvatars($this->publisherId, $taintedAccess, $this, true); return $result; }