/** * Provide player content to view the members of category listing * Uses Streams/$type/category.php view (Streams/$streamType/category/get.php can be used for viewing the category * stream itself if type of category is $streamType/category) * and Streams::related to retrieve streams data * **/ function Streams_category_response_player() { $user = Users::loggedInUser(); $userId = $user ? $user->id : 0; // These are PK of the category! $publisherId = Streams::requestedPublisherId(true); $name = Streams::requestedName(true); // need to know publisher and type of the streams to list $streamType = Streams::requestedType(); if ($streamType) { $prefix = "{$streamType}/"; } $stream_publisherId = Q::expect('Streams', $streamType, 'publisher'); if (substr($name, -1) === '/') { throw new Q_Exception("Player cannot show listing for multiple categories", compact('publisherId', 'name')); } /* * Get shall return only streams which user is authorized to see. */ $categories = Streams::fetch($userId, $publisherId, $name); if (empty($categories)) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => compact('publisherId', 'name'))); } $category = reset($categories); // Are you authorized to see category content? if (!$category->testReadLevel('content')) { throw new Users_Exception_NotAuthorized(); } // get all the streams which are members of this category // as Streams::get verifies access rights, it's safe to show all streams' content list($relations, $streams) = Streams::related($userId, $publisherId, $name, true, array('prefix' => $prefix, 'skipAccess' => true)); Q::view("Stream/{$type}/category.php", compact('relations', 'streams', 'userId')); }
function Streams_invite_response_data() { if (isset(Streams::$cache['invited'])) { return Streams::$cache['invited']; } $user = Users::loggedInUser(true); $publisherId = Streams::requestedPublisherId(); $streamType = Streams::requestedType(); $invitingUserId = Streams::requestedField('invitingUserId'); $limit = Q::ifset($_REQUEST, 'limit', Q_Config::get('Streams', 'invites', 'limit', 100)); $invited = Streams_Invited::select('*')->where(array('userId' => $user->id, 'state' => 'pending', 'expireTime <' => new Db_Expression('CURRENT_TIMESTAMP')))->limit($limit)->fetchDbRows(null, null, 'token'); $query = Streams_Invite::select('*')->where(array('token' => array_keys($invited))); if (isset($publisherId)) { $query = $query->where(array('publisherId' => $publisherId)); } if (isset($streamType)) { $query = $query->where(array('streamName' => new Db_Range($streamType . '/', true, false, true))); } if (isset($invitingUserId)) { $query = $query->where(array('invitingUserId' => $invitingUserId)); } $invites = $query->fetchDbRows(); $streams = array(); foreach ($invites as $invite) { $stream = new Streams_Stream(); $stream->publisherId = $invite->publisherId; $stream->name = $invite->streamName; if ($stream->retrieve()) { $streams[$invite->token] = $stream->exportArray(); $streams[$invite->token]['displayName'] = $invite->displayName; } } return compact('streams', 'invites'); }
function Streams_publisher_validate($params) { // Protect against CSRF attacks: Q_Valid::nonce(true); $type = Streams::requestedType(); if ($type && Q::canHandle("Streams/validate/{$type}")) { return Q::event("Streams/validate/{$type}", $params); } }
function Streams_participating_response() { if (!Q_Request::isAjax()) { return; } $max_limit = Q_Config::expect('Streams', 'db', 'limits', 'participating'); $user = Users::loggedInUser(true); $type = Streams::requestedType(); $limit = Streams::requestedField('limit', false, $max_limit); if ($limit > $max_limit) { throw new Q_Exception("limit is too large, must be <= {$max_limit}"); } $offset = Streams::requestedField('offset', false, 0); $order = Streams::requestedField('order', false, true); $participating = array(); $q = Streams_Participating::select('*')->where(array('userId' => $user->id)); if ($type) { $q = $q->where(array('streamName' => new Db_Range($type . '/', true, false, true))); } if ($limit) { $q = $q->limit($limit, $offset); } if ($order) { $q = $q->orderBy('updatedTime', false); } $res_participating = $q->fetchDbRows(); foreach ($res_participating as $part) { $part_safe = $part->exportArray(); if (isset($part_safe)) { $participating[] = $part_safe; } } Q_Response::setSlot('participating', $participating); if (!Q_Request::slotName('streams')) { return; } $res_streams = array(); $streamNames = array(); foreach ($res_participating as $p) { $streamNames[$p->publisherId][] = $p->streamName; } foreach ($streamNames as $p_id => $names) { $res_streams[$p_id] = Streams::fetch($user->id, $p_id, $names); } $streams = array(); $o = array('asUserId' => $user->id); foreach ($res_streams as $publisherId => $streams_array) { if (!empty($streams_array)) { $streams[$publisherId] = array(); foreach ($streams_array as $streamName => $stream) { $streams[$publisherId][$streamName] = $stream->exportArray($o); } } } Q_Response::setSlot('streams', $streams); }
function Streams_stream_validate($params) { // Protect against CSRF attacks: if (Q_Request::method() !== 'GET') { Q_Valid::nonce(true); } $type = Streams::requestedType(); if ($type && Q::canHandle("Streams/validate/{$type}")) { return Q::event("Streams/validate/{$type}", $params); } }
function Streams_message_tool($options) { extract($options); $user = Users::loggedInUser(); if (!$user) { throw new Users_Exception_NotLoggedIn(); } if (empty($publisherId)) { $publisherId = Streams::requestedPublisherId(); } if (empty($publisherId)) { $publisherId = $_REQUEST['publisherId'] = $user->id; } if (empty($name)) { $name = Streams::requestedName(true); } $stream = Streams::fetch($user->id, $publisherId, $name); $stream = !empty($stream) ? reset($stream) : null; if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name'), 'streamName'); } if (!$stream->testReadLevel('messages') || !$stream->testWriteLevel('post')) { throw new Users_Exception_NotAuthorized(); } $hidden = array('publisherId' => $publisherId, 'streamName' => $name); $fields = array('stream' => array('label' => 'Stream', 'type' => 'static', 'value' => $stream->title)); $type = Streams::requestedType(); // check if stream has messages $types = Q_Config::get('Streams', 'messages', $stream->type, array()); if (count($types) === 0) { throw new Q_Exception("Stream of type '{$stream->type}' does not support messages"); } if (!empty($type) && !in_array($type, $types)) { throw new Q_Exception("Requested message type '{$type}' is not alowed for streams of type '{$stream->type}'"); } if (!empty($type)) { $hidden['type'] = $type; $fields['type'] = array('label' => 'Message type', 'type' => 'static', 'value' => $type); } else { $fields['type'] = array('label' => 'Message type', 'type' => 'select', 'options' => array_merge(array('' => 'Select type'), array_combine($types, $types)), 'value' => ''); } $fields['content'] = array('label' => 'Content', 'type' => 'textarea'); $fields['submit'] = array('label' => '', 'type' => 'submit_buttons', 'options' => array('submit' => 'Post')); return Q_Html::tag('h3', array(), 'Post a message') . Q_Html::form(Q_Request::baseUrl() . '/action.php/Streams/message', 'post', array(), Q_Html::hidden($hidden) . Q::tool('Q/form', array('fields' => $fields, 'onSuccess' => 'function (data) { if (data.errors) alert(data.errors); else { alert("Message posted"); var message = Q.getObject(["slots", "form", "fields"], data); Q.handle(Q.info.baseUrl+"/plugins/Streams/message?publisherId="+message.publisherId+"&name="+message.streamName); } }'))); }
/** * 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; }
/** * 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; }