function Streams_stream_response_Q_inplace() { $stream = isset(Streams::$cache['stream']) ? Streams::$cache['stream'] : null; if (!$stream) { throw new Exception("No stream"); } if (isset($_REQUEST['title'])) { $result = $stream->title; } else { if (isset($_REQUEST['attributes'])) { if (is_array($_REQUEST['attributes'])) { reset($_REQUEST['attributes']); $result = $stream->getAttribute(key($_REQUEST['attributes'])); } else { $result = $stream->attributes; } } else { $fieldNames = array_diff(Streams::getExtendFieldNames($stream->type), array('insertedTime', 'updatedTime')); $field = 'content'; foreach ($fieldNames as $f) { if (isset($_REQUEST[$f])) { $field = $f; break; } } $result = $stream->{$field}; } } $convert = Q::ifset($_REQUEST, 'convert', '["\\n"]'); return Q_Html::text($result, json_decode($convert, true)); }
/** * Used by HTTP clients to create a new stream in the system. * @class Streams-stream * @method post * @param {array} [$params] Parameters that can come from the request * @param {string} $params.publisherId Required. The id of the user to publish the stream. * @param {string} $params.type Required. The type of the stream. * @param {string} [$params.Q_Streams_related_publisherId] Optionally indicate the publisher of the stream to relate the newly created to. Used together with the related.streamName option. * @param {string} [$params.Q_Streams_related_streamName] Optionally indicate the name of a stream to relate the newly crated stream to. This is often necessary in order to obtain permissions to create the stream. * @param {bool} [$params.dontSubscribe=false] Pass 1 or true here in order to skip auto-subscribing to the newly created stream. * @param {array} [$params.icon] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.icon.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.icon.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.icon.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.icon.merge=""] path under web dir for an optional image to use as a background * @param {string} [$params.icon.crop] array with keys "x", "y", "w", "h" to crop the original image * @param {string} [$params.icon.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 {array} [$params.file] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.file.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.file.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.file.subpath=""] subpath that should follow the path, to save the file under * @param {string} [$params.file.name] override name of the file, after the subpath */ function Streams_stream_post($params = array()) { $user = Users::loggedInUser(true); $publisherId = Streams::requestedPublisherId(); if (empty($publisherId)) { $publisherId = $_REQUEST['publisherId'] = $user->id; } $req = array_merge($_REQUEST, $params); $type = Streams::requestedType(true); $types = Q_Config::expect('Streams', 'types'); if (!array_key_exists($type, $types)) { throw new Q_Exception("This app doesn't support streams of type {$type}", 'type'); } if (empty($types[$type]['create'])) { throw new Q_Exception("This app doesn't support directly creating streams of type {$type}", 'type'); } // Should this stream be related to another stream? $relate = array(); $relate['streamName'] = Q_Request::special("Streams.related.streamName", null, $req); if (isset($relate['streamName'])) { $relate['publisherId'] = Q_Request::special("Streams.related.publisherId", $publisherId, $req); $relate['type'] = Q_Request::special("Streams.related.type", "", $req); $relate['weight'] = "+1"; // TODO: introduce ways to have "1" and "+1" for some admins etc. } // Hold on to any icon that was posted $icon = null; if (!empty($req['icon']) and is_array($req['icon'])) { $icon = $req['icon']; unset($req['icon']); } // Hold on to any file that was posted $file = null; if (!empty($req['file']) and is_array($req['file'])) { $file = $req['file']; unset($req['file']); } // Check if client can set the name of this stream if (!empty($req['name'])) { if ($user->id !== $publisherId or !Q_Config::get('Streams', 'possibleUserStreams', $req['name'], false)) { throw new Users_Exception_NotAuthorized(); } } // Create the stream $allowedFields = array_merge(array('publisherId', 'type', 'icon', 'file'), Streams::getExtendFieldNames($type, $user->id === $publisherId)); $fields = Q::take($req, $allowedFields); $stream = Streams::create($user->id, $publisherId, $type, $fields, $relate, $result); Q_Response::setSlot('messageTo', $result['messageTo']->exportArray()); // Process any icon that was posted if ($icon === true) { $icon = array(); } if (is_array($icon)) { if (empty($icon['path'])) { $icon['path'] = 'uploads/Streams'; } if (empty($icon['subpath'])) { $icon['subpath'] = "{$publisherId}/{$stream->name}/icon/" . time(); } Q_Response::setSlot('icon', Q::event("Q/image/post", $icon)); } // Process any file that was posted if ($file === true) { $file = array(); } if ($file) { if (empty($file['path'])) { $file['path'] = 'uploads/Streams'; } if (empty($file['subpath'])) { $file['subpath'] = "{$publisherId}/{$stream->name}/file/" . time(); } Q_Response::setSlot('file', Q::event("Q/file/post", $file)); } $file = Q::ifset($fieldNames, 'file', null); if (is_array($file)) { unset($fieldNames['file']); Q_Response::setSlot('file', Q::event("Q/file/post", $icon)); } // Re-fetch the stream object from the Streams::fetch cache, // since it might have been retrieved and modified to be different // from what is currently in $stream. // This also calculates the access levels on the stream. $stream = Streams::fetchOne($user->id, $publisherId, $stream->name); if (empty($req['dontSubscribe'])) { // autosubscribe to streams you yourself create, using templates $stream->subscribe(); } Streams::$cache['stream'] = $stream; }
/** * Take actions to reflect the stream has changed: save it and post a message. * @method post * @param {string} [$asUserId=null] * The user to post as. Defaults to the logged-in user. * @param {string} [$messageType='Streams/changed'] * The type of the message. * @param {array} [$fieldNames=null] * The names of the fields to check for changes. * By default, checks all the standard stream fields. * @return {array} * The array of results - successfully posted messages or false if post failed */ function changed($asUserId = null, $messageType = 'Streams/changed', $fieldNames = null) { if (!isset($asUserId)) { $asUserId = Users::loggedInUser(); if (!$asUserId) { $asUserId = ""; } } if ($asUserId instanceof Users_User) { $asUserId = $asUserId->id; } if (!isset($fieldNames)) { $fieldNames = Streams::getExtendFieldNames($this->type, true); } $coreFields = array('title', 'icon', 'content', 'attributes', 'readLevel', 'writeLevel', 'adminLevel', 'inheritAccess', 'closedTime'); $original = $this->fieldsOriginal; $changes = array(); foreach ($fieldNames as $f) { if (!isset($this->{$f}) and !isset($original[$f])) { continue; } $v = $this->{$f}; if (isset($original[$f]) and json_encode($original[$f]) === json_encode($v)) { continue; } $changes[$f] = in_array($f, $coreFields) ? $v : null; // record a change but the value may be too big, etc. } unset($changes['updatedTime']); if (!$changes) { return false; // we found no reason to update the stream in the database } $result = $this->save(); $this->post($asUserId, array('type' => $messageType, 'content' => '', 'instructions' => compact('changes')), true); return $result; }
/** * 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; }
/** * Used to update an existing stream * * @param string $params Must include "publisherId" as well as "name" or "streamName". * Can also include 'type', 'title', 'icon', 'content', 'attributes', 'readLevel', * 'writeLevel', 'adminLevel', as well as any fields named in the * 'Streams'/'types'/$type/'fields' config field for this $type of stream. * @param {string} [$params.publisherId] The id of the user publishing the stream * @param {string} [$params.name] The name of the stream * @param {string} [$params.streamName] Alternatively, the name of the stream * @param {array} [$params.attributes] Array of attributeName => value to set in stream. * @param {array} [$params.icon] Optional array of icon data (see Q_Image::save params) * @return {} */ function Streams_stream_put($params) { // only logged in user can edit stream $user = Users::loggedInUser(true); $publisherId = Streams::requestedPublisherId(); if (empty($publisherId)) { $publisherId = $_REQUEST['publisherId'] = $user->id; } $name = Streams::requestedName(true); $req = array_merge($_REQUEST, $params); $closedTime = Q::ifset($req, 'closedTime', null); if (in_array($closedTime, array(false, 'false', 'null'))) { $req['closedTime'] = null; } // do not set stream name $stream = Streams::fetchOne($user->id, $publisherId, $name); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "{publisherId: '{$publisherId}', name: '{$name}'}")); } // valid stream types should be defined in config by 'Streams/type' array $range = Q_Config::expect('Streams', 'types'); if (!array_key_exists($stream->type, $range)) { throw new Q_Exception("This app doesn't support streams of type " . $stream->type); } // check if editing directly from client is allowed if (!Q_Config::get("Streams", "types", $stream->type, 'edit', false)) { throw new Q_Exception("This app doesn't support directly editing streams of type '{$stream->type}'"); } $suggest = false; if ($stream->publisherId != $user->id) { $stream->calculateAccess($user->id); if (!$stream->testWriteLevel('edit')) { if ($stream->testWriteLevel('suggest')) { $suggest = true; } else { throw new Users_Exception_NotAuthorized(); } } } $restricted = array('readLevel', 'writeLevel', 'adminLevel', 'inheritAccess', 'closedTime'); $owned = $stream->testAdminLevel('own'); // owners can reopen streams foreach ($restricted as $r) { if (isset($req[$r]) and !$owned) { throw new Users_Exception_NotAuthorized(); } } // handle setting of attributes if (isset($req['attributes']) and is_array($req['attributes'])) { foreach ($req['attributes'] as $k => $v) { $stream->setAttribute($k, $v); } unset($req['attributes']); } // Get all the extended field names for this stream type $fieldNames = Streams::getExtendFieldNames($stream->type); // Process any icon that was posted $icon = Q::ifset($fieldNames, 'icon', null); if (is_array($icon)) { unset($fieldNames['icon']); Q_Response::setSlot('icon', Q::event("Q/image/post", $icon)); } // Process any file that was posted $file = Q::ifset($fieldNames, 'file', null); if (is_array($file)) { unset($fieldNames['file']); Q_Response::setSlot('file', Q::event("Q/file/post", $icon)); } if (!empty($fieldNames)) { foreach ($fieldNames as $f) { if (array_key_exists($f, $req)) { $stream->{$f} = $req[$f]; } } $stream->changed($user->id, $suggest ? 'Streams/suggest' : 'Streams/changed'); } if (!empty($req['join'])) { $stream->join(); } Streams::$cache['stream'] = $stream; }
/** * Used by HTTP clients to create a new stream in the system. * @class HTTP Streams stream * @method post * @param {array} [$params] Parameters that can come from the request * @param {string} $params.publisherId Required. The id of the user to publish the stream. * @param {string} $params.type Required. The type of the stream. * @param {string} [$params.Q_Streams_related_publisherId] Optionally indicate the publisher of the stream to relate the newly created to. Used together with the related.streamName option. * @param {string} [$params.Q_Streams_related_streamName] Optionally indicate the name of a stream to relate the newly crated stream to. This is often necessary in order to obtain permissions to create the stream. * @param {bool} [$params.dontSubscribe=false] Pass 1 or true here in order to skip auto-subscribing to the newly created stream. * @param {array} [$params.icon] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.icon.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.icon.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.icon.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.icon.merge=""] path under web dir for an optional image to use as a background * @param {string} [$params.icon.crop] array with keys "x", "y", "w", "h" to crop the original image * @param {string} [$params.icon.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 {array} [$params.file] This is used to upload a custom icon for the stream which will then be saved in different sizes. See fields for Q/image/post method * @param {string} [$params.file.data] Required if $_FILES is empty. Base64-encoded data URI - see RFC 2397 * @param {string} [$params.file.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.file.subpath=""] subpath that should follow the path, to save the file under * @param {string} [$params.file.name] override name of the file, after the subpath */ function Streams_stream_post($params = array()) { $user = Users::loggedInUser(true); $publisherId = Streams::requestedPublisherId(); if (empty($publisherId)) { $publisherId = $_REQUEST['publisherId'] = $user->id; } $req = array_merge($_REQUEST, $params); $type = Streams::requestedType(true); $types = Q_Config::expect('Streams', 'types'); if (!array_key_exists($type, $types)) { throw new Q_Exception("This app doesn't support streams of type {$type}", 'type'); } $create = Streams_Stream::getConfigField($type, 'create', false); if (!$create) { throw new Q_Exception("This app doesn't let clients directly create streams of type {$type}", 'type'); } // Should this stream be related to another stream? $relate = array(); $relate['streamName'] = Q_Request::special("Streams.related.streamName", null, $req); if (isset($relate['streamName'])) { $relate['publisherId'] = Q_Request::special("Streams.related.publisherId", $publisherId, $req); $relate['type'] = Q_Request::special("Streams.related.type", "", $req); $relate['weight'] = "+1"; // TODO: introduce ways to have "1" and "+1" for some admins etc. } // Split the id for saving files in the filesystem $splitId = Q_Utils::splitId($publisherId); // Hold on to any icon that was posted $icon = null; if (!empty($req['icon']) and is_array($req['icon'])) { $icon = $req['icon']; unset($req['icon']); } // Hold on to any file that was posted $file = null; if (!empty($req['file']) and is_array($req['file'])) { $file = $req['file']; unset($req['file']); } // Check if the user owns the stream if ($user->id === $publisherId) { $asOwner = true; } else { $streamTemplate = Streams_Stream::getStreamTemplate($publisherId, $type, 'Streams_Stream'); $asOwner = $streamTemplate ? $streamTemplate->testAdminLevel('own') : false; } // Check if client can set the name of this stream if (isset($req['name'])) { $possible = Q_Config::get('Streams', 'possibleUserStreams', $req['name'], false); if (!$asOwner or !$possible) { throw new Users_Exception_NotAuthorized(); } } // Get allowed fields $allowedFields = array_merge(array('publisherId', 'name', 'type', 'icon', 'file'), Streams::getExtendFieldNames($type, $asOwner)); $fields = Q::take($req, $allowedFields); // Prevent setting restricted fields if (is_array($create)) { $restrictedFields = array_diff($allowedFields, $create); foreach ($restrictedFields as $fieldName) { if (in_array($fieldName, array('publisherId', 'type'))) { continue; } if (isset($req[$fieldName])) { throw new Users_Exception_NotAuthorized(); } } } // Create the stream $stream = Streams::create($user->id, $publisherId, $type, $fields, $relate, $result); $messageTo = false; if (isset($result['messagesTo'])) { $messageTo = reset($result['messagesTo']); $messageTo = reset($messageTo); if (is_array($messageTo)) { $messageTo = reset($messageTo); } $messageTo = $messageTo->exportArray(); } Q_Response::setSlot('messageTo', $messageTo); // Process any icon that was posted if ($icon === true) { $icon = array(); } if (is_array($icon)) { if (empty($icon['path'])) { $icon['path'] = 'uploads/Streams'; } if (empty($icon['subpath'])) { $icon['subpath'] = "{$splitId}/{$stream->name}/icon/" . time(); } Q_Response::setSlot('icon', Q::event("Q/image/post", $icon)); // the Streams/after/Q_image_save hook saves some attributes } // Process any file that was posted if ($file === true) { $file = array(); } if (is_array($file)) { if (empty($file['path'])) { $file['path'] = 'uploads/Streams'; } if (empty($file['subpath'])) { $file['subpath'] = "{$splitId}/{$stream->name}/file/" . time(); } Q_Response::setSlot('file', Q::event("Q/file/post", $file)); // the Streams/after/Q_file_save hook saves some attributes } // Re-fetch the stream object from the Streams::fetch cache, // since it might have been retrieved and modified to be different // from what is currently in $stream. // This also calculates the access levels on the stream. $stream = Streams::fetchOne($user->id, $publisherId, $stream->name); if (empty($req['dontSubscribe'])) { // autosubscribe to streams you yourself create, using templates $stream->subscribe(); } Streams::$cache['stream'] = $stream; }