function Streams_after_Q_objects() { $user = Users::loggedInUser(); if (!$user) { return; } $invite = Streams::$followedInvite; if (!$invite) { return; } $displayName = $user->displayName(); if ($displayName) { return; } $stream = new Streams_Stream(); $stream->publisherId = $invite->publisherId; $stream->name = $invite->streamName; if (!$stream->retrieve()) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name'), 'streamName'); } // Prepare the complete invite dialog $invitingUser = Users_User::fetch($invite->invitingUserId); list($relations, $related) = Streams::related($user->id, $stream->publisherId, $stream->name, false); $params = array('displayName' => null, 'action' => 'Streams/basic', 'icon' => $user->iconUrl(), 'token' => $invite->token, 'user' => array('icon' => $invitingUser->iconUrl(), 'displayName' => $invitingUser->displayName(array('fullAccess' => true))), 'stream' => $stream->exportArray(), 'relations' => Db::exportArray($relations), 'related' => Db::exportArray($related)); $config = Streams_Stream::getConfigField($stream->type, 'invite', array()); $defaults = Q::ifset($config, 'dialog', array()); $tree = new Q_Tree($defaults); if ($tree->merge($params)) { $dialogData = $tree->getAll(); if ($dialogData) { Q_Response::setScriptData('Q.plugins.Streams.invite.dialog', $dialogData); Q_Response::addTemplate('Streams/invite/complete'); } } }
function Streams_interests_response() { // serve a javascript file and tell client to cache it $app = Q_Config::expect('Q', 'app'); $communityId = Q::ifset($_REQUEST, 'communityId', $app); $tree = new Q_Tree(); $tree->load("files/Streams/interests/{$communityId}.json"); $categories = $tree->getAll(); foreach ($categories as $category => &$v1) { foreach ($v1 as $k2 => &$v2) { if (!Q::isAssociative($v2)) { ksort($v1); break; } ksort($v2); } } header('Content-Type: text/javascript'); header("Pragma: ", true); // 1 day header("Cache-Control: public, max-age=86400"); // 1 day $expires = date("D, d M Y H:i:s T", time() + 86400); header("Expires: {$expires}"); // 1 day $json = Q::json_encode($categories, true); echo "Q.setObject(['Q', 'Streams', 'Interests', 'all', '{$communityId}'], {$json});"; return false; }
/** * Post one or more fields here to change the corresponding basic streams for the logged-in user. Fields can include: * "firstName": specify the first name directly * "lastName": specify the last name directly * "fullName": the user's full name, which if provided will be split into first and last name and override them * "gender": the user's gender * "birthday_year": the year the user was born * "birthday_month": the month the user was born * "birthday_day": the day the user was born */ function Streams_basic_post() { Q_Valid::nonce(true); $user = Users::loggedInUser(true); $request = $_REQUEST; $fields = array(); if (!empty($request['birthday_year']) && !empty($request['birthday_month']) && !empty($request['birthday_day'])) { $request['birthday'] = sprintf("%04d-%02d-%02d", $_REQUEST['birthday_year'], $_REQUEST['birthday_month'], $_REQUEST['birthday_day']); } // $request['icon'] = $user->icon; if (isset($request['fullName'])) { $name = Streams::splitFullName($request['fullName']); $request['firstName'] = $name['first']; $request['lastName'] = $name['last']; } foreach (array('firstName', 'lastName', 'birthday', 'gender') as $field) { if (isset($request[$field])) { $fields[] = $field; } } $p = new Q_Tree(); $p->load(STREAMS_PLUGIN_CONFIG_DIR . DS . 'streams.json'); $p->load(APP_CONFIG_DIR . DS . 'streams.json'); $names = array(); foreach ($fields as $field) { $names[] = "Streams/user/{$field}"; } $streams = Streams::fetch($user, $user->id, $names); foreach ($fields as $field) { $name = "Streams/user/{$field}"; $type = $p->get($name, "type", null); if (!$type) { throw new Q_Exception("Missing {$name} type", $field); } $title = $p->get($name, "title", null); if (!$title) { throw new Q_Exception("Missing {$name} title", $field); } $stream = $streams[$name]; if (isset($stream) and $stream->content === (string) $request[$field]) { continue; } if (!isset($stream)) { $stream = new Streams_Stream(); $stream->publisherId = $user->id; $stream->name = $name; } $messageType = $stream->wasRetrieved() ? 'Streams/changed' : 'Streams/created'; $stream->content = (string) $request[$field]; $stream->type = $type; $stream->title = $title; $stream->changed($user->id, $messageType); } }
function Streams_after_Users_User_saveExecute($params) { // If the username or icon was somehow modified, // update all the avatars for this publisher $modifiedFields = $params['modifiedFields']; $user = $params['row']; $updates = array(); if (isset($modifiedFields['username'])) { $updates['username'] = $modifiedFields['username']; } if (isset($modifiedFields['icon'])) { $updates['icon'] = $modifiedFields['icon']; } if ($user->id === Users::communityId()) { $firstName = Users::communityName(); $lastName = Users::communitySuffix(); $firstName = $firstName ? $firstName : ""; $lastName = $lastName ? $lastName : ""; } else { $firstName = Q::ifset(Streams::$cache, 'register', 'first', ''); $lastName = Q::ifset(Streams::$cache, 'register', 'last', ''); } if ($params['inserted']) { // create some standard streams for them $onInsert = Q_Config::get('Streams', 'onInsert', 'Users_User', array()); if (!$onInsert) { return; } $p = new Q_Tree(); $p->load(STREAMS_PLUGIN_CONFIG_DIR . DS . 'streams.json'); $p->load(APP_CONFIG_DIR . DS . 'streams.json'); $values = array('Streams/user/firstName' => $firstName, 'Streams/user/lastName' => $lastName); // Check for user data from facebook if (!empty(Users::$cache['facebookUserData'])) { $userData = Users::$cache['facebookUserData']; foreach ($userData as $name_fb => $value) { foreach ($p->getAll() as $name => $info) { if (isset($info['name_fb']) and $info['name_fb'] === $name_fb) { $onInsert[] = $name; $values[$name] = $value; } } } } foreach ($onInsert as $name) { $stream = Streams::fetchOne($user->id, $user->id, $name); if (!$stream) { // it shouldn't really be in the db yet $stream = new Streams_Stream(); $stream->publisherId = $user->id; $stream->name = $name; } $stream->type = $p->expect($name, "type"); $stream->title = $p->expect($name, "title"); $stream->content = $p->get($name, "content", ''); // usually empty $stream->readLevel = $p->get($name, 'readLevel', Streams_Stream::$DEFAULTS['readLevel']); $stream->writeLevel = $p->get($name, 'writeLevel', Streams_Stream::$DEFAULTS['writeLevel']); $stream->adminLevel = $p->get($name, 'adminLevel', Streams_Stream::$DEFAULTS['adminLevel']); if ($name === "Streams/user/icon") { $sizes = Q_Config::expect('Users', 'icon', 'sizes'); sort($sizes); $stream->setAttribute('sizes', $sizes); $stream->icon = $user->iconUrl(); } if (isset($values[$name])) { $stream->content = $values[$name]; } $stream->save(); // this also inserts avatars $o = array('userId' => $user->id, 'skipAccess' => true); $so = $p->get($name, "subscribe", array()); if ($so === false) { $stream->join($o); } else { $stream->subscribe(array_merge($o, $so)); } } // Save a greeting stream, to be edited $communityId = Users::communityId(); Streams::create($user->id, $user->id, "Streams/greeting", array('name' => "Streams/greeting/{$communityId}")); // Create some standard labels $label = new Users_Label(); $label->userId = $user->id; $label->label = 'Streams/invited'; $label->icon = 'labels/Streams/invited'; $label->title = 'People I invited'; $label->save(true); $label2 = new Users_Label(); $label2->userId = $user->id; $label2->label = 'Streams/invitedMe'; $label2->icon = 'labels/Streams/invitedMe'; $label2->title = 'Who invited me'; $label2->save(true); // By default, users they invite should see their full name $access = new Streams_Access(); $access->publisherId = $user->id; $access->streamName = 'Streams/user/firstName'; $access->ofUserId = ''; $access->ofContactLabel = 'Streams/invited'; $access->grantedByUserId = $user->id; $access->readLevel = Streams::$READ_LEVEL['content']; $access->writeLevel = -1; $access->adminLevel = -1; $access->save(); $access = new Streams_Access(); $access->publisherId = $user->id; $access->streamName = 'Streams/user/lastName'; $access->ofUserId = ''; $access->ofContactLabel = 'Streams/invited'; $access->grantedByUserId = $user->id; $access->readLevel = Streams::$READ_LEVEL['content']; $access->writeLevel = -1; $access->adminLevel = -1; $access->save(); // NOTE: the above saving of access caused Streams::updateAvatar to run, // insert a Streams_Avatar row for the new user, and properly configure it. } else { if ($modifiedFields) { if ($updates) { Streams_Avatar::update()->set($updates)->where(array('publisherId' => $user->id))->execute(); } foreach ($modifiedFields as $field => $value) { $name = Q_Config::get('Streams', 'onUpdate', 'Users_User', $field, null); if (!$name) { continue; } $stream = isset(Streams::$beingSaved[$field]) ? Streams::$beingSaved[$field] : Streams::fetchOne($user->id, $user->id, $name); if (!$stream) { // it should probably already be in the db continue; } $stream->content = $value; if ($name === "Streams/user/icon") { $sizes = Q_Config::expect('Users', 'icon', 'sizes'); sort($sizes); $attributes = $stream->attributes; $stream->setAttribute('sizes', $sizes); $stream->icon = $changes['icon'] = $user->iconUrl(); } Streams::$beingSavedQuery = $stream->changed($user->id); } } } }
/** * Clears the value of a field, possibly deep inside the array * @method clear * @param {string} $key1 The name of the first key in the configuration path * @param {string} $key2 Optional. The name of the second key in the configuration path. * You can actually pass as many keys as you need, * delving deeper and deeper into the configuration structure. * All but the second-to-last parameter are interpreted as keys. */ function clear($key1) { if (!isset($key1)) { $this->parameters = self::$cache = array(); return; } $args = func_get_args(); $args_count = func_num_args(); $result =& $this->parameters; for ($i = 0; $i < $args_count - 1; ++$i) { $key = $args[$i]; if (!is_array($result) or !array_key_exists($key, $result)) { return false; } $result =& $result[$key]; } // clear the final value $key = $args[$args_count - 1]; if (isset($key)) { unset($result[$key]); } else { array_pop($result); } }
/** * 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(); }
/** * Creates a new stream in the system * @method create * @static * @param {string} $asUserId The user who is attempting to create the stream. * @param {string} $publisherId The id of the user to publish the stream. * @param {string} $type The type of the stream to create. * @param {array} $fields Use this to set additional fields for the stream: * @param {string} [$fields.title=null] You can set the stream's title * @param {string} [$fields.icon=null] You can set the stream's icon * @param {string} [$fields.title=null] You can set the stream's content * @param {string} [$fields.attributes=null] You can set the stream's attributes directly as a JSON string * @param {string|integer} [$fields.readLevel=null] You can set the stream's read access level, see Streams::$READ_LEVEL * @param {string|integer} [$fields.writeLevel=null] You can set the stream's write access level, see Streams::$WRITE_LEVEL * @param {string|integer} [$fields.adminLevel=null] You can set the stream's admin access level, see Streams::$ADMIN_LEVEL * @param {string} [$fields.name=null] Here you can specify an exact name for the stream to be created. Otherwise a unique one is generated automatically. * @param {boolean} [$fields.skipAccess=false] Skip all access checks when creating and relating the stream. * @param {array} [$relate=array()] * The user would also be authorized if the stream would be related to * an existing category stream, in which the user has a writeLevel of at least "relate", * and the user that would be publishing this new stream has a template for this stream type * that is related to either the category stream or a template matching the category stream. * To test for this, pass an array with the following keys: * @param {string} $relate.publisherId The id of the user publishing that stream, defaults to $publisherId * @param {string} $relate.streamName The name of the stream to which the new stream would be related * @param {string} [$relate.type] The type of relation, defaults to "" * @param {string} [$relate.weight] To set the weight for the relation * @return {Streams_Stream|boolean} Returns the stream that was created. * @throws {Users_Exception_NotAuthorized} */ static function create($asUserId, $publisherId, $type, $fields = array(), $relate = null) { $skipAccess = Q::ifset($fields, 'skipAccess', false); if (!isset($asUserId)) { $asUserId = Users::loggedInUser(); if (!$asUserId) { $asUserId = ""; } } if ($asUserId instanceof Users_User) { $asUserId = $asUserId->id; } if ($publisherId instanceof Users_User) { $publisherId = $publisherId->id; } $authorized = self::isAuthorizedToCreate($asUserId, $publisherId, $type, $relate); if (!$authorized and !$skipAccess) { throw new Users_Exception_NotAuthorized(); } // OK we are good to go! $stream = new Streams_Stream(); $stream->publisherId = $publisherId; if (!empty($fields['name'])) { $p = new Q_Tree(); $p->load(STREAMS_PLUGIN_CONFIG_DIR . DS . 'streams.json'); $p->load(APP_CONFIG_DIR . DS . 'streams.json'); if ($info = $p->get($fields['name'], array())) { foreach (Base_Streams_Stream::fieldNames() as $f) { if (isset($info[$f])) { $stream->{$f} = $info[$f]; } } } } if (!isset($stream->type)) { $stream->type = $type; } // prepare attributes field if (isset($fields['attributes']) and is_array($fields['attributes'])) { $fields['attributes'] = json_encode($fields['attributes']); } // extend with any config defaults for this stream type $fieldNames = Streams::getExtendFieldNames($type); $fieldNames[] = 'name'; $defaults = Q_Config::get('Streams', 'types', $type, 'defaults', array()); foreach ($fieldNames as $f) { if (isset($fields[$f])) { $stream->{$f} = $fields[$f]; } else { if (array_key_exists($f, $defaults)) { $stream->{$f} = $defaults[$f]; } } } // ready to persist this stream to the database if ($relate['streamName']) { $rs = Streams::fetchOne($asUserId, $relate['publisherId'], $relate['streamName']); if ($rs and $rs->inheritAccess) { // inherit from the same stream $rs does $inherit = $rs->inheritAccess; } else { // inherit from $rs $json = Q::json_encode(array(array($relate['publisherId'], $relate['streamName']))); } $stream->inheritAccess = $json; } $stream->save(); $stream->post($asUserId, array('type' => 'Streams/created', 'content' => '', 'instructions' => Q::json_encode($stream->toArray())), true); // relate the stream to category stream, if any if ($relate['streamName']) { $result = Streams::relate($asUserId, $relate['publisherId'], $relate['streamName'], $relate['type'], $stream->publisherId, $stream->name, array('weight' => isset($relate['weight']) ? $relate['weight'] : null, 'skipAccess' => $skipAccess)); Q_Response::setSlot('messageTo', $result['messageTo']->exportArray()); } self::$fetch[$asUserId][$publisherId][$stream->name] = array('*' => $stream); return $stream; }
/** * 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); }
if (!is_dir($LOCAL_DIR)) { #App dir die("[ERROR] {$LOCAL_DIR} doesn't exist or is not a directory" . PHP_EOL); } #Define APP_DIR if (!defined('APP_DIR')) { define('APP_DIR', $LOCAL_DIR); } #Include Q try { include $Q_filename; } catch (Exception $e) { die('[ERROR] ' . $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL); } #Parse secondary arguments $config = new Q_Tree(); $mode = ''; for ($i = $FROM_APP ? 1 : 2; $i < $count; ++$i) { switch ($mode) { case 'part': $part = explode('/', $argv[$i]); if (count($part) < 2) { echo "Not enough parameters to {$argv[$i]} option\n{$usage}"; exit; } $config->set('plugin', $part[0]); $config->set('table', $part[1]); if (isset($part[2])) { $config->set('shard', $part[2]); } $mode = '';
/** * Get a structured, sorted array with all the interests in a community * @method interests * @static * @param {string} [$communityId=Users::communityId()] the id of the community * @return {array} an array of $category => ($subcategory =>) $interest */ static function interests($communityId = null) { if (!isset($communityId)) { $communityId = Users::communityId(); } $tree = new Q_Tree(); $tree->load("files/Streams/interests/{$communityId}.json"); $interests = $tree->getAll(); foreach ($interests as $category => &$v1) { foreach ($v1 as $k2 => &$v2) { if (!Q::isAssociative($v2)) { ksort($v1); break; } ksort($v2); } } return $interests; }
/** * @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; } }