function Streams_0_8_4_Streams_mysql() { $app = Q_Config::expect('Q', 'app'); $communityId = Users::communityId(); $user = Users_User::fetch($communityId); // avatar for the App user $avatar = new Streams_Avatar(); $avatar->toUserId = $communityId; $avatar->publisherId = $communityId; $avatar->username = $user->username; $avatar->firstName = Users::communityName(); $avatar->lastName = Users::communitySuffix(); $avatar->icon = $user->icon; $avatar->save(); $avatar2 = new Streams_Avatar(); $avatar2->copyFrom($avatar, null, false, true); $avatar->toUserId = ''; $avatar->save(); // access stream for managing app roles $stream = new Streams_Stream(); $stream->publisherId = Users::communityId(); $stream->name = 'Streams/contacts'; $stream->type = 'Streams/resource'; $stream->title = "Contacts"; $stream->setAttribute('prefixes', array("Users/", "{$app}/")); $stream->save(); // access stream for managing app roles $stream = new Streams_Stream(); $stream->publisherId = $app; $stream->name = 'Streams/labels'; $stream->type = 'Streams/resource'; $stream->title = "Labels"; $stream->setAttribute('prefixes', array("Users/", "{$app}/")); $stream->save(); // access for managing app contacts $access = new Streams_Access(); $access->publisherId = $communityId; $access->streamName = 'Streams/contacts'; $access->ofUserId = ''; $access->ofContactLabel = "{$app}/admins"; $access->readLevel = Streams::$READ_LEVEL['messages']; $access->writeLevel = Streams::$WRITE_LEVEL['edit']; $access->adminLevel = Streams::$ADMIN_LEVEL['manage']; $access->save(); // access for managing app roles $access = new Streams_Access(); $access->publisherId = $communityId; $access->streamName = 'Streams/labels'; $access->ofUserId = ''; $access->ofContactLabel = "{$app}/admins"; $access->readLevel = Streams::$READ_LEVEL['messages']; $access->writeLevel = Streams::$WRITE_LEVEL['edit']; $access->adminLevel = Streams::$ADMIN_LEVEL['manage']; $access->save(); }
/** * This tool renders a user avatar * * @param {array} $options An associative array of parameters, containing: * @param {string} [$options.userId] * The user's id. Defaults to id of the logged-in user, if any. * Can be '' for a blank-looking avatar. * @param {boolean} [options.short] * Optional. Renders the short version of the display name. * @param {boolean|integer} [options.icon=false] * Optional. Pass the size in pixels of the (square) icon to render * before the username. Or pass true to render the default size. * @param {array} [options.iconAttributes] * Optional. Array of attributes to render for the icon. * @param {boolean|array} [options.editable=false] * Optional. Whether to provide an interface for editing the user's info. Can be array containing one or more of "icon", "name". * @param {boolean} [$options.show] The parts of the name to show. Can have the letters "f", "l", "u" in any order. * @param {boolean} [options.cacheBust=null] * Number of milliseconds to use for Q_Uri::cacheBust for combating unintended caching on some environments. * @param {boolean} [options.renderOnClient] * If true, only the html container is rendered, so the client will do the rest. */ function Users_avatar_tool($options) { $defaults = array('icon' => false, 'short' => false, 'cacheBust' => null, 'editable' => false); $options = array_merge($defaults, $options); Q_Response::addStylesheet('plugins/Users/css/Users.css'); $loggedInUser = Users::loggedInUser(); $loggedInUserId = $loggedInUser ? $loggedInUser->id : ""; if (empty($options['userId'])) { $options['userId'] = $loggedInUserId; } unset($options['iconAttributes']); if (empty($options['editable'])) { $options['editable'] = array(); } else { if (is_string($options['editable'])) { $options['editable'] = array($options['editable']); } else { if ($options['editable'] === true) { $options['editable'] = array('icon', 'name'); } } } Q_Response::setToolOptions($options); if (!empty($options['renderOnClient'])) { return ''; } $avatar = Streams_Avatar::fetch($loggedInUserId, $options['userId']); if (!$avatar) { return ''; } $result = ''; if ($icon = $options['icon']) { if ($icon === true) { $icon = Q_Config::get('Users', 'icon', 'defaultSize', 40); } $attributes = isset($options['iconAttributes']) ? $options['iconAttributes'] : array(); $class = "Users_avatar_icon Users_avatar_icon_{$icon}"; $attributes['class'] = isset($attributes['class']) ? $attributes['class'] . ' ' . $class : $class; if (isset($options['cacheBust'])) { $attributes['cacheBust'] = $options['cacheBust']; } $result .= Q_Html::img(Users::iconUrl($avatar->icon, "{$icon}.png"), 'user icon', $attributes); } $o = $options['short'] ? array('short' => true) : array(); $o['html'] = true; if (in_array('name', $options['editable'])) { $o['show'] = 'fl'; $streams = Streams::fetch(null, $options['userId'], array('Streams/user/firstName', 'Streams/user/lastName', 'Streams/user/username')); foreach ($streams as $s) { $s->addPreloaded(); } } if (!empty($options['show'])) { $o['show'] = $options['show']; } $displayName = $avatar->displayName($o, 'Someone'); $result .= "<span class='Users_avatar_name'>{$displayName}</span>"; return $result; }
function Streams_avatar_response() { $prefix = $limit = $userIds = $batch = $public = null; extract($_REQUEST, EXTR_IF_EXISTS); $user = Users::loggedInUser(); $asUserId = $user ? $user->id : ""; if (isset($prefix)) { $avatars = Streams_Avatar::fetchByPrefix($asUserId, $prefix, compact('limit', 'public')); } else { if (isset($batch)) { $batch = json_decode($batch, true); if (!isset($batch)) { throw new Q_Exception_WrongValue(array('field' => 'batch', 'range' => '{userIds: [userId1, userId2, ...]}')); } if (!isset($batch['userIds'])) { throw new Q_Exception_RequiredField(array('field' => 'userIds')); } $userIds = $batch['userIds']; } if (!isset($userIds)) { throw new Q_Exception_RequiredField(array('field' => 'userIds')); } if (is_string($userIds)) { $userIds = explode(",", $userIds); } $avatars = Streams_Avatar::fetch($asUserId, $userIds); } $avatars = Db::exportArray($avatars); if (isset($batch)) { $result = array(); foreach ($userIds as $userId) { $result[] = array('slots' => array('avatar' => isset($avatars[$userId]) ? $avatars[$userId] : null)); } Q_Response::setSlot('batch', $result); } else { Q_Response::setSlot('avatars', $avatars); } return $avatars; }
function Streams_after_Users_User_saveExecute($params) { // If the username or icon was somehow modified, // update all the avatars for this publisher $modifiedFields = $params['modifiedFields']; $user = $params['row']; $updates = array(); if (isset($modifiedFields['username'])) { $updates['username'] = $modifiedFields['username']; } if (isset($modifiedFields['icon'])) { $updates['icon'] = $modifiedFields['icon']; } if ($user->id === Users::communityId()) { $firstName = Users::communityName(); $lastName = Users::communitySuffix(); $firstName = $firstName ? $firstName : ""; $lastName = $lastName ? $lastName : ""; } else { $firstName = Q::ifset(Streams::$cache, 'register', 'first', ''); $lastName = Q::ifset(Streams::$cache, 'register', 'last', ''); } if ($params['inserted']) { // create some standard streams for them $onInsert = Q_Config::get('Streams', 'onInsert', 'Users_User', array()); if (!$onInsert) { return; } $p = new Q_Tree(); $p->load(STREAMS_PLUGIN_CONFIG_DIR . DS . 'streams.json'); $p->load(APP_CONFIG_DIR . DS . 'streams.json'); $values = array('Streams/user/firstName' => $firstName, 'Streams/user/lastName' => $lastName); // Check for user data from facebook if (!empty(Users::$cache['facebookUserData'])) { $userData = Users::$cache['facebookUserData']; foreach ($userData as $name_fb => $value) { foreach ($p->getAll() as $name => $info) { if (isset($info['name_fb']) and $info['name_fb'] === $name_fb) { $onInsert[] = $name; $values[$name] = $value; } } } } foreach ($onInsert as $name) { $stream = Streams::fetchOne($user->id, $user->id, $name); if (!$stream) { // it shouldn't really be in the db yet $stream = new Streams_Stream(); $stream->publisherId = $user->id; $stream->name = $name; } $stream->type = $p->expect($name, "type"); $stream->title = $p->expect($name, "title"); $stream->content = $p->get($name, "content", ''); // usually empty $stream->readLevel = $p->get($name, 'readLevel', Streams_Stream::$DEFAULTS['readLevel']); $stream->writeLevel = $p->get($name, 'writeLevel', Streams_Stream::$DEFAULTS['writeLevel']); $stream->adminLevel = $p->get($name, 'adminLevel', Streams_Stream::$DEFAULTS['adminLevel']); if ($name === "Streams/user/icon") { $sizes = Q_Config::expect('Users', 'icon', 'sizes'); sort($sizes); $stream->setAttribute('sizes', $sizes); $stream->icon = $user->iconUrl(); } if (isset($values[$name])) { $stream->content = $values[$name]; } $stream->save(); // this also inserts avatars $o = array('userId' => $user->id, 'skipAccess' => true); $so = $p->get($name, "subscribe", array()); if ($so === false) { $stream->join($o); } else { $stream->subscribe(array_merge($o, $so)); } } // Save a greeting stream, to be edited $communityId = Users::communityId(); Streams::create($user->id, $user->id, "Streams/greeting", array('name' => "Streams/greeting/{$communityId}")); // Create some standard labels $label = new Users_Label(); $label->userId = $user->id; $label->label = 'Streams/invited'; $label->icon = 'labels/Streams/invited'; $label->title = 'People I invited'; $label->save(true); $label2 = new Users_Label(); $label2->userId = $user->id; $label2->label = 'Streams/invitedMe'; $label2->icon = 'labels/Streams/invitedMe'; $label2->title = 'Who invited me'; $label2->save(true); // By default, users they invite should see their full name $access = new Streams_Access(); $access->publisherId = $user->id; $access->streamName = 'Streams/user/firstName'; $access->ofUserId = ''; $access->ofContactLabel = 'Streams/invited'; $access->grantedByUserId = $user->id; $access->readLevel = Streams::$READ_LEVEL['content']; $access->writeLevel = -1; $access->adminLevel = -1; $access->save(); $access = new Streams_Access(); $access->publisherId = $user->id; $access->streamName = 'Streams/user/lastName'; $access->ofUserId = ''; $access->ofContactLabel = 'Streams/invited'; $access->grantedByUserId = $user->id; $access->readLevel = Streams::$READ_LEVEL['content']; $access->writeLevel = -1; $access->adminLevel = -1; $access->save(); // NOTE: the above saving of access caused Streams::updateAvatar to run, // insert a Streams_Avatar row for the new user, and properly configure it. } else { if ($modifiedFields) { if ($updates) { Streams_Avatar::update()->set($updates)->where(array('publisherId' => $user->id))->execute(); } foreach ($modifiedFields as $field => $value) { $name = Q_Config::get('Streams', 'onUpdate', 'Users_User', $field, null); if (!$name) { continue; } $stream = isset(Streams::$beingSaved[$field]) ? Streams::$beingSaved[$field] : Streams::fetchOne($user->id, $user->id, $name); if (!$stream) { // it should probably already be in the db continue; } $stream->content = $value; if ($name === "Streams/user/icon") { $sizes = Q_Config::expect('Users', 'icon', 'sizes'); sort($sizes); $attributes = $stream->attributes; $stream->setAttribute('sizes', $sizes); $stream->icon = $changes['icon'] = $user->iconUrl(); } Streams::$beingSavedQuery = $stream->changed($user->id); } } } }
/** * Retrieve avatars for one or more publishers as displayed to a particular user. * * @method fetchByPrefix * @static * @param $toUserId {User_User|string} The id of the user to which this would be displayed * @param $prefix {string} The prefix for the firstName * @param {array} $options=array() * 'limit' => number of records to fetch * 'fields' => defaults to array('username', 'firstName', 'lastName') * 'public' => defaults to false. If true, also gets publicly accessible names. * @return {array} */ static function fetchByPrefix($toUserId, $prefix, $options = array()) { if ($toUserId instanceof Users_User) { $toUserId = $toUserId->id; } $toUserId = empty($options['public']) ? $toUserId : array($toUserId, ''); $fields = isset($options['fields']) ? $options['fields'] : array('firstName', 'lastName', 'username'); $limit = isset($options['limit']) ? $options['limit'] : Q_Config::get('Users', 'Avatar', 'fetchByPrefix', 'limit', 100); $max = $limit; $avatars = array(); $prefixes = preg_split("/\\s+/", $prefix); $prefix = reset($prefixes); $criteria = array(); if (count($prefixes) < 2) { foreach ($fields as $field) { $criteria[] = array($field => new Db_Range($prefix, true, false, true)); } } else { $criteria = array(array('firstName' => new Db_Range($prefixes[0], true, false, true), 'lastName' => new Db_Range($prefixes[1], true, false, true)), array('firstName' => new Db_Range($prefixes[0], true, false, true), 'username' => new Db_Range($prefixes[1], true, false, true)), array('username' => new Db_Range($prefixes[0], true, false, true), 'lastName' => new Db_Range($prefixes[1], true, false, true))); } $count = count($criteria); for ($i = 0; $i < $count; ++$i) { // NOTE: sharding should be done on toUserId only, not publisherId $q = Streams_Avatar::select('*')->where(array('toUserId' => $toUserId))->andWhere($criteria[$i])->orderBy('firstName'); $rows = $q->limit($max)->fetchDbRows(); foreach ($rows as $r) { if (!isset($avatars[$r->publisherId]) or $r->toUserId !== '') { $avatars[$r->publisherId] = $r; } } $max = $limit - count($avatars); if ($max <= 0) { break; } } return $avatars; }
/** * Updates the publisher's avatars, which may have changed with the taintedAccess. * This function should be called during rare events that may cause the * publisher's avatar to change appearance for certain users viewing it.<br/> * * You should rarely have to call this function. It is used internally by the model, * in two main situations: * * 1) adding, removing or modifying a Streams_Access row for Streams/user/firstName or Streams/user/lastName * In this case, the function is able to update exactly the avatars that need updating. * * 2) adding, removing or modifying a Stream row for Streams/user/firstName or Streams/user/lastName * In this case, there may be some avatars which this function will miss. * These correspond to users which are reachable by the access array for one stream, * but not the other. For example, if Streams/user/firstName is being updated, but * a particular user is reachable only by the access array for Streams/user/lastName, then * their avatar will not be updated and contain a stale value for firstName. * To fix this, the Streams_Stream model passes true in the 4th parameter to this function. * @method updateAvatars * @static * @param {string} $publisherId * id of the publisher whose avatar to update * @param {array} $taintedAccess * array of Streams_Access objects representing access information that is either * about to be saved, are about to be overwritten, or will be deleted * @param {string|Streams_Stream} $streamName * pass the stream name here. You can also pass a Stream_Stream object here, * in which case it will be used, instead of selecting that stream from the database. * @param {boolean} $updateToPublicValue=false * if you want to first update all the avatars for this stream * to the what the public would see, to avoid the situation described in 2). */ static function updateAvatars($publisherId, $taintedAccess, $streamName, $updateToPublicValue = false) { if (!isset($streamName)) { $streamAccesses = array(); foreach ($taintedAccess as $access) { $streamAccesses[$access->streamName][] = $access; } if (count($streamAccesses) > 1) { foreach ($streamAccesses as $k => $v) { self::updateAvatars($publisherId, $v, $k); } return false; } } if ($streamName instanceof Streams_Stream) { $stream = $streamName; $streamName = $stream->name; } // If we are here, all the Stream_Access objects have the same streamName if ($streamName !== 'Streams/user/firstName' and $streamName !== 'Streams/user/lastName' and $streamName !== 'Streams/user/username') { // we don't care about access to other streams being updated return false; } $showToUserIds = array(); // Select the user corresponding to this publisher $user = new Users_User(); $user->id = $publisherId; if (!$user->retrieve(null, null, array('ignoreCache' => true))) { throw new Q_Exception_MissingRow(array('table' => 'user', 'criteria' => 'id = ' . $user->id)); } // Obtain the stream object to use if (isset($stream)) { if (!isset($stream->content)) { $stream->content = ''; } } else { // If the $stream isn't already defined, select it $stream = new Streams_Stream(); $stream->publisherId = $publisherId; $stream->name = $streamName; if (!$stream->retrieve()) { // Strange, this stream doesn't exist. // Well, we will just silently set the content to '' then $stream->content = ''; } } $content_readLevel = Streams::$READ_LEVEL['content']; $readLevels = array(); $label_readLevels = array(); $contact_label_list = array(); $removed_labels = array(); // First, assign all the readLevels that are directly set for specific users, // and aggregate the contact_labels from the other accesses, for an upcoming select. foreach ($taintedAccess as $access) { if ($userId = $access->ofUserId) { $readLevel = $access->readLevel; $readLevels[$userId] = $readLevel; if ($readLevel < 0) { $showToUserIds[$userId] = null; // not determined yet } else { if ($readLevel >= $content_readLevel) { $showToUserIds[$userId] = true; } else { $showToUserIds[$userId] = false; } } } else { if ($access->ofContactLabel) { $ofContactLabel = $access->ofContactLabel; $contact_label_list[] = $ofContactLabel; if ($access->get('removed', false)) { $removed_labels[$ofContactLabel] = true; } else { $label_readLevels[$ofContactLabel] = $access->readLevel; } } } } // Now, get all the people affected by this change, and their readLevels $readLevels2 = array(); if ($contact_label_list) { $contact_label_list = array_unique($contact_label_list); $contacts = Users_Contact::select('*')->where(array('userId' => $publisherId, 'label' => $contact_label_list))->fetchDbRows(null, '', 'contactUserId'); foreach ($contacts as $contact) { $contactUserId = $contact->contactUserId; if (isset($showToUserIds[$contactUserId])) { // this user had their read level set directly by the access, // which overrides read levels set by access using ofContactLabel continue; } if (isset($removed_labels[$ofContactLabel])) { // this label doesn't affect readLevels anymore, since it was deleted // but put this contact's id on a list whose readLevels need to be determined $showToUserIds[$contactUserId] = null; continue; } if (!isset($label_readLevels[$contact->label])) { continue; } $readLevel = $label_readLevels[$contact->label]; if (!isset($readLevels2[$contactUserId])) { $readLevels2[$contactUserId] = $readLevel; } else { $readLevels2[$contactUserId] = max($readLevels2[$contactUserId], $readLevel); } } } // Now step through all the users we found who were found through ofContactLabel // and make sure we update the avatar rows that were meant for them. foreach ($readLevels2 as $userId => $rl) { if ($rl >= $content_readLevel) { $showToUserIds[$userId] = true; } else { // in order for this to happen, two things had to be true: // 1) there was no access that directly set a readLevel >= $content_readLevel // 2) there was no access that set a readLevel >= $content_readLevel for any label containing this user // therefore, their view should be the public view $showToUserIds[$userId] = 'public'; } } // Resolve all the undetermined readLevels foreach ($showToUserIds as $userId => $v) { if (!isset($v)) { // if the readLevel hasn't been determined by now, it's the same as the public one $showToUserIds[$userId] = 'public'; } } // Set up the self avatar: $showToUserIds[$publisherId] = true; // Finally, set up the public avatar: if (!isset($stream->readLevel)) { $stream->readLevel = Streams_Stream::$DEFAULTS['readLevel']; } $showToUserIds[""] = $stream->readLevel >= $content_readLevel; // Now, we update the avatars: $parts = explode('/', $streamName); $field = end($parts); $rows_that_show = array(); $rows_that_hide = array(); foreach ($showToUserIds as $userId => $show) { if ($show === 'public') { // If no show is explicitly specified, use the value used for the rest of the public $show = $showToUserIds[""]; } if ($show === true) { $rows_that_show[] = array('publisherId' => $publisherId, 'toUserId' => $userId, 'username' => $user->username, 'icon' => $user->icon, 'updatedTime' => new Db_Expression("CURRENT_TIMESTAMP"), $field => $stream->content); } else { if ($show === false) { $rows_that_hide[] = array('publisherId' => $publisherId, 'toUserId' => $userId, 'username' => $user->username, 'icon' => $user->icon, 'updatedTime' => new Db_Expression("CURRENT_TIMESTAMP"), $field => ''); } } } $updates_that_show = array('username' => $user->username, 'icon' => $user->icon, 'updatedTime' => new Db_Expression("CURRENT_TIMESTAMP"), $field => $stream->content); $updates_that_hide = array('username' => $user->username, 'icon' => $user->icon, 'updatedTime' => new Db_Expression("CURRENT_TIMESTAMP"), $field => ''); // We are now ready to make changes to the database. if ($updateToPublicValue) { Streams_Avatar::update()->set(array($field => $showToUserIds[""] ? $stream->content : ''))->where(compact('publisherId'))->execute(); } Streams_Avatar::insertManyAndExecute($rows_that_show, array('onDuplicateKeyUpdate' => $updates_that_show)); Streams_Avatar::insertManyAndExecute($rows_that_hide, array('onDuplicateKeyUpdate' => $updates_that_hide)); }
/** * Access tool * @class Streams access * @constructor * @param {array} $options Options for the tool * @param {string} [$options.publisherId] the id of the user who is publishing the stream * @param {string} [$options.streamName] the name of the stream for which to edit access levels * @param {array} [$options.tabs] array of tab name => title. Defaults to read, write, admin tabs. * @param {array} [$options.ranges] associative array with keys "read", "write", "admin" and values as associative arrays of ($min, $max) for the displayed levels. * @param {boolean} [$options.controls] optionally set this to true to render only the controls */ function Streams_access_tool($options) { $tabs = array('read' => 'visible to', 'write' => 'editable by', 'admin' => 'members'); extract($options); $user = Users::loggedInUser(true); /** * @var string $streamName */ if (empty($streamName)) { $streamName = Streams::requestedName(true); } if (empty($publisherId)) { $publisherId = Streams::requestedPublisherId(); if (empty($publisherId)) { $publisherId = $user->id; } } reset($tabs); $tab = Q::ifset($_REQUEST, 'tab', key($tabs)); $stream = Streams::fetchOne($user->id, $publisherId, $streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name')); } $stream->addPreloaded($user->id); if (!$stream->testAdminLevel('own')) { throw new Users_Exception_NotAuthorized(); } $access_array = Streams_Access::select('*')->where(array('publisherId' => $stream->publisherId, 'streamName' => $stream->name))->andWhere("{$tab}Level != -1")->fetchDbRows(); $labelRows = Users_Label::fetch($stream->publisherId, '', true); $labels = array(); $icons = array(); foreach ($labelRows as $label => $row) { $labels[$label] = $row->title; $icons[$label] = "labels/{$label}"; } $userId_list = array(); foreach ($access_array as $a) { if ($a->ofUserId) { $userId_list[] = $a->ofUserId; } } $avatar_array = empty($userId_list) ? array() : Streams_Avatar::fetch($user->id, $userId_list); switch ($tab) { case 'read': $levels = Q_Config::get('Streams', 'readLevelOptions', array()); break; case 'write': $levels = Q_Config::get('Streams', 'writeLevelOptions', array()); break; case 'admin': $levels = Q_Config::get('Streams', 'adminLevelOptions', array()); break; } if (isset($ranges[$tab])) { $range_min = reset($ranges[$tab]); $range_max = end($ranges[$tab]); foreach ($levels as $k => $v) { if ($k < $range_min) { unset($levels[$k]); } if ($k > $range_max) { unset($levels[$k]); } } } $accessActionUrl = Q_Uri::url("Streams/access?publisherId={$publisherId}&streamName={$streamName}"); $dir = Q_Config::get('Users', 'paths', 'icons', 'files/Users/icons'); $accessArray = Db::exportArray($access_array); $avatarArray = Db::exportArray($avatar_array); if (empty($controls)) { Q_Response::addScript("plugins/Streams/js/Streams.js"); Q_Response::addScript("plugins/Streams/js/tools/access.js"); Q_Response::setToolOptions(compact('accessArray', 'avatarArray', 'labels', 'icons', 'tab', 'publisherId', 'streamName')); } else { Q_Response::setSlot('extra', array('stream' => $stream->exportArray(), 'accessArray' => $accessArray, 'avatarArray' => $avatarArray, 'labels' => $labels, 'icons' => $icons)); } return Q::view('Streams/tool/access.php', compact('stream', 'tabs', 'tab', 'labels', 'icons', 'levels', 'dir', 'publisherId', 'streamName', 'accessActionUrl', 'controls')); }