function Q_errors_native($params) { echo Q::view('Q/errors.php', $params); $app = Q_Config::expect('Q', 'app'); Q::log("{$app}: Errors in " . ceil(Q::milliseconds()) . "ms\n"); Q::log($params); }
function log_shard_query($params) { foreach ($params['queries'] as $shard => $query) { if ($query->className === 'Users_Session') { continue; } $connection = $query->db->connectionName(); if ($begin = $query->getClause('BEGIN') and $query->nestedTransactionCount == 1) { Q::log($begin); } $duration = ceil($query->endedTime - $query->startedTime); Q::log("Query {$connection} on shard \"{$shard}\":\n{$params['sql']}\n(duration: {$duration} ms)\n\n"); if ($commit = $query->getClause('COMMIT') and $query->nestedTransactionCount == 0) { Q::log($commit); } if (!empty($params['exception'])) { Q::log("ROLLBACK (due to exception)"); Q::log("query was: " . $params['sql']); Q::log($params['exception']); } else { if ($rollback = $query->getClause('ROLLBACK')) { Q::log($rollback); } } } }
/** * Excecute web request * @method execute * @static */ static function execute() { // Fixes for different platforms: if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // ISAPI 3.0 $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL']; } // Get the base URL $base_url = Q_Request::baseUrl(); if (Q::$controller === 'Q_ActionController') { // we detected action.php in the URL, but // a misconfigured web server executed index.php instead return Q_ActionController::execute(); } // Set the controller that is being used if (!isset(Q::$controller)) { Q::$controller = 'Q_WebController'; } try { $slots = Q_Request::slotNames(false); $slots = $slots ? ' slots: (' . implode(',', $slots) . ') from' : ''; $method = Q_Request::method(); Q::log("{$method}{$slots} url: " . Q_Request::url(true), null, null, array('maxLength' => 10000)); Q_Dispatcher::dispatch(); $dispatchResult = Q_Dispatcher::result(); if (!isset($dispatchResult)) { $dispatchResult = 'Ran dispatcher'; } $uri = Q_Request::uri(); $module = $uri->module; $action = $uri->action; if ($module and $action) { $slotNames = Q_Request::slotNames(); $returned_slots = empty($slotNames) ? '' : implode(',', $slotNames); Q::log("~" . ceil(Q::milliseconds()) . 'ms+' . ceil(memory_get_peak_usage() / 1000) . 'kb.' . " {$dispatchResult} for {$module}/{$action}" . " ({$returned_slots})", null, null, array('maxLength' => 10000)); } else { Q::log("~" . ceil(Q::milliseconds()) . 'ms+' . ceil(memory_get_peak_usage() / 1000) . 'kb.' . " {$dispatchResult} No route for " . $_SERVER['REQUEST_URI'], null, null, array('maxLength' => 10000)); } } catch (Exception $exception) { /** * @event Q/exception * @param {Exception} exception */ Q::event('Q/exception', compact('exception')); } }
/** * Logs a message. * * @param $sMessage @type string The message. */ public function log($sMessage) { Q::log(trim($sMessage), 'apns'); }
function Q_exception_native($params) { extract($params); /** * @var Exception $exception */ if ($is_ajax = Q_Request::isAjax()) { $json = @Q::json_encode(array('errors' => Q_Exception::toArray(array($exception)))); $callback = Q_Request::callback(); switch (strtolower($is_ajax)) { case 'iframe': // Render an HTML layout for ajax if (!Q_Response::$batch) { header("Content-type: text/html"); } echo <<<EOT <!doctype html><html lang=en> <head><meta charset=utf-8><title>Q Result</title></head> <body> <script type="text/javascript"> window.result = function () { return {$json} }; </script> </body> </html> EOT; break; case 'json': // Render a JSON layout for ajax // Render a JSON layout for ajax default: header("Content-type: " . ($callback ? "application/javascript" : "application/json")); echo $callback ? "{$callback}({$json})" : $json; } } else { if (Q::textMode()) { echo Q_Exception::coloredString($exception); exit; } $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); if (is_callable(array($exception, 'getTraceAsStringEx'))) { $trace_string = $exception->getTraceAsStringEx(); } else { $trace_string = $exception->getTraceAsString(); } if ($exception instanceof Q_Exception_PhpError or !empty($exception->messageIsHtml)) { // do not sanitize $message } else { $message = Q_Html::text($message); } $content = "<h1 class='exception_message'>{$message}</h1>"; if (Q_Config::get('Q', 'exception', 'showFileAndLine', true)) { $content .= "<h3 class='exception_fileAndLine'>in {$file} ({$line})</h3>"; } if (Q_Config::get('Q', 'exception', 'showTrace', true)) { $content .= "<pre class='exception_trace'>{$trace_string}</pre>"; } $content .= str_repeat(' ', 512); // because of chrome $title = "Exception occurred"; $dashboard = ""; echo Q::view('Q/layout/html.php', compact('content', 'dashboard', 'title')); } $app = Q_Config::get('Q', 'app', null); $colored = Q_Exception::coloredString($exception); Q::log("{$app}: Exception in " . ceil(Q::milliseconds()) . "ms:\n\n{$colored}\n", null, true, array('maxLength' => 10000)); }
/** * Used to create a new stream * * @param {array} $_REQUEST * @param {String} [$_REQUEST.title] Required. The title of the interest. * @param {String} [$_REQUEST.publisherId] Optional. Defaults to the app name. * @param {String} [$_REQUEST.subscribe] Optional. Defauls to false. Whether to subscribe rather than just join the interest stream. * @return {void} */ function Streams_interest_post() { $user = Users::loggedInUser(true); $title = Q::ifset($_REQUEST, 'title', null); if (!isset($title)) { throw new Q_Exception_RequiredField(array('field' => 'title')); } $app = Q_Config::expect('Q', 'app'); $publisherId = Q::ifset($_REQUEST, 'publisherId', $app); $name = 'Streams/interest/' . Q_Utils::normalize($title); $stream = Streams::fetchOne(null, $publisherId, $name); if (!$stream) { $stream = Streams::create($publisherId, $publisherId, 'Streams/interest', array('name' => $name, 'title' => $title)); $parts = explode(': ', $title, 2); $keywords = implode(' ', $parts); try { $data = Q_Image::pixabay($keywords, array('orientation' => 'horizontal', 'min_width' => '500', 'safesearch' => 'true', 'image_type' => 'photo'), true); } catch (Exception $e) { Q::log("Exception during Streams/interest post: " . $e->getMessage()); $data = null; } if (!empty($data)) { $sizes = Q_Config::expect('Streams', 'icons', 'sizes'); ksort($sizes); $params = array('data' => $data, 'path' => "plugins/Streams/img/icons", 'subpath' => $name, 'save' => $sizes, 'skipAccess' => true); Q_Image::save($params); $stream->icon = $name; } $stream->save(); } $subscribe = !!Q::ifset($_REQUEST, 'subscribe', false); if ($subscribe) { if (!$stream->subscription($user->id)) { $stream->subscribe(); } } else { $stream->join(); } $myInterestsName = 'Streams/user/interests'; $myInterests = Streams::fetchOne($user->id, $user->id, $myInterestsName); if (!$myInterests) { $myInterests = new Streams_Stream(); $myInterests->publisherId = $user->id; $myInterests->name = $myInterestsName; $myInterests->type = 'Streams/category'; $myInterests->title = 'My Interests'; $myInterests->save(); } Streams::relate($user->id, $user->id, 'Streams/user/interests', 'Streams/interest', $publisherId, $name, array('weight' => '+1')); Q_Response::setSlot('publisherId', $publisherId); Q_Response::setSlot('streamName', $name); /** * Occurs when the logged-in user has successfully added an interest via HTTP * @event Streams/interest/post {after} * @param {string} publisherId The publisher of the interest stream * @param {string} title The title of the interest * @param {boolean} subscribe Whether the user subscribed to the interest stream * @param {Users_User} user The logged-in user * @param {Streams_Stream} stream The interest stream * @param {Streams_Stream} myInterests The user's "Streams/user/interests" stream */ Q::event("Streams/interest/add", compact('publisherId', 'title', 'subscribe', 'user', 'stream', 'myInterests'), 'after'); }
/** * @method shutdownFunction * @static */ static function shutdownFunction() { if ($error = error_get_last()) { Q::log($error, 'fatal'); header('PHP Fatal Error', true, 500); // do not expose the error contents } /** * @event Q/shutdown {before} */ Q::event('Q/shutdown', compact('error'), 'before'); Q_Cache::shutdownFunction(); if (Q_Session::id()) { session_write_close(); } }
/** * @method writeHandler * @static * @param {string} $id * @param {string} $sess_data * @return {boolean} */ static function writeHandler($id, $sess_data) { try { // if the request is AJAX request that came without session cookie, then do not write session, ignore it if (Q_Request::isAjax() && !isset($_COOKIE[self::name()])) { return false; } // don't save sessions when running from command-line (cli) if (php_sapi_name() == 'cli') { return false; } $our_SESSION = $_SESSION; $old_data = self::$sess_data; $changed = $sess_data !== $old_data; $result = false; /** * @event Q/session/write {before} * @param {string} id * @param {string} sess_data * @param {string} old_data * @param {boolean} changed * @return {boolean} */ if (false === Q::event('Q/session/write', compact('id', 'sess_data', 'old_data', 'changed'), 'before')) { return false; } if (empty(self::$session_save_path)) { self::$session_save_path = self::savePath(); } if (!empty(self::$session_db_connection)) { // Create a new row to be saved in the session table $db_row_class = self::$session_db_row_class; // Make sure it has a primary key! if (count(self::$session_db_row->getPrimaryKey()) != 1) { throw new Q_Exception("The primary key of " . self::$session_db_row_class . " has to consist of exactly 1 field!"); } $id_field = self::$session_db_id_field; $data_field = self::$session_db_data_field; $updated_field = self::$session_db_updated_field; $duration_field = self::$session_db_duration_field; $row = self::$session_db_row; $row->{$id_field} = $id; } else { $duration_name = self::durationName(); $id1 = substr($id, 0, 4); $id2 = substr($id, 4); $ssp = self::$session_save_path; $sess_file = $ssp . DS . "{$duration_name}/{$id1}/{$id2}"; $dir = $ssp . DS . "{$duration_name}/{$id1}/"; } if ($changed) { // Apparently, we want to save some changes. // The convention to avoid locking is that everything // stored in sessions must be mergeable using the // Q_Tree merge algorithm. // So we will retrieve the latest session data again, // merge our changes over it, and save. $params = array('id_field' => $id_field, 'data_field' => $data_field, 'updated_field' => $updated_field, 'duration_field' => $duration_field, 'changed' => $changed, 'sess_data' => $sess_data, 'old_data' => $old_data); if (!empty(self::$session_db_connection)) { $row->retrieve(); $existing_data = Q::ifset($row, $data_field, ""); $params['row'] = $row; } else { if (!is_dir($dir)) { mkdir($dir, fileperms($ssp), true); } if (!is_writable($dir)) { // alert the developer to this problem Q::log("{$sess_file} is not writable", 'fatal'); die("{$sess_file} is not writable"); } $file = fopen($sess_file, "w"); if (!$file) { return false; } $params['row'] = $row; $maxlength = Q_Config::get('Q', 'session', 'maxlength', 4095); $existing_data = fread($file, $maxlength); } $_SESSION = session_decode($existing_data); if (!$_SESSION) { $_SESSION = array(); } $t = new Q_Tree($_SESSION); $t->merge($our_SESSION); $_SESSION = $t->getAll(); $params['existing_data'] = $existing_data; $params['merged_data'] = $merged_data = session_encode(); /** * @event Q/session/save {before} * @param {string} sess_data * @param {string} old_data * @param {string} existing_data * @param {string} merged_data * @param {boolean} changed * @param {Db_Row} row * @return {boolean} */ if (false === Q::event('Q/session/save', $params, 'before')) { return false; } if (!empty(self::$session_db_connection)) { $row->{$data_field} = $merged_data; $row->{$duration_field} = Q_Config::get('Q', 'session', 'durations', Q_Request::formFactor(), Q_Config::expect('Q', 'session', 'durations', 'session')); $row->save(); $result = true; } else { $result = fwrite($file, $merged_data); fclose($file); } } /** * @event Q/session/write {after} * @param {string} id * @param {boolean} changed * @param {string} sess_data * @param {string} old_data * @param {string} existing_data * @param {string} merged_data * @param {string} data_field * @param {string} updated_field * @param {string} duration_field * @param {string} sess_file * @param {integer} row * @return {mixed} */ $result = Q::event('Q/session/write', compact('id', 'data_field', 'updated_field', 'duration_field', 'sess_file', 'row', 'changed', 'sess_data', 'old_data', 'existing_data', 'merged_data'), 'after'); return $result; } catch (Exception $e) { Q::log("Exception when writing session {$id}: " . $e->getMessage()); throw $e; } }
/** * @method setCookie * @static * @param {string} $name * @param {string} $value * @param {string} [$expires=0] * @param {string} [$path=false] * @return {string} */ static function setCookie($name, $value, $expires = 0, $path = false) { if (empty($_SERVER['HTTP_HOST'])) { Q::log('Warning: Ignoring call to Q_Response::setCookie() without $_SERVER["HTTP_HOST"]' . PHP_EOL); return false; } if (isset($_COOKIE[$name]) and $_COOKIE[$name] === $value) { return; } $parts = parse_url(Q_Request::baseUrl()); $path = $path ? $path : !empty($parts['path']) ? $parts['path'] : '/'; $domain = '.' . $parts['host']; setcookie($name, $value, $expires, $path, $domain); $_COOKIE[$name] = $value; $header = 'P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"'; $header = Q::event('Q/Response/setCookie', compact('name', 'value', 'expires', 'path', 'header'), 'before', false, $header); header($header); return $value; }
/** * @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; }
/** * Gets the key into the associative $pdo_array * corresponding to some database credentials. * @method pdo * @protected * @static * @param {string} $dsn The dsn to create PDO * @param {string} $username Username for connection * @param {string} $password Passwork for connection * @param {array} $driver_options Driver options * @return {PDO} */ protected static function pdo($dsn, $username, $password, $driver_options) { $key = $dsn . $username . $password . serialize($driver_options); if (isset(self::$pdo_array[$key])) { return self::$pdo_array[$key]; } // Make a new connection to a database! try { self::$pdo_array[$key] = @new PDO($dsn, $username, $password, $driver_options); } catch (Exception $e) { if (class_exists('Q_Config') and Q_Config::get('Db', 'exceptions', 'log', true)) { Q::log($e); } throw $e; } return self::$pdo_array[$key]; }
/** * Renders a different tag based on what you specified. * @method smartTag * @static * @param {string} $type The type of the tag. Could be one of * 'static', 'boolean', 'text', 'email', 'tel', * 'textarea', 'password', 'select', * 'radios', 'checkboxes', 'buttons', 'submit_buttons', * 'submit', 'hidden', 'image', or the name of a tag. * @param {array|string} [$attributes=array()] The attributes for the resulting element. Should at least include the name. You can also just pass the name as a string here. * @param {array} [$value=null] The value to start out with in the resulting element. If there are options present, this should be the value of one of the options. * @param {array} [$options=null] Associative array of options, used if the tag type is 'select', 'radios' or 'checkboxes'. * @param {array} [$params=array()] Additional parameters to pass to the corresponding function * @return {string} The generated markup */ static function smartTag($type, $attributes = array(), $value = null, $options = null, $params = array()) { if (!isset($type)) { throw new Q_Exception_RequiredField(array('field' => 'type')); } if (is_string($attributes)) { $attributes = array('name' => $attributes); } if (!is_array($attributes)) { $attributes = array(); } $id = isset($attributes['id']) ? $attributes['id'] : null; switch ($type) { case 'hidden': return self::hidden($value, isset($attributes['name']) ? $attributes['name'] : null, isset($params[0]) ? $params[0] : true); case 'static': unset($attributes['name']); if (empty($options['date'])) { $display = isset($options[$value]) ? $options[$value] : $value; } else { $v = is_numeric($value) ? $value : strtotime($value); $display = (!empty($v) and substr($v, 0, 4) !== '0000') ? date($options['date'], $v) : ''; Q::log("\n\n{$v}\n{$display}\n\n"); } return self::tag('span', $attributes, $display); case 'boolean': $attributes['type'] = 'checkbox'; if (!empty($value)) { $attributes['checked'] = 'checked'; } return self::tag('input', $attributes); case 'text': case 'submit': case 'email': case 'tel': $attributes['type'] = $type; $attributes['value'] = $value; return self::tag('input', $attributes); case 'textarea': if (!isset($attributes['rows'])) { $attributes['rows'] = 5; } if (!isset($attributes['cols'])) { $attributes['cols'] = 20; } return self::tag('textarea', $attributes, self::text($value)); case 'password': $attributes['type'] = 'password'; $attributes['maxlength'] = 64; $attributes['value'] = ''; // passwords should be cleared return self::tag('input', $attributes); case 'select': return self::tag('select', $attributes) . self::options($options, $id, $value, isset($params[0]) ? $params[0] : null, isset($params[1]) ? $params[1] : '', isset($params[2]) ? $params[2] : array()) . "</select>"; case 'radios': unset($attributes['value']); return "<div>" . self::radios($attributes['name'], $options, $id, $value, "</div><div>", $attributes, isset($params[0]) ? $params[0] : array()) . "</div>"; case 'checkboxes': unset($attributes['value']); return "<div>" . self::checkboxes($attributes['name'], $options, $id, $value, "</div><div>", $attributes) . "</div>"; case 'buttons': unset($attributes['value']); return "<div>" . self::buttons($attributes['name'], $options, $id, '', $attributes) . "</div>"; case 'submit_buttons': unset($attributes['value']); $attributes['type'] = 'submit'; return "<div>" . self::buttons($attributes['name'], $options, $id, '', $attributes) . "</div>"; case 'image': $attributes['src'] = $value; $attributes['alt'] = $type; return self::tag('img', $attributes); case 'date': return self::date($attributes['name'], $value, $options, $attributes); default: return self::tag($type, $attributes, $value); } }
/** * @method setCookie * @static * @param {string} $name The name of the cookie * @param {string} $value The value of the cookie * @param {string} [$expires=0] The number of seconds since the epoch, 0 means never expires * @param {string} [$path=false] You can specify a path on the server here for the cookie * @return {string} */ static function setCookie($name, $value, $expires = 0, $path = false) { if (empty($_SERVER['HTTP_HOST'])) { Q::log('Warning: Ignoring call to Q_Response::setCookie() without $_SERVER["HTTP_HOST"]' . PHP_EOL); return false; } if (isset($_COOKIE[$name]) and $_COOKIE[$name] === $value) { return; } if (Q_Dispatcher::$startedResponse) { throw new Q_Exception("Q_Response::setCookie must be called before Q/response event"); } // see https://bugs.php.net/bug.php?id=38104 self::$cookies[$name] = array($value, $expires, $path); $_COOKIE[$name] = $value; return $value; }
/** * Front controller for Q */ include dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Q.inc.php'; // // Handle batch request // $urls = Q::ifset($_REQUEST['urls'], array()); Q::log("Batch request for " . count($urls) . " urls"); header("Content-type: application/json"); Q_Response::$batch = true; echo "["; $original_request = $_REQUEST; foreach ($urls as $i => $url) { $request = parse_url($url); parse_str($request['query'], $_REQUEST); $request = explode('?', $url); echo "["; if (!empty($request[0])) { Q_ActionController::execute($request[0]); } echo "]"; if (isset($urls[$i + 1])) { echo ','; } } $_REQUEST = $original_request; echo "]"; Q::log("~" . ceil(Q::milliseconds()) . 'ms+' . ceil(memory_get_peak_usage() / 1000) . 'kb.' . " batch complete.");
function Streams_before_Q_objects() { $token = Q_Request::special('Streams.token', null); if ($token === null) { return; } $invite = Streams_Invite::fromToken($token); if (!$invite) { throw new Q_Exception_MissingRow(array('table' => 'invite', 'criteria' => "token = '{$token}"), 'token'); } // did invite expire? $ts = Streams_Invite::db()->select("CURRENT_TIMESTAMP")->fetchAll(PDO::FETCH_NUM); if (isset($invite->expireTime) and $invite->expireTime < $ts[0][0]) { $invite->state = 'expired'; $invite->save(); } // is invite still pending? if ($invite->state !== 'pending') { switch ($invite->state) { case 'expired': $exception = new Streams_Exception_AlreadyExpired(null, 'token'); break; case 'accepted': $exception = new Streams_Exception_AlreadyAccepted(null, 'token'); break; case 'declined': $exception = new Streams_Exception_AlreadyDeclined(null, 'token'); break; case 'forwarded': $exception = new Streams_Exception_AlreadyForwarded(null, 'token'); break; default: $exception = new Q_Exception("This invite has already been " . $invite->state, 'token'); break; } $shouldThrow = Q::event('Streams/objects/inviteException', compact('invite', 'exception'), 'before'); if ($shouldThrow === null) { Q_Response::setNotice('Streams/objects', $exception->getMessage(), true); } else { if ($shouldThrow === true) { throw $exception; } } } // now process the invite $invitedUser = Users_User::fetch($invite->userId, true); $stream = Streams::fetchOne($invitedUser->id, $invite->publisherId, $invite->streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId = '{$invite->publisherId}', name = '{$invite->streamName}'")); } $byUser = Users_User::fetch($invite->invitingUserId, true); $byStream = Streams::fetchOne($byUser->id, $invite->publisherId, $invite->streamName); if (!$byStream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId = '{$invite->publisherId}', name = '{$invite->streamName}'")); } $access = new Streams_Access(); $access->publisherId = $byStream->publisherId; $access->streamName = $byStream->name; $access->ofUserId = $invite->userId; $specified_access = false; foreach (array('readLevel', 'writeLevel', 'adminLevel') as $level_type) { $access->{$level_type} = -1; if (empty($invite->{$level_type})) { continue; } // Give access level from the invite. // However, if inviting user has a lower access level now, // then give that level instead, unless it is lower than // what the invited user would have had otherwise. $min = min($invite->{$level_type}, $byStream->get($level_type, 0)); if ($min > $stream->get($level_type, 0)) { $access->{$level_type} = $min; $specified_access = true; } } if ($specified_access) { $access->save(true); } // now log invited user in $user = Users::loggedInUser(); if (empty($user) or $user->id !== $invite->userId) { $user = new Users_User(); $user->id = $invite->userId; if (!$user->retrieve()) { // The user who was invited doesn't exist // This shouldn't happen. We just silently log it and return. Q::log("Sanity check failed: invite with {$invite->token} pointed to nonexistent user"); return; } Users::setLoggedInUser($user); } // accept invite and autosubscribe if first time if ($invite->accept() and !$stream->subscription($user->id)) { $stream->subscribe(); } // retain the invite object for further processing Streams::$followedInvite = $invite; }
/** * 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; }
/** * Instantiates a particular tool. * Also generates javascript around it. * @method tool * @static * @param {string} $name * The name of the tool, of the form "$moduleName/$toolName" * The handler is found in handlers/$moduleName/tool/$toolName * Also can be an array of $toolName => $toolOptions, in which case the * following parameter, $options, is skipped. * @param {array} $options=array() * The options passed to the tool (or array of options arrays passed to the tools). * @param {array} [$extra=array()] Options used by Qbix when rendering the tool. * @param {string} [$extra.id] * An additional ID to distinguish tools instantiated * side-by-side from each other, within the same parent HTMLElement. * @param {boolean} [$extra.cache=false] * If true, then the Qbix front end will not replace existing tools with same id * during Q.loadUrl when this tool appears in the rendered HTML * @param {boolean} [$extra.merge=false] * If true, the element for this tool is merged with the element of the tool * already being rendered (if any), producing one element with markup * for both tools and their options. This can be used more than once, merging * multiple tools in one element. * As part of the mege, the content this tool (if any) is prepended * to the content of the tool which is already being rendered. * @throws {Q_Exception_WrongType} * @throws {Q_Exception_MissingFile} */ static function tool($name, $options = array(), $extra = array()) { if (is_string($name)) { $info = array($name => $options); } else { $info = $name; $extra = $options; } $oldToolName = self::$toolName; /** * @event Q/tool/render {before} * @param {string} info * An array of $toolName => $options pairs * @param {array} extra * Options used by Qbix when rendering the tool. Can include:<br/> * "id" => * an additional ID to distinguish tools instantiated * side-by-side from each other. Usually numeric.<br/> * "cache" => * if true, then the Qbix front end will not replace existing tools with same id * during Q.loadUrl when this tool appears in the rendered HTML * @return {string|null} * If set, override the method return */ $returned = Q::event('Q/tool/render', array('info' => $info, 'extra' => &$extra), 'before'); $result = ''; $exception = null; foreach ($info as $name => $options) { Q::$toolName = $name; $toolHandler = "{$name}/tool"; $options = is_array($options) ? $options : array(); if (is_array($returned)) { $options = array_merge($returned, $options); } try { /** * Renders the tool * @event $toolHandler * @param {array} $options * The options passed to the tool * @return {string} * The rendered tool content */ $result .= Q::event($toolHandler, $options); // render the tool } catch (Q_Exception_MissingFile $e) { /** * Renders the 'Missing Tool' content * @event Q/missingTool * @param {array} name * The name of the tool * @return {string} * The rendered content */ $params = $e->params(); if ($params['filename'] === str_replace('/', DS, "handlers/{$toolHandler}.php")) { $result .= self::event('Q/missingTool', compact('name', 'options')); } else { $exception = $e; } } catch (Exception $e) { $exception = $e; } if ($exception) { Q::log($exception); $result .= $exception->getMessage(); } Q::$toolName = $name; // restore the current tool name } // Even if the tool rendering throws an exception, // it is important to run the "after" handlers /** * @event Q/tool/render {after} * @param {string} info * An array of $toolName => $options pairs * @param {array} 'extra' * Options used by Qbix when rendering the tool. Can include:<br/> * "id" => * an additional ID to distinguish tools instantiated * side-by-side from each other. Usually numeric.<br/> * "cache" => * if true, then the Qbix front end will not replace existing tools with same id * during Q.loadUrl when this tool appears in the rendered HTML */ Q::event('Q/tool/render', compact('info', 'extra'), 'after', false, $result); Q::$toolName = $oldToolName; return $result; }
/** * The standard action front controller * @method execute * @static * @throws {Q_Exception_BadUrl} * @throws {Q_Exception} * @throws {Q_Exception_MissingConfig} */ static function execute($url = null) { // Fixes for different platforms: if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // ISAPI 3.0 $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL']; } // Set the controller that is being used if (!isset(Q::$controller)) { Q::$controller = 'Q_ActionController'; } try { $slots = Q_Request::slotNames(false); $slots = $slots ? ' slots: (' . implode(',', $slots) . ') from' : ''; $method = Q_Request::method(); Q::log("{$method}{$slots} url: " . Q_Request::url(true)); $tail = Q_Request::tail($url); if (!isset($tail)) { // Bad url was requested somehow $url = Q_Request::url(true); $base_url = Q_Request::baseUrl(true); throw new Q_Exception_BadUrl(compact('base_url', 'url')); } $parts = explode('/', $tail); $parts_len = count($parts); if ($parts_len >= 1) { $module = $parts[0]; } if ($parts_len >= 2) { $action = $parts[1]; } if (empty($module) or empty($action)) { throw new Q_Exception("Not implemented"); } // Make sure the 'Q'/'web' config fields are set, // otherwise URLs will be formed pointing to the wrong // controller script. $ar = Q_Config::get('Q', 'web', 'appRootUrl', null); if (!isset($ar)) { throw new Q_Exception_MissingConfig(array('fieldpath' => 'Q/web/appRootUrl')); } // Dispatch the request $uri = Q_Uri::from(compact('module', 'action')); Q_Dispatcher::dispatch($uri); $dispatchResult = Q_Dispatcher::result(); if (!isset($dispatchResult)) { $dispatchResult = 'Ran dispatcher'; } if ($module and $action) { $slotNames = Q_Request::slotNames(); $requestedSlots = empty($slotNames) ? '' : implode(',', $slotNames); Q::log("~" . ceil(Q::milliseconds()) . 'ms+' . ceil(memory_get_peak_usage() / 1000) . 'kb.' . " {$dispatchResult} for {$module}/{$action}" . " ({$requestedSlots})"); } else { Q::log("~" . ceil(Q::milliseconds()) . 'ms+' . ceil(memory_get_peak_usage() / 1000) . 'kb.' . " No route for " . $_SERVER['REQUEST_URI']); } } catch (Exception $exception) { /** * @event Q/exception * @param {Exception} exception */ Q::event('Q/exception', compact('exception')); } }
/** * @method writeHandler * @static * @param {string} $id * @param {string} $sess_data * @return {boolean} */ static function writeHandler($id, $sess_data) { try { // if the request is AJAX request that came without session cookie, then do not write session, ignore it if (Q_Request::isAjax() && !isset($_COOKIE[self::name()])) { return false; } // don't save sessions when running from command-line (cli) if (php_sapi_name() == 'cli') { return false; } /** * @event Q/session/write {before} * @param {string} id * @param {string} sess_data * @return {boolean} */ if (false === Q::event('Q/session/write', compact('id', 'sess_data'), 'before')) { return false; } if (empty(self::$session_save_path)) { self::$session_save_path = self::savePath(); } if (!empty(self::$session_db_connection)) { // Create a new row to be saved in the session table $db_row_class = self::$session_db_row_class; // Make sure it has a primary key! if (count(self::$session_db_row->getPrimaryKey()) != 1) { throw new Q_Exception("The primary key of " . self::$session_db_row_class . " has to consist of exactly 1 field!"); } $id_field = self::$session_db_id_field; $data_field = self::$session_db_data_field; $updated_field = self::$session_db_updated_field; $duration_field = self::$session_db_duration_field; $row = self::$session_db_row; if (!$row->wasRetrieved()) { $row->{$id_field} = $id; } $row->{$data_field} = $sess_data; $row->{$duration_field} = Q_Config::get('Q', 'session', 'durations', Q_Request::formFactor(), Q_Config::expect('Q', 'session', 'durations', 'session')); /** * @event Q/session/save {before} * @param {string} id * @param {string} sess_data * @return {boolean} */ if (false === Q::event('Q/session/save', array('row' => $row, 'id_field' => $id_field, 'data_field' => $data_field, 'updated_field' => $updated_field, 'duration_field' => $duration_field), 'before')) { return false; } $row->save(false, true); $result = true; } else { $duration_name = self::durationName(); $id1 = substr($id, 0, 4); $id2 = substr($id, 4); $ssp = self::$session_save_path; $sess_file = $ssp . DS . "{$duration_name}/{$id1}/{$id2}"; $dir = $ssp . DS . "{$duration_name}/{$id1}/"; if (!is_dir($dir)) { mkdir($dir, fileperms($ssp), true); } if (!is_writable($dir)) { // alert the developer to this problem Q::log("{$sess_file} is not writable", 'fatal'); die("{$sess_file} is not writable"); } $fp = fopen($sess_file, "w"); if (!$fp) { return false; } $result = fwrite($fp, $sess_data); fclose($fp); } $changed = $sess_data !== self::$sess_data; $old_data = self::$sess_data; /** * @event Q/session/write {after} * @param {string} id * @param {string} sess_data * @param {string} data_field * @param {string} updated_field * @param {string} duration_field * @param {string} sess_file * @param {integer} row * @param {boolean} changed * @param {string} old_data * @return {mixed} */ $result = Q::event('Q/session/write', compact('id', 'sess_data', 'data_field', 'updated_field', 'duration_field', 'sess_file', 'row', 'changed', 'old_data'), 'after'); return $result; } catch (Exception $e) { Q::log("Exception when writing session {$id}: " . $e->getMessage()); throw $e; } }