/** * Does necessary preparations for saving a stream in the database. * @method beforeSave * @param {array} $modifiedFields * The array of fields * @return {array} * @throws {Exception} * If mandatory field is not set */ function beforeSave($modifiedFields) { if (empty($this->attributes)) { $this->attributes = '{}'; } if (!$this->retrieved) { // Generate a unique name for the stream if (!isset($modifiedFields['name'])) { $this->name = $modifiedFields['name'] = Streams::db()->uniqueId(Streams_Stream::table(), 'name', array('publisherId' => $this->publisherId), array('prefix' => $this->type . '/Q')); } // we don't want user to update private fields but will set initial values to them $privateFieldNames = self::getConfigField($this->type, 'private', array()); // magic fields are handled by parent method $magicFieldNames = array('insertedTime', 'updatedTime'); $privateFieldNames = array_diff($privateFieldNames, $magicFieldNames); $streamTemplate = $this->getStreamTemplate('Streams_Stream'); $fieldNames = Streams_Stream::fieldNames(); if ($streamTemplate) { // if template exists copy all non-PK and non-magic fields from template foreach (array_diff($fieldNames, $this->getPrimaryKey(), $magicFieldNames) as $field) { if (in_array($field, $privateFieldNames) || !array_key_exists($field, $modifiedFields)) { $this->{$field} = $modifiedFields[$field] = $streamTemplate->{$field}; } } } else { // otherwise (no template) set all private fields to defaults foreach ($privateFieldNames as $field) { $defaults = self::getConfigField($this->type, 'defaults', Streams_Stream::$DEFAULTS); $this->{$field} = $modifiedFields[$field] = Q::ifset($defaults, $field, null); } } // Assign default values to fields that haven't been set yet foreach (array_diff($fieldNames, $magicFieldNames) as $field) { if (!array_key_exists($field, $this->fields) and !array_key_exists($field, $modifiedFields)) { $defaults = self::getConfigField($this->type, 'defaults', Streams_Stream::$DEFAULTS); $this->{$field} = $modifiedFields[$field] = Q::ifset($defaults, $field, null); } } // Get all access templates and save corresponding access $type = true; $accessTemplates = $this->getStreamTemplate('Streams_Access', $type); for ($i = 1; $i <= 3; ++$i) { foreach ($accessTemplates[$i] as $template) { $access = new Streams_Access(); $access->copyFrom($template->toArray()); $access->publisherId = $this->publisherId; $access->streamName = $this->name; if (!$access->save(true)) { return false; // JUNK: this leaves junk in the database, but preserves consistency } } } } /** * @event Streams/Stream/save/$streamType {before} * @param {Streams_Stream} stream * @return {false} To cancel further processing */ $params = array('stream' => $this, 'modifiedFields' => $modifiedFields); if (false === Q::event("Streams/Stream/save/{$this->type}", $params, 'before')) { return false; } foreach ($this->fields as $name => $value) { if (!empty($this->fieldsModified[$name])) { $modifiedFields[$name] = $value; } } $this->beforeSaveExtended($modifiedFields); $result = parent::beforeSave($modifiedFields); // Assume that the stream's name is not being changed $fields = array('Streams/user/firstName' => false, 'Streams/user/lastName' => false, 'Streams/user/username' => 'username', 'Streams/user/icon' => 'icon'); if (!isset($fields[$this->name])) { return $result; } $field = $this->name === 'Streams/user/icon' ? 'icon' : 'content'; $wasModified = !empty($this->fieldsModified[$field]) or !empty($this->fieldsModified['readLevel']); if (!$wasModified) { return $result; } if ($publicField = $fields[$this->name] and !Q::eventStack('Db/Row/Users_User/saveExecute')) { Streams::$beingSaved[$publicField] = $this; try { $user = Users_User::fetch($this->publisherId, true); $user->{$publicField} = $modifiedFields[$field]; $user->save(); } catch (Exception $e) { Streams::$beingSaved[$publicField] = array(); throw $e; } Streams::$beingSaved[$publicField] = array(); return Streams::$beingSavedQuery; } if ($this->retrieved and !$publicField) { // Update all avatars corresponding to access rows for this stream $taintedAccess = Streams_Access::select('*')->where(array('publisherId' => $this->publisherId, 'streamName' => $this->name))->fetchDbRows(); Streams::updateAvatars($this->publisherId, $taintedAccess, $this, true); } return $result; }
/** * Fetch all the streams which are related to, or from, a given stream. * @method related * @static * @param {string} $asUserId * The user who is fetching * @param {string} $publisherId * The publisher of the stream * @param {string|array|Db_Range} $streamName * The name of the stream which is presumably related to/from other streams * @param {mixed} $isCategory=true * If false, returns the categories that this stream is related to. * If true, returns all the streams this related to this category. * If a string, returns all the streams related to this category with names prefixed by this string. * @param {array} $options=array() * @param {boolean} [$options.orderBy=false] Defaults to false, which means order by decreasing weight. True means order by increasing weight. * @param {integer} [$options.limit] number of records to fetch * @param {integer} [$options.offset] offset to start from * @param {double} [$options.min] the minimum orderBy value (inclusive) to filter by, if any * @param {double} [$options.max] the maximum orderBy value (inclusive) to filter by, if any * @param {string|array|Db_Range} [$options.type] if specified, this filters the type of the relation. Can be useful for implementing custom indexes using relations and varying the value of "type". * @param {string} [$options.prefix] if specified, this filters by the prefix of the related streams * @param {array} [$options.where] you can also specify any extra conditions here * @param {array} [$options.extra] An array of any extra info to pass to Streams::fetch when fetching streams * @param {array} [$options.relationsOnly] If true, returns only the relations to/from stream, doesn't fetch the other data. Useful if publisher id of relation objects is not the same as provided by publisherId. * @param {array} [$options.streamsOnly] If true, returns only the streams related to/from stream, doesn't return the other data. * @param {array} [$options.streamFields] If specified, fetches only the fields listed here for any streams. * @param {array} [$options.skipFields] Optional array of field names. If specified, skips these fields when fetching streams * @param {array} [$options.includeTemplates] Defaults to false. Pass true here to include template streams (whose name ends in a slash) among the related streams. * @return {array} * Returns array($relations, $relatedStreams, $stream). * However, if $streamName wasn't a string or ended in "/" * then these third parameter is an array of streams. */ static function related($asUserId, $publisherId, $streamName, $isCategory = true, $options = array()) { if (is_string($streamName) and substr($streamName, -1) === '/') { $streamName = new Db_Range($streamName, false, false, true); } $returnMultiple = !is_string($streamName); if (is_array($isCategory)) { $options = $isCategory; $isCategory = true; } // Check access to stream $rows = Streams::fetch($asUserId, $publisherId, $streamName); $streams = array(); foreach ($rows as $n => $row) { if (!$row) { continue; } if (!$row->testReadLevel('see')) { throw new Users_Exception_NotAuthorized(); } $streams[$n] = $row; } if (!$streams) { if (!empty($options['relationsOnly']) || !empty($options['streamsOnly'])) { return array(); } return array(array(), array(), $returnMultiple ? array() : null); } $stream = reset($streams); if ($isCategory) { $query = Streams_RelatedTo::select('*')->where(array('toPublisherId' => $publisherId, 'toStreamName' => $streamName)); } else { $query = Streams_RelatedFrom::select('*')->where(array('fromPublisherId' => $publisherId, 'fromStreamName' => $streamName)); } if ($isCategory) { if (empty($options['orderBy'])) { $query = $query->orderBy('weight', false); } else { if ($options['orderBy'] === true) { $query = $query->orderBy('weight', true); } } } if (isset($options['prefix'])) { if (substr($options['prefix'], -1) !== '/') { throw new Q_Exception("prefix has to end in a slash", 'prefix'); } $other_field = $isCategory ? 'fromStreamName' : 'toStreamName'; $query = $query->where(array($other_field => new Db_Range($options['prefix'], true, false, true))); } $offset = !empty($options['offset']) ? $options['offset'] : 0; $max_limit = Q_Config::expect('Streams', 'db', 'limits', 'stream'); $limit = !empty($options['limit']) ? $options['limit'] : $max_limit; if ($limit > $max_limit) { throw new Q_Exception("Streams::related limit is too large, must be <= {$max_limit}"); } $min = isset($options['min']) ? $options['min'] : null; $max = isset($options['max']) ? $options['max'] : null; if (isset($min) or isset($max)) { $range = new Db_Range($min, true, true, $max); $query = $query->where(array('weight' => $range)); } if (isset($limit)) { $query = $query->limit($limit, $offset); } if (isset($options['type'])) { $query = $query->where(array('type' => $options['type'])); } if (isset($options['where'])) { $query = $query->where($options['where']); } $FT = $isCategory ? 'from' : 'to'; if (empty($options['includeTemplates'])) { $col = $FT . 'StreamName'; $query = $query->where(new Db_Expression("SUBSTRING({$col}, -1, 1) != '/'")); } $relations = $query->fetchDbRows(null, '', $FT . 'StreamName'); if (empty($options['includeTemplates'])) { foreach ($relations as $k => $v) { if (substr($k, -1) === '/') { unset($relations[$k]); } } } if (!empty($options['relationsOnly'])) { return $relations; } if (empty($relations)) { return empty($options['streamsOnly']) ? array($relations, array(), $returnMultiple ? $streams : $stream) : array(); } $fields = '*'; if (isset($options['skipFields'])) { $skip_fields = is_array($options['skipFields']) ? $options['skipFields'] : explode(',', $options['skipFields']); $fields = implode(',', array_diff(Streams_Stream::fieldNames(), $skip_fields)); } else { if (isset($options['streamFields'])) { $fields = is_string($options['streamFields']) ? $options['streamFields'] : implode(',', $options['streamFields']); } } $extra = isset($options['extra']) ? $options['extra'] : null; $names = array(); $FTP = $FT . 'PublisherId'; foreach ($relations as $name => $r) { if ($r->{$FTP} === $publisherId) { $names[] = $name; } } $relatedStreams = Streams::fetch($asUserId, $publisherId, $names, $fields, $extra); foreach ($relatedStreams as $name => $s) { if (!$s) { continue; } $s->weight = isset($relations[$name]->weight) ? $relations[$name]->weight : null; } if (!empty($options['streamsOnly'])) { return $relatedStreams; } return array($relations, $relatedStreams, $returnMultiple ? $streams : $stream); }