/** * 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. * @return {void} */ function Streams_interest_delete() { $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) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => Q::json_encode(compact('publisherId', 'name')))); } $miPublisherId = $user->id; $miName = 'Streams/user/interests'; $myInterests = Streams::fetchOne($user->id, $miPublisherId, $miName); if (!$myInterests) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => Q::json_encode(array('publisherId' => $miPublisherId, 'name' => $miName)))); } $stream->leave(); Streams::unrelate($user->id, $user->id, 'Streams/user/interests', 'Streams/interest', $publisherId, $name, array('adjustWeights' => true)); Q_Response::setSlot('publisherId', $publisherId); Q_Response::setSlot('streamName', $name); /** * Occurs when the logged-in user has successfully removed an interest via HTTP * @event Streams/interest/delete {after} * @param {string} publisherId The publisher of the interest stream * @param {string} title The title of the interest * @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/remove", compact('publisherId', 'title', 'subscribe', 'user', 'stream', 'myInterests'), 'after'); }
function Q_filters_reducisaurus($params) { $parts = $params['parts']; $dest = $params['dest']; $processed = array(); foreach ($parts as $src => $content) { $dest_parts = explode('/', $dest); $src_parts = explode('/', $src); $j = 0; foreach ($dest_parts as $i => $p) { if (!isset($src_parts[$i]) or $src_parts[$i] !== $dest_parts[$i]) { break; } $j = $i + 1; } $dc = count($dest_parts); $sc = count($src_parts); $relative = str_repeat("../", $dc - $j - 1) . implode('/', array_slice($src_parts, $j, $sc - $j - 1)); if ($relative) { $relative .= '/'; } $processed[$src] = preg_replace("/url\\((\\'){0,1}/", 'url($1' . $relative, $content); } $service_url = "http://reducisaurus.appspot.com/css"; $options = array('file' => implode("\n\n", $processed)); $result = Q_Utils::post($service_url, $options); if ($error = substr($result, 0, 5) === 'Error') { throw new Q_Exception("Reducisaurus:\n" . $result); } return $result; }
/** * Adds a label to the system. Fills the "label" (and possibly "icon") slot. * @param {array} $_REQUEST * @param {string} $_REQUEST.title The title of the label * @param {string} [$_REQUEST.label] You can override the label to use * @param {string} [$_REQUEST.icon] Optional path to an icon * @param {string} [$_REQUEST.userId=Users::loggedInUser(true)->id] You can override the user id, if another plugin adds a hook that allows you to do this */ function Users_label_post($params = array()) { $req = array_merge($_REQUEST, $params); Q_Request::requireFields(array('title'), $req, true); $loggedInUserId = Users::loggedInUser(true)->id; $userId = Q::ifset($req, 'userId', $loggedInUserId); $icon = Q::ifset($req, 'icon', null); $title = $req['title']; $l = Q::ifset($req, 'label', 'Users/' . Q_Utils::normalize($title)); Users::canManageLabels($loggedInUserId, $userId, $l, true); $label = new Users_Label(); $label->userId = $userId; $label->label = $l; if ($label->retrieve()) { throw new Users_Exception_LabelExists(); } $label->title = $title; if (is_array($icon)) { // Process any icon that was posted $icon['path'] = 'uploads/Users'; $icon['subpath'] = "{$userId}/label/{$label}/icon"; $data = Q::event("Q/image/post", $icon); Q_Response::setSlot('icon', $data); $label->icon = Q_Request::baseUrl() . '/' . $data['']; } else { $label->icon = 'default'; } $label->save(); Q_Response::setSlot('label', $label->exportArray()); }
static function _create($params, $options) { $title = Q_Utils::normalize($options['title']); $info = $params['info']; $options['name'] = "Places/interest/{$info['geohash']}/{$info['miles']}/{$title}"; return Streams::create(null, $params['publisherId'], 'Places/interest', $options); }
function Websites_0_8_Users_mysql() { $userId = Users::communityId(); Users_Label::addLabel('Websites/admins', $userId, 'Website Admins', 'labels/Websites/admins', false); if (!file_exists(USERS_PLUGIN_FILES_DIR . DS . 'Users' . DS . 'icons' . DS . 'Websites')) { Q_Utils::symlink(WEBSITES_PLUGIN_FILES_DIR . DS . 'Websites' . DS . 'icons' . DS . 'labels' . DS . 'Websites', USERS_PLUGIN_FILES_DIR . DS . 'Users' . DS . 'icons' . DS . 'labels' . DS . 'Websites'); } }
function Streams_before_Q_Utils_canWriteToPath($params, &$result) { extract($params); /** * @var $path * @var $throwIfNotWritable * @var $mkdirIfMissing */ // Assume that Users/before/Q/Utils/canWriteToPath already executed $user = Users::loggedInUser(); $userId = $user ? $user->id : ""; $app = Q_Config::expect('Q', 'app'); $len = strlen(APP_DIR); if (substr($path, 0, $len) === APP_DIR) { $sp = str_replace(DS, '/', substr($path, $len + 1)); if (substr($sp, -1) === '/') { $sp = substr($sp, 0, strlen($sp) - 1); } $prefix = "files/{$app}/uploads/Streams/"; $len = strlen($prefix); if (substr($sp, 0, $len) === $prefix) { $splitId = Q_Utils::splitId($userId); $prefix2 = "files/{$app}/uploads/Streams/invitations/{$splitId}/"; if ($userId and substr($sp, 0, strlen($prefix2)) === $prefix2) { $result = true; // user can write any invitations here return; } $parts = explode('/', substr($sp, $len)); $c = count($parts); if ($c >= 3) { $result = false; for ($j = 0; $j < $c - 3; ++$j) { $publisherId = implode('', array_slice($parts, 0, $j + 1)); $l = $j; for ($i = $c - 1; $i > $j; --$i) { $l = $i; if (in_array($parts[$i], array('icon', 'file'))) { break; } } $name = implode('/', array_slice($parts, $j + 1, $l - $j - 1)); if ($name and $stream = Streams::fetchOne($userId, $publisherId, $name)) { $result = $stream->testWriteLevel('edit'); Streams::$cache['canWriteToStream'] = $stream; break; } } } } } if (!$result and $throwIfNotWritable) { throw new Q_Exception_CantWriteToPath(); } }
/** * This tool contains functionality to show things in columns * @class Q columns * @constructor * @param {array} [options] Provide options for this tool * @param {array} [options.animation] For customizing animated transitions * @param {integer} [options.animation.duration] The duration of the transition in milliseconds, defaults to 500 * @param {array} [options.animation.hide] The css properties in "hide" state of animation * @param {array} [options.animation.show] The css properties in "show" state of animation * @param {array} [options.back] For customizing the back button on mobile * @param {string} [options.back.src] The src of the image to use for the back button * @param {boolean} [options.back.triggerFromTitle] Whether the whole title would be a trigger for the back button. Defaults to true. * @param {boolean} [options.back.hide] Whether to hide the back button. Defaults to false, but you can pass true on android, for example. * @param {array} [options.close] For customizing the back button on desktop and tablet * @param {string} [options.close.src] The src of the image to use for the close button * @param {string} [options.title] You can put a default title for all columns here (which is shown as they are loading) * @param {string} [options.column] You can put a default content for all columns here (which is shown as they are loading) * @param {array} [options.clickable] If not null, enables the Q/clickable tool with options from here. Defaults to null. * @param {array} [options.scrollbarsAutoHide] If not null, enables Q/scrollbarsAutoHide functionality with options from here. Enabled by default. * @param {boolean} [options.fullscreen] Whether to use fullscreen mode on mobile phones, using document to scroll instead of relying on possibly buggy "overflow" CSS implementation. Defaults to true on Android, false everywhere else. * @param {array} [options.columns] In PHP only, an array of $name => $column pairs, where $column is in the form array('title' => $html, 'content' => $html, 'close' => true) * @return {string} */ function Q_columns_tool($options) { $jsOptions = array('animation', 'back', 'close', 'title', 'scrollbarsAutoHide', 'fullscreen'); Q_Response::setToolOptions(Q::take($options, $jsOptions)); if (!isset($options['columns'])) { return ''; } Q_Response::addScript('plugins/Q/js/tools/columns.js'); Q_Response::addStylesheet('plugins/Q/css/columns.css'); $result = '<div class="Q_columns_container Q_clearfix">'; $columns = array(); $i = 0; $closeSrc = Q::ifset($options, 'close', 'src', 'plugins/Q/img/x.png'); $backSrc = Q::ifset($options, 'back', 'src', 'plugins/Q/img/back-v.png'); foreach ($options['columns'] as $name => $column) { $close = Q::ifset($column, 'close', $i > 0); $Q_close = Q_Request::isMobile() ? 'Q_close' : 'Q_close Q_back'; $closeHtml = !$close ? '' : (Q_Request::isMobile() ? '<div class="Q_close Q_back">' . Q_Html::img($backSrc, 'Back') . '</div>' : '<div class="Q_close">' . Q_Html::img($closeSrc, 'Close') . '</div>'); $n = Q_Html::text($name); $columnClass = 'Q_column_' . Q_Utils::normalize($name) . ' Q_column_' . $i; if (isset($column['html'])) { $html = $column['html']; $columns[] = <<<EOT \t<div class="Q_columns_column {$columnClass}" data-index="{$i}" data-name="{$n}"> \t\t{$html} \t</div> EOT; } else { $titleHtml = Q::ifset($column, 'title', '[title]'); $columnHtml = Q::ifset($column, 'column', '[column]'); $classes = $columnClass . ' ' . Q::ifset($column, 'class', ''); $attrs = ''; if (isset($column['data'])) { $json = Q::json_encode($column['data']); $attrs = 'data-more="' . Q_Html::text($json) . '"'; foreach ($column['data'] as $k => $v) { $attrs .= 'data-' . Q_Html::text($k) . '="' . Q_Html::text($v) . '" '; } } $data = Q::ifset($column, 'data', ''); $columns[] = <<<EOT \t<div class="Q_columns_column {$classes}" data-index="{$i}" data-name="{$n}" {$attrs}> \t\t<div class="Q_columns_title"> \t\t\t{$closeHtml} \t\t\t<h2 class="Q_title_slot">{$titleHtml}</h2> \t\t</div> \t\t<div class="Q_column_slot">{$columnHtml}</div> \t</div> EOT; } ++$i; } $result .= "\n" . implode("\n", $columns) . "\n</div>"; return $result; }
/** * We are going to implement a subset of the OAuth 1.0a functionality for now, * and later we can expand it to match the full OAuth specification. */ function Users_authorize_response() { if (Q_Response::getErrors()) { Q_Dispatcher::showErrors(); } $response_type = 'token'; $token_type = 'bearer'; $client_id = $_REQUEST['client_id']; $state = $_REQUEST['state']; $skip = Q::ifset($_REQUEST, 'skip', false); $scope = Users_OAuth::requestedScope(true, $scopes); $client = Users_User::fetch($client_id, true); if (!$client) { throw new Q_Exception_MissingRow(array('table' => 'client user', 'criteria' => "id = '{$client_id}'"), 'client_id'); } if (empty($client->url)) { throw new Q_Exception("Client app needs to register url", 'client_id'); } $redirect_uri = Q::ifset($_REQUEST, 'redirect_uri', $client->url); $user = Users::loggedInUser(); $oa = null; if (isset(Users::$cache['oAuth'])) { $oa = Users::$cache['oAuth']; } else { if ($user) { $oa = new Users_OAuth(); $oa->client_id = $client_id; $oa->userId = $user->id; $oa->state = $state; $oa = $oa->retrieve(); } } $remaining = $scope; if ($oa and $oa->wasRetrieved()) { // User is logged in and already has a token for this client_id and state $paths = Q_Config::get('Users', 'authorize', 'clients', Q::app(), 'redirectPaths', false); $path = substr($redirect_uri, strlen($client->url) + 1); $p = array('response_type' => $response_type, 'token_type' => $token_type, 'access_token' => $oa->access_token, 'expires_in' => $oa->token_expires_seconds, 'scope' => implode(' ', $scope), 'state' => $oa->state); $p = Q_Utils::sign($p, 'Q.Users.oAuth'); // the redirect uri could be a native app url scheme $s = strpos($redirect_uri, '#') === false ? '#' : '&'; $redirect_uri = Q_Uri::from($redirect_uri . $s . http_build_query($p), false)->toUrl(); if (!Q::startsWith($redirect_uri, $client->url) or is_array($paths) and !in_array($path, $paths)) { throw new Users_Exception_Redirect(array('uri' => $redirect_uri)); } Q_Response::redirect($redirect_uri); return false; } $terms_label = Users::termsLabel('authorize'); Q_Response::setScriptData('Q.Users.authorize', compact('client_id', 'redirect_uri', 'scope', 'scopes', 'remaining', 'state', 'response_type', 'skip')); $content = Q::view('Users/content/authorize.php', compact('client', 'user', 'redirect_uri', 'scope', 'scopes', 'remaining', 'state', 'terms_label', 'response_type', 'skip')); Q_Response::setSlot('content', $content); Q_Response::setSlot('column0', $content); return true; }
function Websites_0_8_Users_mysql() { $userId = Q_Config::get("Websites", "user", "id", null); if (!$userId) { throw new Q_Exception('Websites: Please fill in the config field "Websites"/"user"/"id"'); } Users_Label::addLabel('Websites/admins', $userId, 'Website Admins', 'labels/Websites/admins', false); if (!file_exists('Websites')) { Q_Utils::symlink(WEBSITES_PLUGIN_FILES_DIR . DS . 'Websites' . DS . 'icons' . DS . 'labels' . DS . 'Websites', USERS_PLUGIN_FILES_DIR . DS . 'Users' . DS . 'icons' . DS . 'Websites'); } }
/** * Adds a label to the system. Fills the "label" (and possibly "icon") slot. * @param {array} $_REQUEST * @param {string} $_REQUEST.title The title of the label * @param {string} [$_REQUEST.label] You can override the label to use * @param {string} [$_REQUEST.icon] Optional path to an icon * @param {string} [$_REQUEST.userId=Users::loggedInUser(true)->id] You can override the user id, if another plugin adds a hook that allows you to do this */ function Users_label_post($params = array()) { $req = array_merge($_REQUEST, $params); Q_Request::requireFields(array('title'), $req, true); $loggedInUserId = Users::loggedInUser(true)->id; $userId = Q::ifset($req, 'userId', $loggedInUserId); $icon = Q::ifset($req, 'icon', null); $title = Q::ifset($req, 'title', null); $l = Q::ifset($req, 'label', 'Users/' . Q_Utils::normalize($title)); $label = Users_Label::addLabel($l, $userId, $title, $icon); Q_Response::setSlot('label', $label->exportArray()); }
/** * This is the default handler for the Q/responseExtras event. * It should not be invoked during AJAX requests, and especially * not during JSONP requests. It will output things like the nonce, * which prevents CSRF attacks, but is only supposed to be printed * on our webpages and not also given to anyone who does a JSONP request. */ function Q_before_Q_responseExtras() { $app = Q_Config::expect('Q', 'app'); $uri = Q_Dispatcher::uri(); $url = Q_Request::url(true); $base_url = Q_Request::baseUrl(); $ajax = Q_Request::isAjax(); if (!$uri) { return; } $info = array('url' => $url, 'uriString' => (string) $uri); if ($uri) { $info['uri'] = $uri->toArray(); } if (!$ajax) { $info = array_merge(array('app' => Q_Config::expect('Q', 'app')), $info, array('proxies' => Q_Config::get('Q', 'proxies', array()), 'baseUrl' => $base_url, 'proxyBaseUrl' => Q_Uri::url($base_url), 'proxyUrl' => Q_Uri::url($url), 'sessionName' => Q_Session::name(), 'nodeUrl' => Q_Utils::nodeUrl(), 'slotNames' => Q_Config::get("Q", "response", "slotNames", array('content', 'dashboard', 'title', 'notices')))); } foreach ($info as $k => $v) { Q_Response::setScriptData("Q.info.{$k}", $v); } if (!$ajax) { $uris = Q_Config::get('Q', 'javascript', 'uris', array()); $urls = array(); foreach ($uris as $u) { $urls["{$u}"] = Q_Uri::url("{$u}"); } Q_Response::setScriptData('Q.urls', $urls); } // Export more variables to inline js $nonce = isset($_SESSION['Q']['nonce']) ? $_SESSION['Q']['nonce'] : null; if ($nonce) { Q_Response::setScriptData('Q.nonce', $nonce); } // Attach stylesheets and scripts foreach (Q_Config::get('Q', 'javascript', 'responseExtras', array()) as $src => $b) { if (!$b) { continue; } Q_Response::addScript($src); } foreach (Q_Config::get('Q', 'stylesheets', 'responseExtras', array()) as $src => $media) { if (!$media) { continue; } if ($media === true) { $media = 'screen,print'; } Q_Response::addStylesheet($src, null, $media); } }
/** * Generates a form with inputs that modify various streams * @class Streams form * @constructor * @param {array} $options * An associative array of parameters, containing: * @param {array} [$options.fields] an associative array of $id => $fieldinfo pairs, * where $id is the id to append to the tool's id, to generate the input's id, * and fieldinfo is either an associative array with the following fields, * or a regular array consisting of fields in the following order: * "publisherId" => Required. The id of the user publishing the stream * "streamName" => Required. The name of the stream * "field" => The stream field to edit, or "attribute:$attributeName" for an attribute. * "input" => The type of the input (@see Q_Html::smartTag()) * "attributes" => Additional attributes for the input * "options" => options for the input (if type is "select", "checkboxes" or "radios") * "params" => array of extra parameters to Q_Html::smartTag */ function Streams_form_tool($options) { $fields = Q::ifset($options, 'fields', array()); $defaults = array('publisherId' => null, 'streamName' => null, 'field' => null, 'type' => 'text', 'attributes' => array(), 'value' => array(), 'options' => array(), 'params' => array()); $sections = array(); $hidden = array(); $contents = ''; foreach ($fields as $id => $field) { if (Q::isAssociative($field)) { $r = Q::take($field, $defaults); } else { $c = count($field); if ($c < 4) { throw new Q_Exception("Streams/form tool: field needs at least 4 values"); } $r = array('publisherId' => $field[0], 'streamName' => $field[1], 'field' => $field[2], 'type' => $field[3], 'attributes' => isset($field[4]) ? $field[4] : array(), 'value' => isset($field[5]) ? $field[5] : '', 'options' => isset($field[6]) ? $field[6] : null, 'params' => isset($field[7]) ? $field[7] : null); } $r['attributes']['name'] = "input_{$id}"; if (!isset($r['type'])) { var_dump($r['type']); exit; } $stream = Streams::fetchOne(null, $r['publisherId'], $r['streamName']); if ($stream) { if (substr($r['field'], 0, 10) === 'attribute:') { $attribute = trim(substr($r['field'], 10)); $value = $stream->get($attribute, $r['value']); } else { $field = $r['field']; $value = $stream->{$field}; } } else { $value = $r['value']; } $tag = Q_Html::smartTag($r['type'], $r['attributes'], $value, $r['options'], $r['params']); $class1 = 'publisherId_' . Q_Utils::normalize($r['publisherId']); $class2 = 'streamName_' . Q_Utils::normalize($r['streamName']); $contents .= "<span class='Q_before {$class1} {$class2}'></span>" . Q_Html::tag('span', array('data-publisherId' => $r['publisherId'], 'data-streamName' => $r['streamName'], 'data-field' => $r['field'], 'data-type' => $r['type'], 'class' => "{$class1} {$class2}"), $tag); $hidden[$id] = array(!!$stream, $r['publisherId'], $r['streamName'], $r['field']); } $contents .= Q_Html::hidden(array('inputs' => Q::json_encode($hidden))); return Q_Html::form('Streams/form', 'post', array(), $contents); // // $fields = array('onSubmit', 'onResponse', 'onSuccess', 'slotsToRequest', 'loader', 'contentElements'); // Q_Response::setToolOptions(Q::take($options, $fields)); // Q_Response::addScript('plugins/Q/js/tools/form.js'); // Q_Response::addStylesheet('plugins/Q/css/form.css'); // return $result; }
/** * Removes a label from the system. * @param {array} $_REQUEST * @param {string} [$_REQUEST.title] Find it by title * @param {string} [$_REQUEST.label] Find it by label * @param {string} [$_REQUEST.userId=Users::loggedInUser(true)->id] You can override the user id, if another plugin adds a hook that allows you to do this */ function Users_label_delete($params = array()) { $req = array_merge($_REQUEST, $params); $loggedInUserId = Users::loggedInUser(true)->id; $userId = Q::ifset($req, 'userId', $loggedInUserId); $l = Q::ifset($req, 'label', null); if (!$l) { if ($title = Q::ifset($req, 'title', null)) { $l = 'Users/' . Q_Utils::normalize($title); } else { throw new Q_Exception_RequiredField(array('field' => 'label')); } } return !!Users_Label::removeLabel($l, $userId); }
function Q_filters_googleClosureCompiler($params) { $content = implode("\n\n", $params['parts']); $compilation_level = isset($params['compilation_level']) ? $params['compilation_level'] : 'SIMPLE_OPTIMIZATIONS'; $service_url = "http://closure-compiler.appspot.com/compile"; $options = array('js_code' => $content, 'compilation_level' => $compilation_level, 'output_format' => 'text', 'output_info' => 'compiled_code'); $result = Q_Utils::post($service_url, $options); if ($error = substr($result, 0, 5) === 'Error') { throw new Q_Exception("Google Closure Compiler:\n" . $result); } if (!trim($result)) { $options['output_info'] = 'errors'; throw new Q_Exception("Google Closure Compiler:\n" . Q_Utils::post($service_url, $options)); } return $result; }
function Q_after_Q_tool_render($params, &$result) { $info = $params['info']; $extra = $params['extra']; if (!is_array($extra)) { $extra = array(); } $id_prefix = Q_Html::getIdPrefix(); $tool_ids = Q_Html::getToolIds(); $tag = Q::ifset($extra, 'tag', 'div'); if (empty($tag)) { Q_Html::popIdPrefix(); return; } $classes = ''; $data_options = ''; $count = count($info); foreach ($info as $name => $opt) { $classes = ($classes ? "{$classes} " : $classes) . implode('_', explode('/', $name)) . '_tool'; $options = Q_Response::getToolOptions($name); if (isset($options)) { $friendly_options = str_replace(array('"', '\\/'), array('"', '/'), Q_Html::text(Q::json_encode($options))); } else { $friendly_options = ''; } $normalized = Q_Utils::normalize($name, '-'); if (isset($options) or $count > 1) { $id = $tool_ids[$name]; $id_string = $count > 1 ? "{$id} " : ''; $data_options .= " data-{$normalized}='{$id_string}{$friendly_options}'"; } $names[] = $name; } if (isset($extra['classes'])) { $classes .= ' ' . $extra['classes']; } $attributes = isset($extra['attributes']) ? ' ' . Q_Html::attributes($extra['attributes']) : ''; $data_retain = !empty($extra['retain']) || Q_Response::shouldRetainTool($id_prefix) ? " data-Q-retain=''" : ''; $data_replace = !empty($extra['replace']) || Q_Response::shouldReplaceWithTool($id_prefix) ? " data-Q-replace=''" : ''; $names = $count === 1 ? ' ' . key($info) : 's ' . implode(" ", $names); $ajax = Q_Request::isAjax(); $result = "<{$tag} id='{$id_prefix}tool' " . "class='Q_tool {$classes}'{$data_options}{$data_retain}{$data_replace}{$attributes}>" . "{$result}</{$tag}>"; if (!Q_Request::isAjax()) { $result = "<!--\nbegin tool{$names}\n-->{$result}<!--\nend tool{$names} \n-->"; } Q_Html::popIdPrefix(); }
/** * 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); }
function Users_device_post() { $user = Users::loggedInUser(true); $token = isset($_REQUEST['token']) ? $_REQUEST['token'] : null; $platform = Q_Request::platform(); $version = Q_Request::OSVersion(); $formFactor = Q_Request::isMobile() ? 'mobile' : (Q_Request::isTablet() ? 'tablet' : null); $device = new Users_Device(); $device->userId = $user->id; $device->deviceId = $token; $device->platform = $platform; $device->version = $version; $device->formFactor = $formFactor; $device->sessionId = Q_Session::id(); $_SESSION['Users']['deviceId'] = $token; Q_Response::setSlot('data', !!$device->save(true)); Q_Utils::sendToNode(array("Q/method" => "Users/device", "userId" => $user->id, "deviceId" => $token)); }
/** * This tool renders ways to get in touch * * @param array [$options] An associative array of options, containing: * @param {string|Users_User} [$options.user] Required. The user object or id of the user exposing their primary identifiers for getting in touch. * @param {boolean|string} [$options.email] Pass true here to use the primary verified email address, if any. Or pass the string label for this button. * @param {string} [$options.emailSubject] Fill this if you want the email subject to be automatically filled in * @param {string} [$options.emailBody] Fill this if you want the email body to be automatically filled in * @param {boolean|string} [$options.sms] Pass true here to allow texting the primary verified mobile number, if any. Or pass the string label for this button. * @param {boolean|string} [$options.call] Pass true here to allow calling the primary verified mobile number, if any. Or pass the string label for this button. * @param {string} [$options.tag] The type of tag to use, defaults to "button" * @param {string} [$options.class] Any classes to add to the tags * @param {string} [$options.between] Any HTML to put between the elements */ function Users_getintouch_tool($options) { $tag = 'button'; $class = null; $between = ''; $user = null; $emailSubject = ''; $emailBody = ''; extract($options, EXTR_IF_EXISTS); if (!$user) { throw new Q_Exception_RequiredField(array('field' => 'user')); } if (is_string($user)) { $userId = $user; $user = Users_User::fetch($userId); if (!$user) { throw new Q_Exception_MissingRow(array('table' => 'user', 'criteria' => "id={$userId}")); } } $ways = array(); $email = $sms = $call = false; if (!empty($options['email']) and $user->emailAddress) { $email = is_string($options['email']) ? $options['email'] : "Email me"; $email = Q_Html::img("plugins/Users/img/email.png") . $email; $ways['email'] = Q_Html::tag($tag, array('id' => 'email', 'class' => $class), $email); Q_Response::setToolOptions(array('emailAddress' => Q_Utils::obfuscate($user->emailAddress), 'emailSubject' => Q_Utils::obfuscate($emailSubject), 'emailBody' => Q_Utils::obfuscate($emailBody))); } if (Q_Request::isMobile()) { $obfuscated_mobileNumber = Q_Utils::obfuscate($user->mobileNumber); if (!empty($options['sms']) and $user->mobileNumber) { $sms = is_string($options['sms']) ? $options['sms'] : "Text me"; $sms = Q_Html::img("plugins/Users/img/sms.png") . $sms; $ways['sms'] = Q_Html::tag($tag, array('id' => 'sms', 'class' => $class), $sms); Q_Response::setToolOptions(array('mobileNumber' => $obfuscated_mobileNumber)); } if (!empty($options['call']) and $user->mobileNumber) { $call = is_string($options['call']) ? $options['call'] : "Call me"; $call = Q_Html::img("plugins/Users/img/call.png") . $call; $ways['call'] = Q_Html::tag($tag, array('id' => 'call', 'class' => $class), $call); Q_Response::setToolOptions(array('mobileNumber' => $obfuscated_mobileNumber)); } } return implode($between, $ways); }
function Streams_0_8_1_Streams_mysql() { $app = Q_Config::expect('Q', 'app'); // template for community stream $stream = new Streams_Stream(); $stream->publisherId = ''; $stream->name = 'Streams/community/'; $stream->type = 'Streams/template'; $stream->title = "Community"; $stream->content = ''; $readLevel = Streams::$READ_LEVEL['content']; $writeLevel = Streams::$WRITE_LEVEL['join']; $adminLevel = Streams::$ADMIN_LEVEL['invite']; $stream->save(); // app community stream, for announcements Streams::create($app, $app, 'Streams/community', array('skipAccess' => true, 'name' => 'Streams/community/main', 'title' => "{$app} Community")); // symlink the labels folder if (!file_exists('Streams')) { Q_Utils::symlink(STREAMS_PLUGIN_FILES_DIR . DS . 'Streams' . DS . 'icons' . DS . 'labels' . DS . 'Streams', USERS_PLUGIN_FILES_DIR . DS . 'Users' . DS . 'icons' . DS . 'Streams'); } }
/** * 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); }
function Users_after_Q_session_write($params) { Q::$state['session'] = true; if (!$params['changed']) { return; } // Q::autoload('Db'); // Q::autoload('Db_Mysql'); // Q::autoload('Db_Result'); // Q::autoload('Db_Expression'); // Q::autoload('Db_Query'); // Q::autoload('Db_Query_Mysql'); // Q::autoload('Db_Row'); // Q::autoload('Base_Users_Session'); // Q::autoload('Base_Users'); // Q::autoload('Users'); Q::autoload('Q_Utils'); Q::autoload('Q_Config'); Q::autoload('Q_Session'); $id = Q_Session::id(); if (!$id) { return; } $parts = explode('-', $id); $duration = count($parts) > 1 ? $parts[0] : 0; $content = Q::json_encode($_SESSION, JSON_FORCE_OBJECT); if (Users::$loggedOut) { Q_Utils::sendToNode(array("Q/method" => "Users/session", "sessionId" => $id, "content" => null, "duration" => $duration)); } else { if (Q_Session::id() and !empty($_SERVER['HTTP_HOST'])) { try { Q_Utils::sendToNode(array("Q/method" => "Users/session", "sessionId" => $id, "content" => $content, "duration" => $duration)); } catch (Exception $e) { // don't throw here, it would only result in a mysterious fatal error } } } }
function Users_after_Q_image_save($params, &$return) { extract($params); /** * @var string $path * @var string $subpath * @var Users_User $user */ $user = Users::loggedInUser(true); $fullpath = $path . ($subpath ? DS . $subpath : ''); $splitId = Q_Utils::splitId($user->id); $prefix = "uploads/Users/{$splitId}/icon"; if (substr($fullpath, 0, strlen($prefix)) === $prefix) { if ($user->icon != $subpath) { $user->icon = Q_Html::themedUrl("{$path}/{$subpath}"); $user->save(); // triggers any registered hooks Users::$cache['iconUrlWasChanged'] = true; } else { Users::$cache['iconUrlWasChanged'] = false; } } }
function Users_after_Q_session_destroy($params) { Q::$state['session'] = true; // Q::autoload('Db'); // Q::autoload('Db_Mysql'); // Q::autoload('Db_Result'); // Q::autoload('Db_Expression'); // Q::autoload('Db_Query'); // Q::autoload('Db_Query_Mysql'); // Q::autoload('Db_Row'); // Q::autoload('Base_Users_Session'); // Q::autoload('Base_Users'); // Q::autoload('Users'); Q::autoload('Q_Utils'); Q::autoload('Q_Config'); Q::autoload('Q_Session'); $id = Q_Session::id(); if (!$id) { return; } $content = Q::json_encode($_SESSION, JSON_FORCE_OBJECT); Q_Utils::sendToNode(array("Q/method" => "Users/session", "sessionId" => $id, "content" => null, "updatedTime" => null, "destroyed" => true)); }
/** * Return colored text that you can output in logs or text mode * Pass an exception or * @param {string|Exception} $exception The exception or an exception message. If the later, you must pass three more arguments. * @param {string} [$file] * @param {string} [$line] * @param {string} [$trace] * @return {string} */ static function coloredString($message, $file = null, $line = null, $trace = null) { if ($message instanceof Exception) { $e = $message; $traceString = is_callable(array($e, 'getTraceAsStringEx')) ? $e->getTraceAsStringEx() : $e->getTraceAsString(); return self::coloredString($e->getMessage(), $e->getFile(), $e->getLine(), $traceString); } $colors = Q_Config::get('Q', 'exception', 'colors', array()); Q::autoload('Q_Utils'); $fields = array('message' => $message, 'fileAndLine' => "in {$file} ({$line})", 'trace' => $trace); foreach ($fields as $f => $v) { $c0 = isset($colors[$f][0]) ? $colors[$f][0] : null; $c1 = isset($colors[$f][1]) ? $colors[$f][1] : null; $fields[$f] = Q_Utils::colored($v, $c0, $c1); } $reset = Q_Utils::colored("", "", ""); return "{$fields['message']}\n\n{$fields['fileAndLine']}\n{$fields['trace']}\n"; }
function resendActivationMessage($subject = null, $view = null, $fields = array(), $options = array()) { if (!isset($subject)) { $subject = Q_Config::get('Users', 'transactional', 'resend', 'subject', Q_Config::get('Users', 'transactional', 'activation', 'subject', 'Did you forget your passphrase?')); } if (!isset($view)) { $view = Q_Config::get('Users', 'transactional', 'resend', 'body', Q_Config::get('Users', 'transactional', 'activation', 'body', 'Users/email/activation.php')); } if (!isset($options['html'])) { $options['html'] = true; } $user = $this->get('user', null); if (!$user) { $user = new Users_User(); $user->id = $this->userId; if (!$user->retrieve()) { throw new Q_Exception_NotVerified(array('type' => 'email address'), 'emailAddress'); } } $minutes = Q_Config::get('Users', 'activation', 'expires', 60 * 24 * 7); $this->activationCode = strtolower(Q_Utils::unique(7)); $this->activationCodeExpires = new Db_Expression("CURRENT_TIMESTAMP + INTERVAL {$minutes} MINUTE"); $this->authCode = md5(microtime() + mt_rand()); $link = 'Users/activate?p=1&code=' . urlencode($this->activationCode) . ' emailAddress=' . urlencode($this->address); /** * @event Users/resend {before} * @param {string} user * @param {string} email */ Q::event('Users/resend', compact('user', 'email', 'link'), 'before'); $this->save(); $email = $this; $fields2 = array_merge($fields, array('user' => $user, 'email' => $this, 'app' => Q_Config::expect('Q', 'app'), 'baseUrl' => Q_Request::baseUrl(), 'link' => $link)); $this->sendMessage($subject, $view, $fields2, $options); // may throw exception if badly configured /** * @event Users/resend {after} * @param {string} user * @param {string} email */ Q::event('Users/resend', compact('user', 'email'), 'after'); }
/** * @method links * @static * @param {array} $contact_info An array of key => value pairs, where keys can be: * * * "email" => the user's email address * * "mobile" => the user's mobile number * * "email_hashed" => the standard hash of the user's email address * * "mobile_hashed" => the standard hash of the user's mobile number * * "facebook" => the user's facebook uid * * "twitter" => the user's twitter uid * * @return {array} * Returns an array of all links to this user's contact info */ static function links($contact_info) { $links = array(); $identifiers = array(); if (!empty($contact_info['email'])) { Q_Valid::email($contact_info['email'], $emailAddress); $identifiers[] = "email_hashed:" . Q_Utils::hash($emailAddress); } if (!empty($contact_info['mobile'])) { Q_Valid::phone($contact_info['mobile'], $mobileNumber); $identifiers[] = "mobile_hashed:" . Q_Utils::hash($mobileNumber); } if (!empty($contact_info['email_hashed'])) { $identifiers[] = "email_hashed" . $contact_info['email_hashed']; } if (!empty($contact_info['mobile_hashed'])) { $identifiers[] = "mobile_hashed:" . $contact_info['mobile_hashed']; } if (!empty($contact_info['facebook'])) { $identifiers[] = "facebook:" . $contact_info['facebook']; } if (!empty($contact_info['twitter'])) { $identifiers[] = "twitter:" . $contact_info['twitter']; } return Users_Link::select('*')->where(array('identifier' => $identifiers))->fetchDbRows(); }
function Users_before_Q_Utils_canWriteToPath($params, &$result) { extract($params); /** * @var $path * @var $throwIfNotWritable * @var $mkdirIfMissing */ // The Users plugin requires that a user be logged in before uploading a file, // and only in the proper directories. $user = Users::loggedInUser($throwIfNotWritable); if (!$user) { return false; } $app = Q_Config::expect('Q', 'app'); $subpaths = Q_Config::get('Users', 'paths', 'uploads', array('files/{{app}}/uploads/Users/{{userId}}' => true)); $paths = array(); $path = str_replace(array("/", "\\"), DS, $path); foreach ($subpaths as $subpath => $can_write) { if (!$can_write) { continue; } $subpath = Q::interpolate($subpath, array('userId' => Q_Utils::splitId($user->id), 'app' => $app)); if ($subpath and ($subpath[0] !== '/' or $subpath[0] !== DS)) { $subpath = DS . $subpath; } $last_char = substr($subpath, -1); if ($subpath and $last_char !== '/' and $last_char !== DS) { $subpath .= DS; } $paths[] = APP_DIR . $subpath; foreach (Q_Config::get('Q', 'plugins', array()) as $plugin) { $c = strtoupper($plugin) . '_PLUGIN_DIR'; if (defined($c)) { $paths[] = constant($c) . $subpath; } } $paths[] = Q_DIR . $subpath; } if (strpos($path, "../") === false and strpos($path, ".." . DS) === false) { foreach ($paths as $p) { $p = str_replace(array("/", "\\"), DS, $p); $len = strlen($p); if (strncmp($path, $p, $len) === 0) { // we can write to this path if ($mkdirIfMissing and !file_exists($path)) { $mode = is_integer($mkdirIfMissing) ? $mkdirIfMissing : 0777; $mask = umask(Q_Config::get('Q', 'internal', 'umask', 00)); if (!@mkdir($path, $mode, true)) { throw new Q_Exception_FilePermissions(array('action' => 'create', 'filename' => $path, 'recommendation' => ' Please set your files directory to be writable.')); } umask($mask); $dir3 = $path; do { chmod($dir3, $mode); $dir3 = dirname($dir3); } while ($dir3 and $dir3 != $p and $dir3 . DS != $p); } $result = true; return; } } } if ($throwIfNotWritable) { throw new Q_Exception_CantWriteToPath(); } $result = false; }
/** * Unsubcsribe from all or specific stream's messages * @method unsubscribe * @param $options=array() {array} * "userId": The user who is unsubscribing from the stream. Defaults to the logged-in user. * "skipAccess": if true, skip access check for whether user can unsubscribe * @return {boolean} */ function unsubscribe($options = array()) { $stream = $this->fetchAsUser($options, $userId); if (empty($options['skipAccess']) and !$stream->testReadLevel('messages')) { if (!$stream->testReadLevel('see')) { throw new Streams_Exception_NoSuchStream(); } throw new Users_Exception_NotAuthorized(); } $participant = $stream->join(array("userId" => $userId, 'subscribed' => false, 'noVisit' => true, "skipAccess" => Q::ifset($options, 'skipAccess', false))); Q_Utils::sendToNode(array("Q/method" => "Streams/Stream/unsubscribe", "stream" => Q::json_encode($stream->toArray()), "participant" => Q::json_encode($participant), "success" => Q::json_encode(!!$participant))); // Post Streams/unsubscribe message to the stream $stream->post($userId, array('type' => 'Streams/unsubscribe'), true); // Now post Streams/unsubscribed message to Streams/participating Streams_Message::post($userId, $userId, 'Streams/participating', array('type' => 'Streams/unsubscribed', 'instructions' => Q::json_encode(array('publisherId' => $stream->publisherId, 'streamName' => $stream->name))), true); return !!$participant; }
/** * Sends asynchronous internal message to Node.js * If "Q.clientId" is in $_REQUEST, adds it into the data * @method sendToNode * @static * @param {array} $data Associative array of data of the message to send. * It should contain the key "Q/method" so Node can decide what to do with the message. * @param {string|array} [$url=null] and url to query. Default to 'Q/nodeInternal' config value and path '/Q/node' * @param {boolean} [$throwIfRefused=false] Pass true here to throw an exception whenever Node process is not running or refuses the request */ static function sendToNode($data, $url = null, $throwIfRefused = false) { if (!is_array($data)) { throw new Q_Exception_WrongType(array('field' => 'data', 'type' => 'array')); } if (empty($data['Q/method'])) { throw new Q_Exception_RequiredField(array('field' => 'Q/method')); } $ssid = Q_Request::special('clientId', null); if (isset($ssid)) { $data['Q.clientId'] = $ssid; } // The following hook may modify the url /** * @event Q/Utils/sendToNode {before} * @param {array} data * @param {string|array} 'url' */ Q::event('Q/Utils/sendToNode', array('data' => $data, 'url' => $url), 'before'); if (!$url) { $nodeh = Q_Config::get('Q', 'nodeInternal', 'host', null); $nodep = Q_Config::get('Q', 'nodeInternal', 'port', null); $url = $nodep && $nodeh ? "http://{$nodeh}:{$nodep}/Q/node" : false; } if (!$url) { $result = false; } else { // Should we switch to sending JSON over TCP? $result = Q_Utils::postAsync($url, self::sign($data), null, Q_UTILS_INTERNAL_TIMEOUT, $throwIfRefused); } return $result; // if (!$result) { // throw new Q_Exception_SendingToNode(array('method' => $data['Q/method'])); // } }
/** * 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'); }