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; }
/** * 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; }
static function getExtendClasses($type) { static $result = array(); if (isset($result[$type])) { return $result[$type]; } $extend = Q_Config::get('Streams', 'types', $type, 'extend', null); if (is_string($extend)) { $extend = array($extend); } $classes = array(); if ($extend) { if (!Q::isAssociative($extend)) { $temp = array(); foreach ($extend as $k) { $temp[$k] = true; } $extend = $temp; } foreach ($extend as $k => $v) { if (!class_exists($k, true)) { throw new Q_Exception_MissingClass(array('className' => $k)); } if (!is_subclass_of($k, 'Db_Row')) { throw new Q_Exception_BadValue(array('internal' => "Streams/types/{$type}/extend", 'problem' => "{$k} must extend Db_Row")); } if ($v === true) { $v = call_user_func(array('Base_' . $k, 'fieldNames')); $v = array_diff($v, array('publisherId', 'streamName', 'name')); } else { if (Q::isAssociative($v)) { $v = array_keys($v); } } $classes[$k] = $v; } } return $result[$type] = $classes; }
/** * If an array is not associative, then makes an associative array * with the keys taken from the values of the regular array * @param {array} $array * @param {array} [$value=true] The value to assign to each item in the generated array * @return {array} */ static function makeAssociative($array, $value = true) { if (Q::isAssociative($array)) { return $array; } $result = array(); foreach ($array as $item) { $result[$item] = $value; } return $result; }
/** * Sets one of the attributes of a style to a value. * @method setStyle * @static * @param {string|array} $keys Can be a key or array of keys in the style of Q_Tree->set * @param {mixed} [$value=null] * @param {string} [$slotName=null] */ static function setStyle($keys, $value = null, $slotName = null) { if (Q::isAssociative($keys)) { foreach ($keys as $k => $v) { self::setStyle($k, $v, $value); } return; } $args = is_array($keys) ? $keys : array($keys); $args[] = $value; $p = new Q_Tree(self::$styles); call_user_func_array(array($p, 'set'), $args); // Now, for the slot if (!isset($slotName)) { $slotName = isset(self::$slotName) ? self::$slotName : ''; } if (!isset(self::$stylesForSlot[$slotName])) { self::$stylesForSlot[$slotName] = array(); } $p = new Q_Tree(self::$stylesForSlot[$slotName]); call_user_func_array(array($p, 'set'), $args); }
/** * Post (potentially) multiple messages to multiple streams. * @method postMessages * @static * @param {string} $asUserId * The user to post the message as * @param {string} $messages * Array indexed as follows: * array($publisherId => array($streamName => $message)) * where $message are either Streams_Message objects, * or arrays containing all the fields of messages that will need to be posted. * @param {booleam} $skipAccess=false * If true, skips the access checks and just posts the message. * @return {array} * Returns an array(array(Streams_Message), array(Streams_Stream)) */ static function postMessages($asUserId, $messages, $skipAccess = false) { if (!isset($asUserId)) { $asUserId = Users::loggedInUser(); if (!$asUserId) { $asUserId = ""; } } if ($asUserId instanceof Users_User) { $asUserId = $asUserId->id; } // Build arrays we will need foreach ($messages as $publisherId => $arr) { if (!is_array($arr)) { throw new Q_Exception_WrongType(array('field' => "messages", 'type' => 'array of publisherId => streamName => message')); } foreach ($arr as $streamName => &$message) { if (!is_array($message)) { if (!$message instanceof Streams_Message) { throw new Q_Exception_WrongType(array('field' => "message under {$publisherId} => {$streamName}", 'type' => 'array or Streams_Message')); } $message = $message->fields; } } } // Check if there are any messages to post $atLeastOne = false; foreach ($messages as $publisherId => $arr) { foreach ($arr as $streamName => $m) { if (!$m) { continue; } $atLeastOne = true; break 2; } } if (!$atLeastOne) { return array(array(), array()); } // Start posting messages, publisher by publisher $eventParams = array(); $posted = array(); $streams = array(); $messages2 = array(); $totals2 = array(); $updates = array(); $clientId = Q_Request::special('clientId', ''); $sendToNode = true; foreach ($messages as $publisherId => $arr) { $streamNames = array_keys($messages[$publisherId]); $streams[$publisherId] = $fetched = Streams::fetch($asUserId, $publisherId, $streamNames, '*', array('refetch' => true, 'begin' => true)); foreach ($arr as $streamName => $m) { // Get the Streams_Stream object if (!isset($fetched[$streamName])) { $p = new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId {$publisherId} and name {$streamName}")); $updates[$publisherId]['missingRow'][] = $streamName; continue; } $p =& $posted[$publisherId][$streamName]; $p = array(); if (!$m) { $updates[$publisherId]['noMessages'][] = $streamName; continue; } $messages3 = is_array($m) && !Q::isAssociative($m) ? $m : array($m); $count = count($messages3); $updates[$publisherId][$count][] = $streamName; $i = 0; foreach ($messages3 as $message) { ++$i; $type = isset($message['type']) ? $message['type'] : 'text/small'; $content = isset($message['content']) ? $message['content'] : ''; $instructions = isset($message['instructions']) ? $message['instructions'] : ''; $weight = isset($message['weight']) ? $message['weight'] : 1; if (!isset($message['byClientId'])) { $message['byClientId'] = $clientId ? substr($clientId, 0, 255) : ''; } if (is_array($instructions)) { $instructions = Q::json_encode($instructions); } $byClientId = $message['byClientId']; $stream = $fetched[$streamName]; // Make a Streams_Message object $message = new Streams_Message(); $message->publisherId = $publisherId; $message->streamName = $streamName; $message->insertedTime = new Db_Expression("CURRENT_TIMESTAMP"); $message->sentTime = new Db_Expression("CURRENT_TIMESTAMP"); $message->byUserId = $asUserId; $message->byClientId = $byClientId ? substr($byClientId, 0, 31) : ''; $message->type = $type; $message->content = $content; $message->instructions = $instructions; $message->weight = $weight; $message->ordinal = $stream->messageCount + $i; // thanks to transaction // Set up some parameters for the event hooks $params = $eventParams[$publisherId][$streamName][] = array('publisherId' => $publisherId, 'message' => $message, 'skipAccess' => $skipAccess, 'sendToNode' => &$sendToNode, 'stream' => $stream); /** * @event Streams/post/$streamType {before} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message * @return {false} To cancel further processing */ if (Q::event("Streams/post/{$stream->type}", $params, 'before') === false) { $results[$stream->name] = false; continue; } /** * @event Streams/message/$messageType {before} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message * @return {false} To cancel further processing */ if (Q::event("Streams/message/{$type}", $params, 'before') === false) { $results[$stream->name] = false; continue; } if (!$skipAccess && !$stream->testWriteLevel('post')) { $p[] = new Users_Exception_NotAuthorized(); /** * @event Streams/notAuthorized {before} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message */ Q::event("Streams/notAuthorized", $params, 'after'); continue; } // if we are still here, mark the message as "in the database" $message->wasRetrieved(true); $p[] = $message; // build the arrays of rows to insert $messages2[] = $mf = $message->fields; } $totals2[$count][] = array('publisherId' => $mf['publisherId'], 'streamName' => $mf['streamName'], 'messageType' => $mf['type'], 'messageCount' => $count); } } foreach ($totals2 as $count => $rows) { Streams_Total::insertManyAndExecute($rows, array('onDuplicateKeyUpdate' => array('messageCount' => new Db_Expression("messageCount + {$count}")))); } if ($messages2) { Streams_Message::insertManyAndExecute($messages2); } // time to update the stream rows and commit the transaction // on all the shards where the streams were fetched. foreach ($updates as $publisherId => $arr) { foreach ($arr as $count => $streamNames) { $suffix = is_numeric($count) ? " + {$count}" : ''; Streams_Stream::update()->set(array('messageCount' => new Db_Expression('messageCount' . $suffix)))->where(array('publisherId' => $publisherId, 'name' => $streamNames))->commit()->execute(); } } // handle all the events for successfully posting foreach ($posted as $publisherId => $arr) { foreach ($arr as $streamName => $messages3) { $stream = $streams[$publisherId][$streamName]; foreach ($messages3 as $i => $message) { $params = $eventParams[$publisherId][$streamName][$i]; /** * @event Streams/message/$messageType {after} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message */ Q::event("Streams/message/{$message->type}", $params, 'after', false); /** * @event Streams/post/$streamType {after} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} message */ Q::event("Streams/post/{$stream->type}", $params, 'after', false); } } } /** * @event Streams/postMessages {after} * @param {string} publisherId * @param {Streams_Stream} stream * @param {string} posted */ Q::event("Streams/postMessages", array('streams' => $streams, 'messages' => $messages, 'skipAccess' => $skipAccess, 'posted' => $posted), 'after', false); if ($sendToNode) { Q_Utils::sendToNode(array("Q/method" => "Streams/Message/postMessages", "posted" => Q::json_encode($messages2), "streams" => Q::json_encode(Db::exportArray($streams)))); } return array($posted, $streams); }
/** * 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' => ''); if (!Q::isAssociative($save)) { throw new Q_Exception_WrongType(array('field' => 'save', 'type' => 'associative array')); } // 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); imagesavealpha($thumb, true); imagealphablending($thumb, false); $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; }
/** * Method is called before setting the field and verifies that, if it is a string, * it contains a JSON array. * @method beforeSet_permissions * @param {string} $value * @return {array} An array of field name and value * @throws {Exception} An exception is thrown if $value is not string or is exceedingly long */ function beforeSet_permissions($value) { if (is_string($value)) { $decoded = Q::json_decode($value, true); if (!is_array($decoded) or Q::isAssociative($decoded)) { throw new Q_Exception_WrongValue(array('field' => 'permissions', 'range' => 'JSON array')); } } return parent::beforeSet_permissions($value); }