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); } } } }
/** * 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)); }