123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951 |
- <?php
- // This file is part of GNU social - https://www.gnu.org/software/social
- //
- // GNU social is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // GNU social is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
- /**
- * @category Notices
- * @package GNUsocial
- * @author Shashi Gowda <connect2shashi@gmail.com>
- * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
- * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
- */
- defined('GNUSOCIAL') || die();
- class Profile_list extends Managed_DataObject
- {
- public $__table = 'profile_list'; // table name
- public $id; // int(4) primary_key not_null
- public $tagger; // int(4)
- public $tag; // varchar(64)
- public $description; // text
- public $private; // bool default_false
- public $created; // datetime() not_null default_0000-00-00%2000%3A00%3A00
- public $modified; // datetime() not_null default_CURRENT_TIMESTAMP
- public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
- public $mainpage; // varchar(191) not 255 because utf8mb4 takes more space
- public $tagged_count; // smallint
- public $subscriber_count; // smallint
- public static function schemaDef()
- {
- return array(
- 'fields' => array(
- 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
- 'tagger' => array('type' => 'int', 'not null' => true, 'description' => 'user making the tag'),
- 'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'people tag'),
- 'description' => array('type' => 'text', 'description' => 'description of the people tag'),
- 'private' => array('type' => 'bool', 'default' => false, 'description' => 'is this tag private'),
- 'created' => array('type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date the tag was added'),
- 'modified' => array('type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date the tag was modified'),
- 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'),
- 'mainpage' => array('type' => 'varchar', 'length' => 191, 'description' => 'page to link to'),
- 'tagged_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of people tagged with this tag by this user'),
- 'subscriber_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of subscribers to this tag'),
- ),
- 'primary key' => array('tagger', 'tag'),
- 'unique keys' => array(
- 'profile_list_id_key' => array('id'),
- ),
- 'foreign keys' => array(
- 'profile_list_tagger_fkey' => array('profile', array('tagger' => 'id')),
- ),
- 'indexes' => array(
- 'profile_list_modified_idx' => array('modified'),
- 'profile_list_tag_idx' => array('tag'),
- 'profile_list_tagger_tag_idx' => array('tagger', 'tag'),
- 'profile_list_tagged_count_idx' => array('tagged_count'),
- 'profile_list_subscriber_count_idx' => array('subscriber_count'),
- ),
- );
- }
- /**
- * get the tagger of this profile_list object
- *
- * @return Profile the tagger
- */
- public function getTagger()
- {
- return Profile::getByID($this->tagger);
- }
- /**
- * return a string to identify this
- * profile_list in the user interface etc.
- *
- * @return String
- */
- public function getBestName()
- {
- return $this->tag;
- }
- /**
- * return a uri string for this profile_list
- *
- * @return String uri
- */
- public function getUri()
- {
- $uri = null;
- if (Event::handle('StartProfiletagGetUri', array($this, &$uri))) {
- if (!empty($this->uri)) {
- $uri = $this->uri;
- } else {
- $uri = common_local_url(
- 'profiletagbyid',
- ['id' => $this->id, 'tagger_id' => $this->tagger]
- );
- }
- }
- Event::handle('EndProfiletagGetUri', array($this, &$uri));
- return $uri;
- }
- /**
- * return a url to the homepage of this item
- *
- * @return String home url
- */
- public function homeUrl()
- {
- $url = null;
- if (Event::handle('StartUserPeopletagHomeUrl', array($this, &$url))) {
- // normally stored in mainpage, but older ones may be null
- if (!empty($this->mainpage)) {
- $url = $this->mainpage;
- } else {
- $url = common_local_url(
- 'showprofiletag',
- [
- 'nickname' => $this->getTagger()->nickname,
- 'tag' => $this->tag,
- ]
- );
- }
- }
- Event::handle('EndUserPeopletagHomeUrl', array($this, &$url));
- return $url;
- }
- /**
- * return an immutable url for this object
- *
- * @return String permalink
- */
- public function permalink()
- {
- $url = null;
- if (Event::handle('StartProfiletagPermalink', array($this, &$url))) {
- $url = common_local_url(
- 'profiletagbyid',
- ['id' => $this->id]
- );
- }
- Event::handle('EndProfiletagPermalink', array($this, &$url));
- return $url;
- }
- /**
- * Query notices by users associated with this tag,
- * but first check the cache before hitting the DB.
- *
- * @param integer $offset offset
- * @param integer $limit maximum no of results
- * @param integer $since_id=null since this id
- * @param integer $max_id=null maximum id in result
- *
- * @return Notice the query
- */
- public function getNotices($offset, $limit, $since_id = null, $max_id = null)
- {
- // FIXME: Use something else than Profile::current() to avoid
- // possible confusion between session user and queue processing.
- $stream = new PeopletagNoticeStream($this, Profile::current());
- return $stream->getNotices($offset, $limit, $since_id, $max_id);
- }
- /**
- * Get subscribers (local and remote) to this people tag
- * Order by reverse chronology
- *
- * @param integer $offset offset
- * @param integer $limit maximum no of results
- * @param integer $since_id=null since unix timestamp
- * @param integer $upto=null maximum unix timestamp when subscription was made
- *
- * @return Profile results
- */
- public function getSubscribers(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0)
- {
- $subs = new Profile();
- $subs->joinAdd(
- array('id', 'profile_tag_subscription:profile_id')
- );
- $subs->whereAdd('profile_tag_subscription.profile_tag_id = ' . $this->id);
- if (common_config('db', 'type') !== 'mysql') {
- $subs->selectAdd(sprintf(
- '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' .
- 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"',
- "FROM (profile_tag_subscription.created - TIMESTAMP '1970-01-01 00:00:00')"
- ));
- } else {
- $subs->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag_subscription.created) AS `cursor`");
- }
- if ($since != 0) {
- $subs->whereAdd('cursor > ' . $since);
- }
- if ($upto != 0) {
- $subs->whereAdd('cursor <= ' . $upto);
- }
- if ($limit != null) {
- $subs->limit($offset, $limit);
- }
- $subs->orderBy('profile_tag_subscription.created DESC');
- $subs->find();
- return $subs;
- }
- /**
- * Get all and only local subscribers to this people tag
- * used for distributing notices to user inboxes.
- *
- * @return array ids of users
- */
- public function getUserSubscribers()
- {
- // XXX: cache this
- $user = new User();
- $user->query(sprintf(
- 'SELECT id ' .
- 'FROM %1$s INNER JOIN profile_tag_subscription ' .
- 'ON %1$s.id = profile_tag_subscription.profile_id ' .
- 'WHERE profile_tag_subscription.profile_tag_id = %2$d ',
- $user->escapedTableName(),
- $this->id
- ));
- $ids = [];
- while ($user->fetch()) {
- $ids[] = $user->id;
- }
- $user->free();
- return $ids;
- }
- /**
- * Check to see if a given profile has
- * subscribed to this people tag's timeline
- *
- * @param mixed $id User or Profile object or integer id
- *
- * @return boolean subscription status
- */
- public function hasSubscriber($id)
- {
- if (!is_numeric($id)) {
- $id = $id->id;
- }
- $sub = Profile_tag_subscription::pkeyGet(array('profile_tag_id' => $this->id,
- 'profile_id' => $id));
- return !empty($sub);
- }
- /**
- * Get profiles tagged with this people tag,
- * include modified timestamp as a "cursor" field
- * order by descending order of modified time
- *
- * @param integer $offset offset
- * @param integer $limit maximum no of results
- * @param integer $since_id=null since unix timestamp
- * @param integer $upto=null maximum unix timestamp when subscription was made
- *
- * @return Profile results
- */
- public function getTagged(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0)
- {
- $tagged = new Profile();
- $tagged->joinAdd(['id', 'profile_tag:tagged']);
- if (common_config('db', 'type') !== 'mysql') {
- $tagged->selectAdd(sprintf(
- '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' .
- 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"',
- "FROM (profile_tag.modified - TIMESTAMP '1970-01-01 00:00:00')"
- ));
- } else {
- $tagged->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag.modified) AS `cursor`");
- }
- $tagged->whereAdd('profile_tag.tagger = '.$this->tagger);
- $tagged->whereAdd("profile_tag.tag = '{$this->tag}'");
- if ($since != 0) {
- $tagged->whereAdd('cursor > ' . $since);
- }
- if ($upto != 0) {
- $tagged->whereAdd('cursor <= ' . $upto);
- }
- if ($limit != null) {
- $tagged->limit($offset, $limit);
- }
- $tagged->orderBy('profile_tag.modified DESC');
- $tagged->find();
- return $tagged;
- }
- /**
- * Gracefully delete one or many people tags
- * along with their members and subscriptions data
- *
- * @return boolean success
- */
- public function delete($useWhere = false)
- {
- // force delete one item at a time.
- if (empty($this->id)) {
- $this->find();
- while ($this->fetch()) {
- $this->delete();
- }
- }
- Profile_tag::cleanup($this);
- Profile_tag_subscription::cleanup($this);
- self::blow('profile:lists:%d', $this->tagger);
- return parent::delete($useWhere);
- }
- /**
- * Update a people tag gracefully
- * also change "tag" fields in profile_tag table
- *
- * @param Profile_list $dataObject Object's original form
- *
- * @return boolean success
- */
- public function update($dataObject = false)
- {
- if (!is_object($dataObject) && !$dataObject instanceof Profile_list) {
- return parent::update($dataObject);
- }
- $result = true;
- // if original tag was different
- // check to see if the new tag already exists
- // if not, rename the tag correctly
- if ($dataObject->tag != $this->tag || $dataObject->tagger != $this->tagger) {
- $existing = Profile_list::getByTaggerAndTag($this->tagger, $this->tag);
- if (!empty($existing)) {
- // TRANS: Server exception.
- throw new ServerException(_('The tag you are trying to rename ' .
- 'to already exists.'));
- }
- // move the tag
- // XXX: allow OStatus plugin to send out profile tag
- $result = Profile_tag::moveTag($dataObject, $this);
- }
- return parent::update($dataObject);
- }
- /**
- * return an xml string representing this people tag
- * as the author of an atom feed
- *
- * @return string atom author element
- */
- public function asAtomAuthor()
- {
- $xs = new XMLStringer(true);
- $tagger = $this->getTagger();
- $xs->elementStart('author');
- $xs->element('name', null, '@' . $tagger->nickname . '/' . $this->tag);
- $xs->element('uri', null, $this->permalink());
- $xs->elementEnd('author');
- return $xs->getString();
- }
- /**
- * return an xml string to represent this people tag
- * as a noun in an activitystreams feed.
- *
- * @param string $element the xml tag
- *
- * @return string activitystreams noun
- */
- public function asActivityNoun($element)
- {
- $noun = ActivityObject::fromPeopletag($this);
- return $noun->asString('activity:' . $element);
- }
- /**
- * get the cached number of profiles tagged with this
- * people tag, re-count if the argument is true.
- *
- * @param boolean $recount whether to ignore cache
- *
- * @return integer count
- */
- public function taggedCount($recount = false)
- {
- $keypart = sprintf(
- 'profile_list:tagged_count:%d:%s',
- $this->tagger,
- $this->tag
- );
- $count = self::cacheGet($keypart);
- if ($count === false) {
- $tags = new Profile_tag();
- $tags->tag = $this->tag;
- $tags->tagger = $this->tagger;
- $count = $tags->count('distinct tagged');
- self::cacheSet($keypart, $count);
- }
- return $count;
- }
- /**
- * get the cached number of profiles subscribed to this
- * people tag, re-count if the argument is true.
- *
- * @param boolean $recount whether to ignore cache
- *
- * @return integer count
- */
- public function subscriberCount($recount = false)
- {
- $keypart = sprintf(
- 'profile_list:subscriber_count:%d',
- $this->id
- );
- $count = self::cacheGet($keypart);
- if ($count === false) {
- $sub = new Profile_tag_subscription();
- $sub->profile_tag_id = $this->id;
- $count = (int) $sub->count('distinct profile_id');
- self::cacheSet($keypart, $count);
- }
- return $count;
- }
- /**
- * get the cached number of profiles subscribed to this
- * people tag, re-count if the argument is true.
- *
- * @param boolean $recount whether to ignore cache
- *
- * @return integer count
- */
- public function blowNoticeStreamCache($all = false)
- {
- self::blow('profile_list:notice_ids:%d', $this->id);
- if ($all) {
- self::blow('profile_list:notice_ids:%d;last', $this->id);
- }
- }
- /**
- * get the Profile_list object by the
- * given tagger and with given tag
- *
- * @param integer $tagger the id of the creator profile
- * @param integer $tag the tag
- *
- * @return integer count
- */
- public static function getByTaggerAndTag($tagger, $tag)
- {
- $ptag = Profile_list::pkeyGet(array('tagger' => $tagger, 'tag' => $tag));
- return $ptag;
- }
- /**
- * create a profile_list record for a tag, tagger pair
- * if it doesn't exist, return it.
- *
- * @param integer $tagger the tagger
- * @param string $tag the tag
- * @param string $description description
- * @param boolean $private protected or not
- *
- * @return Profile_list the people tag object
- */
- public static function ensureTag($tagger, $tag, $description = null, $private = false)
- {
- $ptag = Profile_list::getByTaggerAndTag($tagger, $tag);
- if (empty($ptag->id)) {
- $args = array(
- 'tag' => $tag,
- 'tagger' => $tagger,
- 'description' => $description,
- 'private' => $private
- );
- $new_tag = Profile_list::saveNew($args);
- return $new_tag;
- }
- return $ptag;
- }
- /**
- * get the maximum number of characters
- * that can be used in the description of
- * a people tag.
- *
- * determined by $config['peopletag']['desclimit']
- * if not set, falls back to $config['site']['textlimit']
- *
- * @return integer maximum number of characters
- */
- public static function maxDescription()
- {
- $desclimit = common_config('peopletag', 'desclimit');
- // null => use global limit (distinct from 0!)
- if (is_null($desclimit)) {
- $desclimit = common_config('site', 'textlimit');
- }
- return $desclimit;
- }
- /**
- * check if the length of given text exceeds
- * character limit.
- *
- * @param string $desc the description
- *
- * @return boolean is the descripition too long?
- */
- public static function descriptionTooLong($desc)
- {
- $desclimit = self::maxDescription();
- return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
- }
- /**
- * save a new people tag, this should be always used
- * since it makes uri, homeurl, created and modified
- * timestamps and performs checks.
- *
- * @param array $fields an array with fields and their values
- *
- * @return mixed Profile_list on success, false on fail
- */
- public static function saveNew(array $fields)
- {
- extract($fields);
- $ptag = new Profile_list();
- $ptag->query('START TRANSACTION');
- if (empty($tagger)) {
- // TRANS: Server exception saving new tag without having a tagger specified.
- throw new Exception(_('No tagger specified.'));
- }
- if (empty($tag)) {
- // TRANS: Server exception saving new tag without having a tag specified.
- throw new Exception(_('No tag specified.'));
- }
- if (empty($mainpage)) {
- $mainpage = null;
- }
- if (empty($uri)) {
- // fill in later...
- $uri = null;
- }
- if (empty($mainpage)) {
- $mainpage = null;
- }
- if (empty($description)) {
- $description = null;
- }
- if (empty($private)) {
- $private = false;
- }
- $ptag->tagger = $tagger;
- $ptag->tag = $tag;
- $ptag->description = $description;
- $ptag->private = $private;
- $ptag->uri = $uri;
- $ptag->mainpage = $mainpage;
- $ptag->created = common_sql_now();
- $ptag->modified = common_sql_now();
- $result = $ptag->insert();
- if (!$result) {
- common_log_db_error($ptag, 'INSERT', __FILE__);
- // TRANS: Server exception saving new tag.
- throw new ServerException(_('Could not create profile tag.'));
- }
- if (!isset($uri) || empty($uri)) {
- $orig = clone($ptag);
- $ptag->uri = common_local_url('profiletagbyid', array('id' => $ptag->id, 'tagger_id' => $ptag->tagger));
- $result = $ptag->update($orig);
- if (!$result) {
- common_log_db_error($ptag, 'UPDATE', __FILE__);
- // TRANS: Server exception saving new tag.
- throw new ServerException(_('Could not set profile tag URI.'));
- }
- }
- if (!isset($mainpage) || empty($mainpage)) {
- $orig = clone($ptag);
- $user = User::getKV('id', $ptag->tagger);
- if (!empty($user)) {
- $ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'nickname' => $user->getNickname()));
- } else {
- $ptag->mainpage = $uri; // assume this is a remote peopletag and the uri works
- }
- $result = $ptag->update($orig);
- if (!$result) {
- common_log_db_error($ptag, 'UPDATE', __FILE__);
- // TRANS: Server exception saving new tag.
- throw new ServerException(_('Could not set profile tag mainpage.'));
- }
- }
- return $ptag;
- }
- /**
- * get all items at given cursor position for api
- *
- * @param callback $fn a function that takes the following arguments in order:
- * $offset, $limit, $since_id, $max_id
- * and returns a Profile_list object after making the DB query
- * @param array $args arguments required for $fn
- * @param integer $cursor the cursor
- * @param integer $count max. number of results
- *
- * Algorithm:
- * - if cursor is 0, return empty list
- * - if cursor is -1, get first 21 items, next_cursor = 20th prev_cursor = 0
- * - if cursor is +ve get 22 consecutive items before starting at cursor
- * - return items[1..20] if items[0] == cursor else return items[0..21]
- * - prev_cursor = items[1]
- * - next_cursor = id of the last item being returned
- *
- * - if cursor is -ve get 22 consecutive items after cursor starting at cursor
- * - return items[1..20]
- *
- * @returns array (array (mixed items), int next_cursor, int previous_cursor)
- */
- // XXX: This should be in Memcached_DataObject... eventually
- public static function getAtCursor($fn, array $args, $cursor, $count = 20)
- {
- $items = array();
- $since_id = 0;
- $max_id = 0;
- $next_cursor = 0;
- $prev_cursor = 0;
- if ($cursor > 0) {
- // if cursor is +ve fetch $count+2 items before cursor starting at cursor
- $max_id = $cursor;
- $fn_args = array_merge($args, array(0, $count+2, 0, $max_id));
- $list = call_user_func_array($fn, $fn_args);
- while ($list->fetch()) {
- $items[] = clone($list);
- }
- if ((isset($items[0]->cursor) && $items[0]->cursor == $cursor) ||
- $items[0]->id == $cursor) {
- array_shift($items);
- $prev_cursor = isset($items[0]->cursor) ?
- -$items[0]->cursor : -$items[0]->id;
- } else {
- if (count($items) > $count+1) {
- array_shift($items);
- }
- // this means the cursor item has been deleted, check to see if there are more
- $fn_args = array_merge($args, array(0, 1, $cursor));
- $more = call_user_func($fn, $fn_args);
- if (!$more->fetch() || empty($more)) {
- // no more items.
- $prev_cursor = 0;
- } else {
- $prev_cursor = isset($items[0]->cursor) ?
- -$items[0]->cursor : -$items[0]->id;
- }
- }
- if (count($items)==$count+1) {
- // this means there is a next page.
- $next = array_pop($items);
- $next_cursor = isset($next->cursor) ?
- $items[$count-1]->cursor : $items[$count-1]->id;
- }
- } elseif ($cursor < -1) {
- // if cursor is -ve fetch $count+2 items created after -$cursor-1
- $cursor = abs($cursor);
- $since_id = $cursor-1;
- $fn_args = array_merge($args, array(0, $count+2, $since_id));
- $list = call_user_func_array($fn, $fn_args);
- while ($list->fetch()) {
- $items[] = clone($list);
- }
- $end = count($items)-1;
- if ((isset($items[$end]->cursor) && $items[$end]->cursor == $cursor) ||
- $items[$end]->id == $cursor) {
- array_pop($items);
- $next_cursor = isset($items[$end-1]->cursor) ?
- $items[$end-1]->cursor : $items[$end-1]->id;
- } else {
- $next_cursor = isset($items[$end]->cursor) ?
- $items[$end]->cursor : $items[$end]->id;
- if ($end > $count) {
- // excess item
- array_pop($items);
- }
- // check if there are more items for next page
- $fn_args = array_merge($args, array(0, 1, 0, $cursor));
- $more = call_user_func_array($fn, $fn_args);
- if (!$more->fetch() || empty($more)) {
- $next_cursor = 0;
- }
- }
- if (count($items) == $count+1) {
- // this means there is a previous page.
- $prev = array_shift($items);
- $prev_cursor = isset($prev->cursor) ?
- -$items[0]->cursor : -$items[0]->id;
- }
- } elseif ($cursor == -1) {
- $fn_args = array_merge($args, array(0, $count+1));
- $list = call_user_func_array($fn, $fn_args);
- while ($list->fetch()) {
- $items[] = clone($list);
- }
- if (count($items)==$count+1) {
- $next = array_pop($items);
- if (isset($next->cursor)) {
- $next_cursor = $items[$count-1]->cursor;
- } else {
- $next_cursor = $items[$count-1]->id;
- }
- }
- }
- return array($items, $next_cursor, $prev_cursor);
- }
- /**
- * save a collection of people tags into the cache
- *
- * @param string $ckey cache key
- * @param Profile_list &$tag the results to store
- * @param integer $offset offset for slicing results
- * @param integer $limit maximum number of results
- *
- * @return boolean success
- */
- public static function setCache($ckey, &$tag, $offset = 0, $limit = null)
- {
- $cache = Cache::instance();
- if (empty($cache)) {
- return false;
- }
- $str = '';
- $tags = array();
- while ($tag->fetch()) {
- $str .= $tag->tagger . ':' . $tag->tag . ';';
- $tags[] = clone($tag);
- }
- $str = substr($str, 0, -1);
- if ($offset>=0 && !is_null($limit)) {
- $tags = array_slice($tags, $offset, $limit);
- }
- $tag = new ArrayWrapper($tags);
- return self::cacheSet($ckey, $str);
- }
- /**
- * get people tags from the cache
- *
- * @param string $ckey cache key
- * @param integer $offset offset for slicing
- * @param integer $limit limit
- *
- * @return Profile_list results
- */
- public static function getCached($ckey, $offset = 0, $limit = null)
- {
- $keys_str = self::cacheGet($ckey);
- if ($keys_str === false) {
- return false;
- }
- $pairs = explode(';', $keys_str);
- $keys = array();
- foreach ($pairs as $pair) {
- $keys[] = explode(':', $pair);
- }
- if ($offset>=0 && !is_null($limit)) {
- $keys = array_slice($keys, $offset, $limit);
- }
- return self::getByKeys($keys);
- }
- /**
- * get Profile_list objects from the database
- * given their (tag, tagger) key pairs.
- *
- * @param array $keys array of array(tagger, tag)
- *
- * @return Profile_list results
- */
- public static function getByKeys(array $keys)
- {
- $cache = Cache::instance();
- if (!empty($cache)) {
- $tags = array();
- foreach ($keys as $key) {
- $t = Profile_list::getByTaggerAndTag($key[0], $key[1]);
- if (!empty($t)) {
- $tags[] = $t;
- }
- }
- return new ArrayWrapper($tags);
- } else {
- $tag = new Profile_list();
- if (empty($keys)) {
- //if no IDs requested, just return the tag object
- return $tag;
- }
- $pairs = array();
- foreach ($keys as $key) {
- $pairs[] = '(' . $key[0] . ', "' . $key[1] . '")';
- }
- $tag->whereAdd('(tagger, tag) in (' . implode(', ', $pairs) . ')');
- $tag->find();
- $temp = array();
- while ($tag->fetch()) {
- $temp[$tag->tagger.'-'.$tag->tag] = clone($tag);
- }
- $wrapped = array();
- foreach ($keys as $key) {
- $id = $key[0].'-'.$key[1];
- if (array_key_exists($id, $temp)) {
- $wrapped[] = $temp[$id];
- }
- }
- return new ArrayWrapper($wrapped);
- }
- }
- public function insert()
- {
- $result = parent::insert();
- if ($result) {
- self::blow('profile:lists:%d', $this->tagger);
- }
- return $result;
- }
- }
|