function Q_before_Q_autoload($params, &$filename) { // TODO: Integrate with Composer // require "vendor/autoload.php", implementing PSR-4 // look at optimizations: // Workaround for Zend Framework, because it has require_once // in various places, instead of just relying on autoloading. // As a result, we need to add some more directories to the path. // The trigger is that we will be loading a file beginning with "classes/Zend". // This is handled natively inside this method for the purpose of speed. $paths = array('classes/Zend/' => 'classes'); static $added_paths = array(); foreach ($paths as $prefix => $new_path) { if (substr($filename, 0, strlen($prefix)) != $prefix) { continue; } if (isset($added_paths[$new_path])) { break; } $abs_filename = Q::realPath($filename); $new_path_parts = array(); $prev_part = null; foreach (explode(DS, $abs_filename) as $part) { if ($prev_part == 'classes' and $part == 'Zend') { break; } $prev_part = $part; $new_path_parts[] = $part; } $new_path = implode(DS, $new_path_parts); $paths = array($new_path, get_include_path()); set_include_path(implode(PS, $paths)); $added_paths[$new_path] = true; } }
/** * Saves a file, usually sent by the client * @method save * @static * @param {array} $params * @param {string} [$params.data] the file data * @param {string} [$params.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.name] override the name of the file, after the subpath * @param {string} [$params.skipAccess=false] if true, skips the check for authorization to write files there * @param {boolean} [$params.audio] set this to true if the file is an audio file * @return {array} Returns array containing ($name => $tailUrl) pair */ static function save($params) { if (empty($params['data'])) { throw new Q_Exception(array('field' => 'file'), 'data'); } // check whether we can write to this path, and create dirs if needed $data = $params['data']; $audio = $params['audio']; $path = isset($params['path']) ? $params['path'] : 'uploads'; $subpath = isset($params['subpath']) ? $params['subpath'] : ''; $realPath = Q::realPath(APP_WEB_DIR . DS . $path); if ($realPath === false) { throw new Q_Exception_MissingFile(array('filename' => APP_WEB_DIR . DS . $path)); } $name = isset($params['name']) ? $params['name'] : 'file'; if (!preg_match('/^[\\w.-]+$/', $name)) { $info = pathinfo($name); $name = Q_Utils::normalize($info['filename']) . '.' . $info['extension']; } // TODO: recognize some extensions maybe $writePath = $realPath . ($subpath ? DS . $subpath : ''); $lastChar = substr($writePath, -1); if ($lastChar !== DS and $lastChar !== '/') { $writePath .= DS; } $skipAccess = !empty($params['skipAccess']); Q_Utils::canWriteToPath($writePath, $skipAccess ? null : true, true); file_put_contents($writePath . $name, $data); $size = filesize($writePath . $name); $tailUrl = $subpath ? "{$path}/{$subpath}/{$name}" : "{$path}/{$name}"; /** * @event Q/file/save {after} * @param {string} user the user * @param {string} path the path in the url * @param {string} subpath the subpath in the url * @param {string} name the actual name of the file * @param {string} writePath the actual folder where the path is written * @param {string} data the data written to the file * @param {string} tailUrl consists of $path/[$subpath/]$name * @param {integer} size the size of the file that was written * @param {boolean} skipAccess whether we are skipping access checks * @param {boolean} audio whether the file is audio */ Q::event('Q/file/save', compact('path', 'subpath', 'name', 'writePath', 'data', 'tailUrl', 'size', 'skipAccess', 'audio'), 'after'); return array($name => $tailUrl); }
/** * Saves a file, usually sent by the client * @method save * @static * @param {array} $params * @param {string} [$params.data] the file data * @param {string} [$params.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.name] override the name of the file, after the subpath * @param {string} [$params.skipAccess=false] if true, skips the check for authorization to write files there * @return {array} Returns array containing ($name => $tailUrl) pair */ static function save($params) { if (empty($params['data'])) { throw new Q_Exception(array('field' => 'file'), 'data'); } // check whether we can write to this path, and create dirs if needed $data = $params['data']; $path = isset($params['path']) ? $params['path'] : 'uploads'; $subpath = isset($params['subpath']) ? $params['subpath'] : ''; $realPath = Q::realPath(APP_WEB_DIR . DS . $path); if ($realPath === false) { throw new Q_Exception_MissingFile(array('filename' => APP_WEB_DIR . DS . $path)); } $name = isset($params['name']) ? $params['name'] : 'file'; if (!preg_match('/^[\\w.-]+$/', $name)) { $info = pathinfo($name); $name = Q_Utils::normalize($info['filename']) . '.' . $info['extension']; } // TODO: recognize some extensions maybe $writePath = $realPath . ($subpath ? DS . $subpath : ''); $lastChar = substr($writePath, -1); if ($lastChar !== DS and $lastChar !== '/') { $writePath .= DS; } $throwIfNotWritable = empty($params['skipAccess']) ? true : null; Q_Utils::canWriteToPath($writePath, $throwIfNotWritable, true); file_put_contents($writePath . DS . $name, $data); $tailUrl = $subpath ? "{$path}/{$subpath}/{$name}" : "{$path}/{$name}"; /** * @event Q/file/save {after} * @param {string} user * @param {string} path * @param {string} subpath * @param {string} name * @param {string} writePath * @param {string} data */ Q::event('Q/file/save', compact('path', 'subpath', 'name', 'writePath', 'data', 'tailUrl'), 'after'); return array($name => $tailUrl); }
/** * Saves parameters to a file * @method save * @param {string} $filename Name of file to save to. If tree was loaded, you can leave this blank to update that file. * @param {array} [$array_path=array()] Array of keys identifying the path of the config subtree to save * @return {boolean} Returns true if saved, otherwise false; **/ function save($filename = null, $array_path = array(), $prefix_path = null) { if (empty($filename) and !empty($this->filename)) { $filename = $this->filename; } if (!($filename2 = Q::realPath($filename))) { $filename2 = $filename; } if (empty($array_path)) { $array_path = array(); $toSave = $this->parameters; } else { $array_path[] = null; $toSave = call_user_func_array(array($this, 'get'), $array_path); } if (is_null($prefix_path)) { $prefix_path = $array_path; } $prefix_path = array_reverse($prefix_path); foreach ($prefix_path as $ap) { if ($ap) { $toSave = array($ap => $toSave); } } $mask = umask(Q_Config::get('Q', 'internal', 'umask', 00)); $success = file_put_contents($filename2, !empty($toSave) ? Q::json_encode($toSave, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) : '{}', LOCK_EX); clearstatcache(true, $filename2); umask($mask); if ($success) { self::$cache[$filename] = $toSave; Q_Cache::set("Q_Tree\t{$filename}", $toSave); // no need to check result - on failure Q_Cache is disabled } return $success; }
/** * Dispatches a URI for internal processing. * Usually called by a front controller. * @method dispatch * @static * @param {mixed} [$uri=null] You can pass a custom URI to dispatch. Otherwise, Qbix will attempt * to route the requested URL, if any. * @param {array} [$check=array('accessible')] Pass array() to skip checking whether the URI can be obtained * as a result of routing some URL. * @return {boolean} * @throws {Q_Exception_MethodNotSupported} * @throws {Q_Exception_Recursion} * @throws {Q_Exception_DispatcherErrors} * @throws {Q_Exception_DispatcherForward} */ static function dispatch($uri = null, $check = array('accessible')) { if (!is_array($check)) { $check = array('accessible'); } if (isset($uri)) { if (in_array('accessible', $check)) { if (!Q_Uri::url($uri)) { // We shouldn't dispatch to this URI $uri = Q_Uri::from(array()); } } self::$uri = Q_Uri::from($uri); } else { $request_uri = Q_Request::uri(); self::$uri = clone $request_uri; } // if file or dir is requested, try to serve it $served = false; $skip = Q_Config::get('Q', 'dispatcherSkipFilename', false); $filename = $skip ? false : Q_Request::filename(); if ($filename) { if (is_dir($filename)) { /** * @event Q/dir * @param {string} filename * @param {string} routed_uri * @return {boolean} */ $served = Q::event("Q/dir", compact('filename', 'routed_uri')); $dir_was_served = true; } else { /** * @event Q/file * @param {string} filename * @param {string} routed_uri * @return {boolean} */ $served = Q::event("Q/file", compact('filename', 'routed_uri')); $dir_was_served = false; } } // if response was served, then return if ($served) { self::result($dir_was_served ? "Dir served" : "File served"); return true; } // This loop is for forwarding $max_forwards = Q_Config::get('Q', 'maxForwards', 10); for ($try = 0; $try < $max_forwards; ++$try) { // Make an array from the routed URI $routed_uri_array = array(); if (self::$uri instanceof Q_Uri) { $routed_uri_array = self::$uri->toArray(); } // If no module was found, then respond with noModule and return if (!isset(self::$uri->module)) { /** * @event Q/noModule * @param {array} $routed_uri_array */ Q::event("Q/noModule", $routed_uri_array); // should echo things self::result("No module"); return false; } $module = self::$uri->module; try { // Implement restricting of modules we are allowed to access $routed_modules = Q_Config::get('Q', 'routedModules', null); if (isset($routed_modules)) { if (!in_array($module, $routed_modules)) { /** * @event Q/notFound * @param {array} $routed_uri_array */ Q::event('Q/notFound', $routed_uri_array); // should echo things self::result("Unknown module"); return false; } } else { if (!Q::realPath("handlers/{$module}")) { Q::event('Q/notFound', $routed_uri_array); // should echo things self::result("Unknown module"); return false; } } // Implement notFound if action was not found if (empty(self::$uri->action)) { Q::event('Q/notFound', $routed_uri_array); // should echo things self::result("Unknown action"); return false; } // Fire a pure event, for aggregation etc if (!isset(self::$skip['Q/prepare'])) { /** * @event Q/prepare * @param {array} $routed_uri_array */ Q::event('Q/prepare', $routed_uri_array, true); } // Perform validation if (!isset(self::$skip['Q/validate'])) { /** * @event Q/validate * @param {array} $routed_uri_array */ Q::event('Q/validate', $routed_uri_array); if (!isset(self::$skip['Q/errors'])) { // Check if any errors accumulated if (Q_Response::getErrors()) { // There were validation errors -- render a response self::result('Validation errors'); self::errors(null, $module, null); return false; } } } // Time to instantiate some app objects from the request if (!isset(self::$skip['Q/objects'])) { /** * @event Q/objects * @param {array} $routed_uri_array */ Q::event('Q/objects', $routed_uri_array, true); } // We might want to reroute the request if (!isset(self::$skip['Q/reroute'])) { /** * @event Q/reroute * @param {array} $routed_uri_array * @return {boolean} whether to stop the dispatch */ $stop_dispatch = Q::event('Q/reroute', $routed_uri_array, true); if ($stop_dispatch) { self::result("Stopped dispatch"); return false; } } // Make some changes to server state, possibly $method = Q_Request::method(); if ($method != 'GET') { $methods = Q_Config::get('Q', 'methods', array('POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD')); if (!in_array($method, $methods)) { throw new Q_Exception_MethodNotSupported(compact('method')); } $method_event = 'Q/' . strtolower($method); if (!isset(self::$skip['Q/method']) and !isset(self::$skip[$method_event])) { if (!Q::canHandle($method_event)) { throw new Q_Exception_MethodNotSupported(compact('method')); } Q::event($method_event); } } // You can calculate some analytics here, and store them somewhere if (!isset(self::$skip['Q/analytics'])) { /** * @event Q/analytics * @param {array} $routed_uri_array */ Q::event('Q/analytics', $routed_uri_array, true); } if (!isset(self::$skip['Q/errors'])) { // Check if any errors accumulated if (Q_Response::getErrors()) { // There were processing errors -- render a response self::result('Processing errors'); self::errors(null, $module, null); return false; } } // When handling all further events, you should probably // refrain from changing server state, and only do reading. // That is because GET in HTTP is not supposed to have side effects // for which the client is responsible. // Start buffering the response, unless otherwise requested $handler = Q_Response::isBuffered(); if ($handler !== false) { $ob = new Q_OutputBuffer($handler); } // Generate and render a response /** * @event Q/response * @param {array} $routed_uri_array */ self::$response_started = true; Q::event("Q/response", $routed_uri_array); if (!empty($ob)) { $ob->endFlush(); } self::result("Served response"); return true; } catch (Q_Exception_DispatcherForward $e) { if (!empty($ob)) { $ob->getClean(); } self::handleForwardException($e); } catch (Q_Exception_DispatcherErrors $e) { if (!empty($ob)) { $partial_response = $ob->getClean(); } else { $partial_response = null; } self::errors(null, $module, $partial_response); self::result("Rendered errors"); return true; } catch (Exception $exception) { if (!empty($ob)) { $partial_response = $ob->getClean(); } else { $partial_response = null; } $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); if (is_callable(array($exception, 'getTraceAsStringEx'))) { $trace_string = $exception->getTraceAsStringEx(); } else { $trace_string = $exception->getTraceAsString(); } $colored = Q_Exception::coloredString($message, $file, $line, $trace_string); self::result("Exception occurred:\n\n{$colored}"); try { self::errors($exception, $module, $partial_response); } catch (Exception $e) { if (!empty($forwarding_to_error_action)) { // Looks like there were errors in the error action // So show the default one with the original exception throw $exception; } if (get_class($e) === 'Q_Exception_DispatcherForward') { $forwarding_to_error_action = true; self::handleForwardException($e); continue; } else { throw $e; } } return false; } } // If we are here, we have done forwarding too much throw new Q_Exception_Recursion(array('function_name' => 'Dispatcher::forward()')); }
/** * Loads the configuration and plugins in the right order * @method configure * @static * @param boolean [$force_reload=false] If true, forces the reload of the cache. * Otherwise it happens only if Q/configServer/interval seconds * have passed since the last time. * @throws {Q_Exception_MissingPlugin} */ static function configure($force_reload = false) { $app_tree = new Q_Tree(); // check if config need to be reloaded if (Q_Cache::connected()) { // we need to know reload interval $app_tree->load('config/Q.json'); $app_tree->load('config/app.json'); $app_tree->load('local/app.json'); $config_files = $app_tree->get('Q', 'configFiles', array()); foreach ($config_files as $cf) { $app_tree->merge(Q_Config::getFromServer($cf)); } // second round to catch configFiles inside configFiles $config_files = $app_tree->get('Q', 'configFiles', array()); foreach ($config_files as $cf) { $app_tree->merge(Q_Config::getFromServer($cf)); } $interval = $app_tree->get('Q', 'configServer', 'interval', 60); // reload each minute by default $app_tree->clear(null); $timestamp = Q_Cache::get("Q_Config\tupdate_time"); if (!isset($timestamp) || time() - $timestamp > $interval) { $force_reload = true; } } if ($force_reload) { $old_setting = Q_Cache::ignore(true); } Q_Config::clear(null); // clear the config Q_Config::load('config/Q.json'); // Get the app config, but don't load it yet $app_tree->load('config/app.json'); $app_tree->load('local/app.json'); // Load all the plugin config files first $paths = explode(PS, get_include_path()); $plugins = $app_tree->get('Q', 'plugins', array()); if (!in_array('Q', $plugins)) { array_unshift($plugins, 'Q'); } global $Q_Bootstrap_config_plugin_limit; $i = 0; foreach ($plugins as $k => $v) { ++$i; if (isset($Q_Bootstrap_config_plugin_limit) and $i > $Q_Bootstrap_config_plugin_limit) { continue; } $plugin = is_numeric($k) ? $v : $k; $plugin_path = Q::realPath('plugins' . DS . $v); if (!$plugin_path) { throw new Q_Exception_MissingPlugin(compact('plugin')); } Q_Config::load($plugin_path . DS . 'config' . DS . 'plugin.json'); array_splice($paths, 1, 0, array($plugin_path)); $PLUGIN = strtoupper($plugin); if (!defined($PLUGIN . '_PLUGIN_DIR')) { define($PLUGIN . '_PLUGIN_DIR', $plugin_path); } if (!defined($PLUGIN . '_PLUGIN_CONFIG_DIR')) { define($PLUGIN . '_PLUGIN_CONFIG_DIR', $plugin_path . DS . 'config'); } if (!defined($PLUGIN . '_PLUGIN_CLASSES_DIR')) { define($PLUGIN . '_PLUGIN_CLASSES_DIR', $plugin_path . DS . 'classes'); } if (!defined($PLUGIN . '_PLUGIN_FILES_DIR')) { define($PLUGIN . '_PLUGIN_FILES_DIR', $plugin_path . DS . 'files'); } if (!defined($PLUGIN . '_PLUGIN_HANDLERS_DIR')) { define($PLUGIN . '_PLUGIN_HANDLERS_DIR', $plugin_path . DS . 'handlers'); } if (!defined($PLUGIN . '_PLUGIN_PLUGINS_DIR')) { define($PLUGIN . '_PLUGIN_PLUGINS_DIR', $plugin_path . DS . 'plugins'); } if (!defined($PLUGIN . '_PLUGIN_SCRIPTS_DIR')) { define($PLUGIN . '_PLUGIN_SCRIPTS_DIR', $plugin_path . DS . 'scripts'); } if (!defined($PLUGIN . '_PLUGIN_VIEWS_DIR')) { define($PLUGIN . '_PLUGIN_VIEWS_DIR', $plugin_path . DS . 'views'); } if (!defined($PLUGIN . '_PLUGIN_TESTS_DIR')) { define($PLUGIN . '_PLUGIN_TESTS_DIR', $plugin_path . DS . 'tests'); } if (!defined($PLUGIN . '_PLUGIN_WEB_DIR')) { define($PLUGIN . '_PLUGIN_WEB_DIR', $plugin_path . DS . 'web'); } self::$plugins[$plugin] = $plugin_path; } $paths = array_unique($paths); set_include_path(implode(PS, $paths)); // Now, we can merge in our app's config Q_Config::merge($app_tree); // Now, load any other files we were supposed to load $config_files = Q_Config::get('Q', 'configFiles', array()); foreach ($config_files as $cf) { Q_Config::merge(Q_Config::getFromServer($cf)); } // second round to catch configFiles inside configFiles $config_files = Q_Config::get('Q', 'configFiles', array()); foreach ($config_files as $cf) { Q_Config::merge(Q_Config::getFromServer($cf)); } $script_files = Q_Config::get('Q', 'scriptFiles', array()); foreach ($script_files as $cf) { Q::includeFile($cf); } error_reporting(Q_Config::get('Q', 'errorReporting', E_ALL)); if (isset($old_setting)) { Q_Cache::ignore($old_setting); } set_time_limit(Q_Config::get('Q', 'internal', 'phpTimeout', 30)); self::setDefaultTimezone(); }
/** * Invites a user (or a future user) to a stream . * @method invite * @static * @param {string} $publisherId The id of the stream publisher * @param {string} $streamName The name of the stream the user will be invited to * @param {array} $who Array that can contain the following keys: * @param {string|array} [$who.userId] user id or an array of user ids * @param {string|array} [$who.fb_uid] fb user id or array of fb user ids * @param {string|array} [$who.label] label or an array of labels, or tab-delimited string * @param {string|array} [$who.identifier] identifier or an array of identifiers, or tab-delimited string * @param {integer} [$who.newFutureUsers] the number of new Users_User objects to create via Users::futureUser in order to invite them to this stream. This typically is used in conjunction with passing the "html" option to this function. * @param {array} [$options=array()] * @param {string|array} [$options.label] label or an array of labels for adding publisher's contacts * @param {string|array} [$options.myLabel] label or an array of labels for adding logged-in user's contacts * @param {integer} [$options.readLevel] => the read level to grant those who are invited * @param {integer} [$options.writeLevel] => the write level to grant those who are invited * @param {integer} [$options.adminLevel] => the admin level to grant those who are invited * @param {string} [$options.displayName] => the display name to use to represent the inviting user * @param {string} [$options.appUrl] => Can be used to override the URL to which the invited user will be redirected and receive "Q.Streams.token" in the querystring. * @param {array} [$options.html] => an array of ($template, $batchName) such as ("MyApp/foo.handlebars", "foo") for generating html snippets which can then be viewed from and printed via the action Streams/invitations?batchName=$batchName * @param {array} [$options.asUserId=null] Invite as this user id * @see Users::addLink() * @return {array} returns array with keys "success", "invited", "statuses", "identifierTypes", "alreadyParticipating" */ static function invite($publisherId, $streamName, $who, $options = array()) { if (isset($options['asUserId'])) { $asUserId = $options['asUserId']; $asUser = Users_User::fetch($asUserId); } else { $asUser = Users::loggedInUser(true); $asUserId = $asUser->id; } // Fetch the stream as the logged-in user $stream = Streams::fetch($asUserId, $publisherId, $streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name'), 'streamName'); } $stream = reset($stream); // Do we have enough admin rights to invite others to this stream? if (!$stream->testAdminLevel('invite') || !$stream->testWriteLevel('join')) { throw new Users_Exception_NotAuthorized(); } if (isset($options['html'])) { $html = $options['html']; if (!is_array($html) or count($html) < 2) { throw new Q_Exception_WrongType(array('field' => "options.html", 'type' => 'array of 2 strings')); } list($template, $batchName) = $html; // validate these paths $filename = APP_VIEWS_DIR . DS . $template; if (!Q::realPath($filename)) { throw new Q_Exception_MissingFile(compact('filename')); } $ext = $pathinfo = pathinfo($template, PATHINFO_EXTENSION); if ($ext !== 'handlebars') { throw new Q_Exception_WrongValue(array('field' => 'options.html[0]', 'range' => 'a filename with extension .handlebars')); } $path = Streams::invitationsPath($asUserId) . DS . $batchName; Q_Utils::canWriteToPath($path, true, true); } // get user ids if any to array, throw if user not found $raw_userIds = isset($who['userId']) ? Users_User::verifyUserIds($who['userId'], true) : array(); // merge labels if any if (isset($who['label'])) { $label = $who['label']; if (is_string($label)) { $label = array_map('trim', explode("\t", $labels)); } $raw_userIds = array_merge($raw_userIds, Users_User::labelsToIds($asUserId, $label)); } // merge identifiers if any $identifierType = null; $statuses = null; if (isset($who['identifier'])) { $identifier = $who['identifier']; if (is_string($identifier)) { if (Q_Valid::email($who['identifier'])) { $identifierType = 'email'; } else { if (Q_Valid::phone($who['identifier'])) { $identifierType = 'mobile'; } } $identifier = array_map('trim', explode("\t", $identifier)); } $statuses = array(); $identifier_ids = Users_User::idsFromIdentifiers($identifier, $statuses); $raw_userIds = array_merge($raw_userIds, $identifier_ids); } // merge fb uids if any if (isset($who['fb_uid'])) { $fb_uids = $who['fb_uid']; if (is_string($fb_uids)) { $fb_uids = array_map('trim', explode("\t", $fb_uids)); } $raw_userIds = array_merge($raw_userIds, Users_User::idsFromFacebook($fb_uids)); } if (!empty($who['newFutureUsers'])) { $nfu = $who['newFutureUsers']; for ($i = 0; $i < $nfu; ++$i) { $raw_userIds[] = Users::futureUser('none', null)->id; } } // ensure that each userId is included only once // and remove already participating users $raw_userIds = array_unique($raw_userIds); $total = count($raw_userIds); $userIds = Streams_Participant::filter($raw_userIds, $stream); $to_invite = count($userIds); $appUrl = !empty($options['appUrl']) ? $options['appUrl'] : Q_Request::baseUrl() . '/' . Q_Config::get("Streams", "types", $stream->type, "invite", "url", "plugins/Streams/stream"); // now check and define levels for invited user $readLevel = isset($options['readLevel']) ? $options['readLevel'] : null; if (isset($readLevel)) { if (!$stream->testReadLevel($readLevel)) { // We can't assign greater read level to other people than we have ourselves! throw new Users_Exception_NotAuthorized(); } } $writeLevel = isset($options['writeLevel']) ? $options['writeLevel'] : null; if (isset($writeLevel)) { if (!$stream->testWriteLevel($writeLevel)) { // We can't assign greater write level to other people than we have ourselves! throw new Users_Exception_NotAuthorized(); } } $adminLevel = isset($options['adminLevel']) ? $options['adminLevel'] : null; if (isset($adminLevel)) { if (!$stream->testAdminLevel($adminLevel + 1)) { // We can't assign an admin level greater, or equal, to our own! // A stream's publisher can assign owners. Owners can assign admins. // Admins can confer powers to invite others, to some people. // Those people can confer the privilege to publish a message re this stream. // But admins can't assign other admins, and even stream owners // can't assign other owners. throw new Users_Exception_NotAuthorized(); } } // calculate expiry time $duration = Q_Config::get("Streams", "types", $stream->type, "invite", "duration", false); $expiry = $duration ? strtotime($duration) : null; // let node handle the rest, and get the result $params = array("Q/method" => "Streams/Stream/invite", "invitingUserId" => $asUserId, "username" => $asUser->username, "userIds" => Q::json_encode($userIds), "stream" => Q::json_encode($stream->toArray()), "appUrl" => $appUrl, "label" => Q::ifset($options, 'label', null), "myLabel" => Q::ifset($options, 'myLabel', null), "readLevel" => $readLevel, "writeLevel" => $writeLevel, "adminLevel" => $adminLevel, "displayName" => isset($options['displayName']) ? $options['displayName'] : Streams::displayName($asUser), "expiry" => $expiry); if ($template) { $params['template'] = $template; $params['batchName'] = $batchName; } $result = Q_Utils::queryInternal('Q/node', $params); return array('success' => $result, 'invited' => $userIds, 'statuses' => $statuses, 'identifierType' => $identifierType, 'alreadyParticipating' => $total - $to_invite); }
/** * Saves an image, usually sent by the client, in one or more sizes. * @method save * @static * @param {array} $params * @param {string} [$params.data] the image data * @param {string} [$params.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.merge=""] path under web dir for an optional image to use as a background * @param {string} [$params.crop] array with keys "x", "y", "w", "h" to crop the original image * @param {string} [$params.save=array("x" => "")] array of $size => $basename pairs * where the size is of the format "WxH", and either W or H can be empty. * @param {string} [$params.skipAccess=false] if true, skips the check for authorization to write files there * @return {array} an array of ($size => $fullImagePath) pairs */ static function save($params) { if (empty($params['data'])) { throw new Q_Exception("Image data is missing"); } $imageData = $params['data']; $image = imagecreatefromstring($imageData); if (!$image) { throw new Q_Exception("Image type not supported"); } // image dimensions $maxW = Q_Config::get('Q', 'uploads', 'limits', 'image', 'width', 5000); $maxH = Q_Config::get('Q', 'uploads', 'limits', 'image', 'height', 5000); $iw = imagesx($image); $ih = imagesy($image); if ($maxW and $iw > $maxW) { throw new Q_Exception("Uploaded image width exceeds {$maxW}"); } if ($maxH and $iw > $maxH) { throw new Q_Exception("Uploaded image width exceeds {$maxH}"); } // check whether we can write to this path, and create dirs if needed $path = isset($params['path']) ? $params['path'] : 'uploads'; $subpath = isset($params['subpath']) ? $params['subpath'] : ''; $realPath = Q::realPath(APP_WEB_DIR . DS . $path); if ($realPath === false) { throw new Q_Exception_MissingFile(array('filename' => APP_WEB_DIR . DS . $path)); } $writePath = $realPath . ($subpath ? DS . $subpath : ''); $lastChar = substr($writePath, -1); if ($lastChar !== DS and $lastChar !== '/') { $writePath .= DS; } $throwIfNotWritable = empty($params['skipAccess']) ? true : null; Q_Utils::canWriteToPath($writePath, $throwIfNotWritable, true); // check if exif is available if (self::isJPEG($imageData)) { $exif = exif_read_data("data://image/jpeg;base64," . base64_encode($imageData)); // rotate original image if necessary (hopefully it's not too large). if (!empty($exif['Orientation'])) { switch ($exif['Orientation']) { case 3: $image = imagerotate($image, 180, 0); break; case 6: $image = imagerotate($image, -90, 0); break; case 8: $image = imagerotate($image, 90, 0); break; } } } $crop = isset($params['crop']) ? $params['crop'] : array(); $save = !empty($params['save']) ? $params['save'] : array('x' => ''); // crop parameters - size of source image $isw = isset($crop['w']) ? $crop['w'] : $iw; $ish = isset($crop['h']) ? $crop['h'] : $ih; $isx = isset($crop['x']) ? $crop['x'] : 0; $isy = isset($crop['y']) ? $crop['y'] : 0; // process requested thumbs $data = array(); $merge = null; $m = isset($params['merge']) ? $params['merge'] : null; if (isset($m) && strtolower(substr($m, -4)) === '.png') { $mergePath = Q::realPath(APP_WEB_DIR . DS . implode(DS, explode('/', $m))); if ($mergePath) { $merge = imagecreatefrompng($mergePath); $mw = imagesx($merge); $mh = imagesy($merge); } } foreach ($save as $size => $name) { if (empty($name)) { // generate a filename do { $name = Q_Utils::unique(8) . '.png'; } while (file_exists($writePath . $name)); } if (strrpos($name, '.') === false) { $name .= '.png'; } list($n, $ext) = explode('.', $name); $sw = $isw; $sh = $ish; $sx = $isx; $sy = $isy; // determine destination image size if (!empty($size)) { $sa = explode('x', $size); if (count($sa) > 1) { if ($sa[0] === '') { if ($sa[1] === '') { $dw = $sw; $dh = $sh; } else { $dh = intval($sa[1]); $dw = $sw * $dh / $sh; } } else { $dw = intval($sa[0]); if ($sa[1] === '') { $dh = $sh * $dw / $sw; } else { $dh = intval($sa[1]); } } } else { $dw = $dh = intval($sa[0]); } // calculate the origin point of source image // we have a cropped image of dimension $sw, $sh and need to make new with dimension $dw, $dh if ($dw / $sw < $dh / $sh) { // source is wider then destination $new = $dw / $dh * $sh; $sx += round(($sw - $new) / 2); $sw = round($new); } else { // source is narrower then destination $new = $dh / $dw * $sw; $sy += round(($sh - $new) / 2); $sh = round($new); } } else { $size = ''; $dw = $sw; $dh = $sh; } // create destination image $maxWidth = Q_Config::get('Q', 'images', 'maxWidth', null); $maxHeight = Q_Config::get('Q', 'images', 'maxHeight', null); if (isset($maxWidth) and $dw > $maxWidth) { throw new Q_Exception("Image width exceeds maximum width of {$dw}"); } if (isset($maxHeight) and $dh > $maxHeight) { throw new Q_Exception("Image height exceeds maximum height of {$dh}"); } $thumb = imagecreatetruecolor($dw, $dh); $res = $sw === $dw && $sh === $dh ? imagecopy($thumb, $image, 0, 0, $sx, $sy, $sw, $sh) : imagecopyresampled($thumb, $image, 0, 0, $sx, $sy, $dw, $dh, $sw, $sh); if (!$res) { throw new Q_Exception("Failed to save image file of type '{$ext}'"); } if ($merge) { $mergethumb = imagecreatetruecolor($mw, $mh); imagesavealpha($mergethumb, false); imagealphablending($mergethumb, false); if (imagecopyresized($mergethumb, $merge, 0, 0, 0, 0, $dw, $dh, $mw, $mh)) { imagecopy($thumb, $mergethumb, 0, 0, 0, 0, $dw, $dh); } } switch ($ext) { case 'jpeg': case 'jpeg': $func = 'imagejpeg'; break; case 'gif': $func = 'imagegif'; break; case 'png': default: $func = 'imagepng'; break; } if ($res = call_user_func($func, $thumb, $writePath . $name)) { $data[$size] = $subpath ? "{$path}/{$subpath}/{$name}" : "{$path}/{$name}"; } } $data[''] = $subpath ? "{$path}/{$subpath}" : "{$path}"; /** * @event Q/image/save {after} * @param {string} user * @param {string} path * @param {string} subpath * @param {string} writePath * @param {string} data */ Q::event('Q/image/save', compact('path', 'subpath', 'writePath', 'data', 'save', 'crop'), 'after'); return $data; }
/** * Append a message to the main log * @method log * @static * @param {mixed} $message * the message to append. Usually a string. * @param {string} $key=null * The name of log file. Defaults to "$app_name.log" * @param {bool} $timestamp=true * whether to prepend the current timestamp * @param {array} $options * @param {integer} [$options.maxLength=ini_get('log_errors_max_len')] * @param {integer} [$options.maxDepth=3] * @throws {Q_Exception_MissingFile} * If unable to create directory or file for the log */ static function log($message, $key = null, $timestamp = true, $options = array()) { if (is_array($timestamp)) { $options = $timestamp; $timestamp = true; } if (is_array($key)) { $options = $key; $key = null; $timestamp = true; } if (false === Q::event('Q/log', compact('message', 'timestamp', 'error_log_arguments'), 'before')) { return; } if (!is_string($message)) { $maxDepth = Q::ifset($options, 'maxDepth', 3); if (!is_object($message)) { $message = Q::var_dump($message, $maxDepth, '$', 'text'); } else { if (!is_callable(array($message, '__toString'))) { $message = Q::var_dump($message, $maxDepth, '$', 'text'); } } } $app = Q_Config::get('Q', 'app', null); if (!isset($app)) { $app = defined('APP_DIR') ? basename(APP_DIR) : 'Q App'; } $message = "(" . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : "cli") . ") {$app}: {$message}"; $max_len = Q::ifset($options, 'maxLength', Q_Config::get('Q', 'log', 'maxLength', ini_get('log_errors_max_len'))); $path = (defined('APP_FILES_DIR') ? APP_FILES_DIR : Q_FILES_DIR) . DS . 'Q' . DS . Q_Config::get('Q', 'internal', 'logDir', 'logs'); $mask = umask(00); if (!($realPath = Q::realPath($path)) and !($realPath = Q::realPath($path, true))) { if (!@mkdir($path, 0777, true)) { throw new Q_Exception_FilePermissions(array('action' => 'create', 'filename' => $path, 'recommendation' => ' Please set the app files directory to be writable.')); } $realPath = Q::realPath($path, true); } $filename = (isset($key) ? $key : $app) . '.log'; $toSave = "\n" . ($timestamp ? '[' . date('Y-m-d h:i:s') . '] ' : '') . substr($message, 0, $max_len); file_put_contents($realPath . DS . $filename, $toSave, FILE_APPEND); umask($mask); }
/** * Adds inline template to the response * @method addTemplate * @static * @param {string} $name The location of the template file relative to the "views" folder * @param {string} [$type="handlebars"] * @param {array} [$params=array()] Optional array of parameters to pass to PHP * @param {array} [$slotName=null] A way to override the slot name. Pass "" here to * have the script lines be returned first by Q_Response::scriptLines. * The other special value, "Q", is intended for internal use. * @return {boolean} returns false if template was already added, else returns true */ static function addTemplate($name, $type = 'handlebars', $params = array(), $slotName = null) { self::$templates[] = compact('name', 'type'); // Now, for the slot if (!isset($slotName)) { $slotName = isset(self::$slotName) ? self::$slotName : ''; } if (!isset(self::$inlineTemplates[$slotName])) { self::$inlineTemplates[$slotName] = array(); } foreach (self::$inlineTemplates[$slotName] as $template) { Q::log("=====\n\n" . $template['name']); if ($template['name'] == $name && $template['type'] == $type) { return false; // already added } } $filename = Q::realPath("views/{$name}.{$type}"); if (!$filename) { throw new Q_Exception_MissingFile(array('filename' => "views/{$name}.{$type}")); } if ($type === 'php') { $ob = new Q_OutputBuffer(); Q::includeFile($filename, $params, false); $content = $ob->getClean(); } else { $content = file_get_contents($filename); } if (!$content) { throw new Q_Exception("Failed to load template '{$name}'"); } self::$inlineTemplates[$slotName][] = compact('name', 'content', 'type'); return true; }
/** * Modify a config file by clearing some data * Config file is searched in APP_DIR/files forder. If config server url is defined * the filename is searched on config server * @method clearOnServer * @static * @param {string} $filename The name of the config file. If config server is defined, file is changed there * @param {string|array} [$args=null] OA key or an array of keys for traversing the tree. * If keys are not supplied the file is cleared * If all-but-last keys point to plain array, last key is interpreted as a member * of that array and only this array member is removed * If all-but-last keys point to associative array (A) and last key is plain array (B) * all keys from array A which are in array B are unset * @param {boolean} [$noSave=false] Weather result shall be returned or saved. Shall be of type boolean * @return {boolean} Wheather data was successfuly cleared. If some key does not exist still true * @throws {Q_Exception} */ static function clearOnServer($filename, $args = null, $noSave = false) { if (!isset($args) || $args === 'null') { $args = array(); } if (is_string($args)) { $args = array($args); } if (is_string($noSave)) { $noSave = json_decode($noSave); } $noSave = !!$noSave; if ($cs = self::serverInfo()) { // request config server if (!empty($cs['url'])) { if (!empty($cs['internal'])) { // query "internal" Qbix server return Q_Utils::queryInternal('Q/Config', array('Q/method' => 'clear', 'filename' => $filename, 'args' => $args, 'noSave' => $noSave), $cs['url']); } else { // query "external" Qbix server return Q_Utils::queryExternal('Q/Config', array('Q/method' => 'clear', 'filename' => $filename, 'args' => $args, 'noSave' => $noSave), $cs['url']); } } } // modify local file if (defined('APP_DIR')) { $filename = Q::realPath(APP_DIR . DS . 'files' . DS . $filename); } else { throw new Q_Exception("'APP_DIR' is not defined"); } if (!$filename) { return true; } $tree = new Q_Tree(); if (count($args)) { $tree->load($filename); // if not loaded we consider three empty if (count($args) > 1) { $last = call_user_func_array(array($tree, "get"), $args); } else { $last = $tree->getAll(); } if (is_array($last)) { if (array_keys($last) === range(0, count($last) - 1)) { // it's plain array and we remove it's member $search = array_pop($args); if (!is_array($search)) { $search = array($search); } foreach ($search as $value) { $keys = array_keys($last, $value); for ($deleted = 0, $i = 0; $i < count($keys); $i++) { array_splice($last, $keys[$i] - $deleted++, 1); } } call_user_func_array(array($tree, "clear"), $args); if (count($last)) { array_push($args, $last); call_user_func_array(array($tree, "set"), $args); } } else { // $last is associative array $search = array_pop($args); if (!is_array($search)) { $search = array($search); } foreach ($search as $value) { call_user_func_array(array($tree, "clear"), array_merge($args, array($value))); } } } } else { $tree = new Q_Tree(); } return $noSave ? $tree->getAll() : $tree->save($filename); }