return $groups;
}
+ function isTagged($peopletag)
+ {
+ $tag = Profile_tag::pkeyGet(array('tagger' => $peopletag->tagger,
+ 'tagged' => $this->id,
+ 'tag' => $peopletag->tag));
+ return !empty($tag);
+ }
+
+ function canTag($tagged)
+ {
+ if (empty($tagged)) {
+ return false;
+ }
+
+ if ($tagged->id == $this->id) {
+ return true;
+ }
+
+ $all = common_config('peopletag', 'allow_tagging', 'all');
+ $local = common_config('peopletag', 'allow_tagging', 'local');
+ $remote = common_config('peopletag', 'allow_tagging', 'remote');
+ $subs = common_config('peopletag', 'allow_tagging', 'subs');
+
+ if ($all) {
+ return true;
+ }
+
+ $tagged_user = $tagged->getUser();
+ if (!empty($tagged_user)) {
+ if ($local) {
+ return true;
+ }
+ } else if ($subs) {
+ return (Subscription::exists($this, $tagged) ||
+ Subscription::exists($tagged, $this));
+ } else if ($remote) {
+ return true;
+ }
+ return false;
+ }
+
+ function getOwnedTags($auth_user, $offset=0, $limit=null, $since_id=0, $max_id=0)
+ {
+ $tags = new Profile_list();
+ $tags->tagger = $this->id;
+
+ if (($auth_user instanceof User || $auth_user instanceof Profile) &&
+ $auth_user->id === $this->id) {
+ // no condition, get both private and public tags
+ } else {
+ $tags->private = false;
+ }
+
+ $tags->selectAdd('id as "cursor"');
+
+ if ($since_id>0) {
+ $tags->whereAdd('id > '.$since_id);
+ }
+
+ if ($max_id>0) {
+ $tags->whereAdd('id <= '.$max_id);
+ }
+
+ if($offset>=0 && !is_null($limit)) {
+ $tags->limit($offset, $limit);
+ }
+
+ $tags->orderBy('id DESC');
+ $tags->find();
+
+ return $tags;
+ }
+
+ function getOtherTags($auth_user=null, $offset=0, $limit=null, $since_id=0, $max_id=0)
+ {
+ $lists = new Profile_list();
+
+ $tags = new Profile_tag();
+ $tags->tagged = $this->id;
+
+ $lists->joinAdd($tags);
+ #@fixme: postgres (round(date_part('epoch', my_date)))
+ $lists->selectAdd('unix_timestamp(profile_tag.modified) as "cursor"');
+
+ if ($auth_user instanceof User || $auth_user instanceof Profile) {
+ $lists->whereAdd('( ( profile_list.private = false ) ' .
+ 'OR ( profile_list.tagger = ' . $auth_user->id . ' AND ' .
+ 'profile_list.private = true ) )');
+ } else {
+ $lists->private = false;
+ }
+
+ if ($since_id>0) {
+ $lists->whereAdd('cursor > '.$since_id);
+ }
+
+ if ($max_id>0) {
+ $lists->whereAdd('cursor <= '.$max_id);
+ }
+
+ if($offset>=0 && !is_null($limit)) {
+ $lists->limit($offset, $limit);
+ }
+
+ $lists->orderBy('profile_tag.modified DESC');
+ $lists->find();
+
+ return $lists;
+ }
+
+ function getPrivateTags($offset=0, $limit=null, $since_id=0, $max_id=0)
+ {
+ $tags = new Profile_list();
+ $tags->private = true;
+ $tags->tagger = $this->id;
+
+ if ($since_id>0) {
+ $tags->whereAdd('id > '.$since_id);
+ }
+
+ if ($max_id>0) {
+ $tags->whereAdd('id <= '.$max_id);
+ }
+
+ if($offset>=0 && !is_null($limit)) {
+ $tags->limit($offset, $limit);
+ }
+
+ $tags->orderBy('id DESC');
+ $tags->find();
+
+ return $tags;
+ }
+
+ function hasLocalTags()
+ {
+ $tags = new Profile_tag();
+
+ $tags->joinAdd(array('tagger', 'user:id'));
+ $tags->whereAdd('tagged = '.$this->id);
+ $tags->whereAdd('tagger != '.$this->id);
+
+ $tags->limit(0, 1);
+ $tags->fetch();
+
+ return ($tags->N == 0) ? false : true;
+ }
+
+ function getTagSubscriptions($offset=0, $limit=null, $since_id=0, $max_id=0)
+ {
+ $lists = new Profile_list();
+ $subs = new Profile_tag_subscription();
+
+ $lists->joinAdd($subs);
+ #@fixme: postgres (round(date_part('epoch', my_date)))
+ $lists->selectAdd('unix_timestamp(profile_tag_subscription.created) as "cursor"');
+
+ $lists->whereAdd('profile_tag_subscription.profile_id = '.$this->id);
+
+ if ($since_id>0) {
+ $lists->whereAdd('cursor > '.$since_id);
+ }
+
+ if ($max_id>0) {
+ $lists->whereAdd('cursor <= '.$max_id);
+ }
+
+ if($offset>=0 && !is_null($limit)) {
+ $lists->limit($offset, $limit);
+ }
+
+ $lists->orderBy('"cursor" DESC');
+ $lists->find();
+
+ return $lists;
+ }
+
function avatarUrl($size=AVATAR_PROFILE_SIZE)
{
$avatar = $this->getAvatar($size);
return new ArrayWrapper($profiles);
}
+
+ function getTaggedSubscribers($tag)
+ {
+ $qry =
+ 'SELECT profile.* ' .
+ 'FROM profile JOIN (subscription, profile_tag, profile_list) ' .
+ 'ON profile.id = subscription.subscriber ' .
+ 'AND profile.id = profile_tag.tagged ' .
+ 'AND profile_tag.tagger = profile_list.tagger AND profile_tag.tag = profile_list.tag ' .
+ 'WHERE subscription.subscribed = %d ' .
+ 'AND subscription.subscribed != subscription.subscriber ' .
+ 'AND profile_tag.tagger = %d AND profile_tag.tag = "%s" ' .
+ 'AND profile_list.private = false ' .
+ 'ORDER BY subscription.created DESC';
+
+ $profile = new Profile();
+ $tagged = array();
+
+ $cnt = $profile->query(sprintf($qry, $this->id, $this->id, $tag));
+
+ while ($profile->fetch()) {
+ $tagged[] = clone($profile);
+ }
+ return $tagged;
+ }
+
function subscriptionCount()
{
$c = Cache::instance();
--- /dev/null
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Notices
+ * @package StatusNet
+ * @author Shashi Gowda <connect2shashi@gmail.com>
+ * @license GNU Affero General Public License http://www.gnu.org/licenses/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Table Definition for profile_list
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Profile_list extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ 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; // tinyint(1)
+ public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00
+ public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
+ public $uri; // varchar(255) unique_key
+ public $mainpage; // varchar(255)
+ public $tagged_count; // smallint
+ public $subscriber_count; // smallint
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Profile_list',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ /**
+ * return a profile_list record, given its tag and tagger.
+ *
+ * @param array $kv ideally array('tag' => $tag, 'tagger' => $tagger)
+ *
+ * @return Profile_list a Profile_list object with the given tag and tagger.
+ */
+
+ function pkeyGet($kv)
+ {
+ return Memcached_DataObject::pkeyGet('Profile_list', $kv);
+ }
+
+ /**
+ * get the tagger of this profile_list object
+ *
+ * @return Profile the tagger
+ */
+
+ function getTagger()
+ {
+ return Profile::staticGet('id', $this->tagger);
+ }
+
+ /**
+ * return a string to identify this
+ * profile_list in the user interface etc.
+ *
+ * @return String
+ */
+
+ function getBestName()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * return a uri string for this profile_list
+ *
+ * @return String uri
+ */
+
+ function getUri()
+ {
+ $uri = null;
+ if (Event::handle('StartProfiletagGetUri', array($this, &$uri))) {
+ if (!empty($this->uri)) {
+ $uri = $this->uri;
+ } else {
+ $uri = common_local_url('profiletagbyid',
+ array('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
+ */
+
+ 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',
+ array('tagger' => $this->getTagger()->nickname,
+ 'tag' => $this->tag));
+ }
+ }
+ Event::handle('EndUserPeopletagHomeUrl', array($this, &$url));
+ return $url;
+ }
+
+ /**
+ * return an immutable url for this object
+ *
+ * @return String permalink
+ */
+
+ function permalink()
+ {
+ $url = null;
+ if (Event::handle('StartProfiletagPermalink', array($this, &$url))) {
+ $url = common_local_url('profiletagbyid',
+ array('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
+ */
+
+ function getNotices($offset, $limit, $since_id=null, $max_id=null)
+ {
+ $ids = Notice::stream(array($this, '_streamDirect'),
+ array(),
+ 'profile_tag:notice_ids:' . $this->id,
+ $offset, $limit, $since_id, $max_id);
+
+ return Notice::getStreamByIds($ids);
+ }
+
+ /**
+ * Query notices by users associated with this tag from the database.
+ *
+ * @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 array array of notice ids.
+ */
+
+ function _streamDirect($offset, $limit, $since_id, $max_id)
+ {
+ $inbox = new Profile_tag_inbox();
+
+ $inbox->profile_tag_id = $this->id;
+
+ $inbox->selectAdd();
+ $inbox->selectAdd('notice_id');
+
+ if ($since_id != 0) {
+ $inbox->whereAdd('notice_id > ' . $since_id);
+ }
+
+ if ($max_id != 0) {
+ $inbox->whereAdd('notice_id <= ' . $max_id);
+ }
+
+ $inbox->orderBy('notice_id DESC');
+
+ if (!is_null($offset)) {
+ $inbox->limit($offset, $limit);
+ }
+
+ $ids = array();
+
+ if ($inbox->find()) {
+ while ($inbox->fetch()) {
+ $ids[] = $inbox->notice_id;
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * 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
+ */
+
+ function getSubscribers($offset=0, $limit=null, $since=0, $upto=0)
+ {
+ $subs = new Profile();
+ $sub = new Profile_tag_subscription();
+ $sub->profile_tag_id = $this->id;
+
+ $subs->joinAdd($sub);
+ $subs->selectAdd('unix_timestamp(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
+ */
+
+ function getUserSubscribers()
+ {
+ // XXX: cache this
+
+ $user = new User();
+ if(common_config('db','quote_identifiers'))
+ $user_table = '"user"';
+ else $user_table = 'user';
+
+ $qry =
+ 'SELECT id ' .
+ 'FROM '. $user_table .' JOIN profile_tag_subscription '.
+ 'ON '. $user_table .'.id = profile_tag_subscription.profile_id ' .
+ 'WHERE profile_tag_subscription.profile_tag_id = %d ';
+
+ $user->query(sprintf($qry, $this->id));
+
+ $ids = array();
+
+ 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
+ */
+
+ 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
+ */
+
+ function getTagged($offset=0, $limit=null, $since=0, $upto=0)
+ {
+ $tagged = new Profile();
+ $tagged->joinAdd(array('id', 'profile_tag:tagged'));
+
+ #@fixme: postgres
+ $tagged->selectAdd('unix_timestamp(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
+ */
+
+ function delete()
+ {
+ // 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);
+
+ return parent::delete();
+ }
+
+ /**
+ * Update a people tag gracefully
+ * also change "tag" fields in profile_tag table
+ *
+ * @param Profile_list $orig Object's original form
+ *
+ * @return boolean success
+ */
+
+ function update($orig=null)
+ {
+ $result = true;
+
+ if (!is_object($orig) && !$orig instanceof Profile_list) {
+ parent::update($orig);
+ }
+
+ // if original tag was different
+ // check to see if the new tag already exists
+ // if not, rename the tag correctly
+ if($orig->tag != $this->tag || $orig->tagger != $this->tagger) {
+ $existing = Profile_list::getByTaggerAndTag($this->tagger, $this->tag);
+ if(!empty($existing)) {
+ 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($orig, $this);
+ }
+ parent::update($orig);
+ return $result;
+ }
+
+ /**
+ * return an xml string representing this people tag
+ * as the author of an atom feed
+ *
+ * @return string atom author element
+ */
+
+ 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 the subject of an activitystreams feed.
+ *
+ * @return string activitystreams subject
+ */
+
+ function asActivitySubject()
+ {
+ return $this->asActivityNoun('subject');
+ }
+
+ /**
+ * 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
+ */
+
+ 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
+ */
+
+ function taggedCount($recount=false)
+ {
+ if (!$recount) {
+ return $this->tagged_count;
+ }
+
+ $tags = new Profile_tag();
+ $tags->tag = $this->tag;
+ $tags->tagger = $this->tagger;
+ $orig = clone($this);
+ $this->tagged_count = (int) $tags->count('distinct tagged');
+ $this->update($orig);
+
+ return $this->tagged_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
+ */
+
+ function subscriberCount($recount=false)
+ {
+ if ($recount) {
+ return $this->subscriber_count;
+ }
+
+ $sub = new Profile_tag_subscription();
+ $sub->profile_tag_id = $this->id;
+ $orig = clone($this);
+ $this->subscriber_count = (int) $sub->count('distinct profile_id');
+ $this->update($orig);
+
+ return $this->subscriber_count;
+ }
+
+ /**
+ * 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
+ */
+
+ 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
+ */
+
+ 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
+ */
+
+ 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?
+ */
+
+ 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
+ */
+ static function saveNew($fields) {
+
+ extract($fields);
+
+ $ptag = new Profile_list();
+
+ $ptag->query('BEGIN');
+
+ if (empty($tagger)) {
+ throw new Exception(_('No tagger specified.'));
+ }
+
+ if (empty($tag)) {
+ 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__);
+ 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__);
+ throw new ServerException(_('Could not set profile tag URI.'));
+ }
+ }
+
+ if (!isset($mainpage) || empty($mainpage)) {
+ $orig = clone($ptag);
+ $user = User::staticGet('id', $ptag->tagger);
+ if(!empty($user)) {
+ $ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'tagger' => $user->nickname));
+ } 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__);
+ 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.
+
+ static function getAtCursor($fn, $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;
+ }
+
+ } else if($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) array_pop($items); // excess item.
+
+ // 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;
+ }
+ } else if($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
+ */
+
+ 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
+ */
+
+ 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
+ */
+
+ static function getByKeys($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);
+ }
+ }
+}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- static function getTags($tagger, $tagged) {
- $tags = array();
+ function pkeyGet($kv) {
+ return Memcached_DataObject::pkeyGet('Profile_tag', $kv);
+ }
+
+ function links()
+ {
+ return array('tagger,tag' => 'profile_list:tagger,tag');
+ }
+
+ function getMeta()
+ {
+ return Profile_list::pkeyGet(array('tagger' => $this->tagger, 'tag' => $this->tag));
+ }
+
+ static function getTags($tagger, $tagged, $auth_user=null) {
+
+ $profile_list = new Profile_list();
+ $include_priv = 1;
+
+ if (!($auth_user instanceof User ||
+ $auth_user instanceof Profile) ||
+ ($auth_user->id !== $tagger)) {
- # XXX: store this in memcached
+ $profile_list->private = false;
+ $include_priv = 0;
+ }
+
+ $key = sprintf('profile_tag:tagger_tagged_privacy:%d-%d-%d', $tagger, $tagged, $include_priv);
+ $tags = Profile_list::getCached($key);
+ if ($tags !== false) {
+ return $tags;
+ }
$profile_tag = new Profile_tag();
- $profile_tag->tagger = $tagger;
+ $profile_list->tagger = $tagger;
$profile_tag->tagged = $tagged;
- $profile_tag->find();
+ $profile_list->selectAdd();
+
+ // only fetch id, tag, mainpage and
+ // private hoping this will be faster
+ $profile_list->selectAdd('profile_list.id, ' .
+ 'profile_list.tag, ' .
+ 'profile_list.mainpage, ' .
+ 'profile_list.private');
+ $profile_list->joinAdd($profile_tag);
+ $profile_list->find();
+
+ Profile_list::setCache($key, $profile_list);
- while ($profile_tag->fetch()) {
- $tags[] = $profile_tag->tag;
+ return $profile_list;
+ }
+
+ static function getTagsArray($tagger, $tagged, $auth_user_id=null)
+ {
+ $ptag = new Profile_tag();
+ $ptag->tagger = $tagger;
+ $ptag->tagged = $tagged;
+
+ if ($tagger != $auth_user_id) {
+ $list = new Profile_list();
+ $list->private = false;
+ $ptag->joinAdd($list);
+ $ptag->selectAdd();
+ $ptag->selectAdd('profile_tag.tag');
}
- $profile_tag->free();
+ $tags = array();
+ $ptag->find();
+ while ($ptag->fetch()) {
+ $tags[] = $ptag->tag;
+ }
+ $ptag->free();
return $tags;
}
- static function setTags($tagger, $tagged, $newtags) {
+ static function setTags($tagger, $tagged, $newtags, $privacy=array()) {
+
$newtags = array_unique($newtags);
- $oldtags = Profile_tag::getTags($tagger, $tagged);
+ $oldtags = self::getTagsArray($tagger, $tagged, $tagger);
+
+ $ptag = new Profile_tag();
- # Delete stuff that's old that not in new
+ # Delete stuff that's in old and not in new
$to_delete = array_diff($oldtags, $newtags);
$to_insert = array_diff($newtags, $oldtags);
- $profile_tag = new Profile_tag();
+ foreach ($to_delete as $deltag) {
+ self::unTag($tagger, $tagged, $deltag);
+ }
- $profile_tag->tagger = $tagger;
- $profile_tag->tagged = $tagged;
+ foreach ($to_insert as $instag) {
+ $private = isset($privacy[$instag]) ? $privacy[$instag] : false;
+ self::setTag($tagger, $tagged, $instag, null, $private);
+ }
+ return true;
+ }
+
+ # set a single tag
+ static function setTag($tagger, $tagged, $tag, $desc=null, $private=false) {
+
+ $ptag = Profile_tag::pkeyGet(array('tagger' => $tagger,
+ 'tagged' => $tagged,
+ 'tag' => $tag));
+
+ # if tag already exists, return it
+ if(!empty($ptag)) {
+ return $ptag;
+ }
+
+ $tagger_profile = Profile::staticGet('id', $tagger);
+ $tagged_profile = Profile::staticGet('id', $tagged);
+
+ if (Event::handle('StartTagProfile', array($tagger_profile, $tagged_profile, $tag))) {
+
+ if (!$tagger_profile->canTag($tagged_profile)) {
+ throw new ClientException(_('You cannot tag this user.'));
+ return false;
+ }
+
+ $tags = new Profile_list();
+ $tags->tagger = $tagger;
+ $count = (int) $tags->count('distinct tag');
+
+ if ($count >= common_config('peopletag', 'maxtags')) {
+ throw new ClientException(sprintf(_('You already have created %d or more tags ' .
+ 'which is the maximum allowed number of tags. ' .
+ 'Try using or deleting some existing tags.'),
+ common_config('peopletag', 'maxtags')));
+ return false;
+ }
+
+ $plist = new Profile_list();
+ $plist->query('BEGIN');
+
+ $profile_list = Profile_list::ensureTag($tagger, $tag, $desc, $private);
+
+ if ($profile_list->taggedCount() >= common_config('peopletag', 'maxpeople')) {
+ throw new ClientException(sprintf(_('You already have %d or more people tagged %s ' .
+ 'which is the maximum allowed number.' .
+ 'Try untagging others with the same tag first.'),
+ common_config('peopletag', 'maxpeople'), $tag));
+ return false;
+ }
+
+ $newtag = new Profile_tag();
+
+ $newtag->tagger = $tagger;
+ $newtag->tagged = $tagged;
+ $newtag->tag = $tag;
+
+ $result = $newtag->insert();
- $profile_tag->query('BEGIN');
- foreach ($to_delete as $deltag) {
- $profile_tag->tag = $deltag;
- $result = $profile_tag->delete();
if (!$result) {
- common_log_db_error($profile_tag, 'DELETE', __FILE__);
+ common_log_db_error($newtag, 'INSERT', __FILE__);
+ return false;
+ }
+
+ try {
+ $plist->query('COMMIT');
+ Event::handle('EndTagProfile', array($newtag));
+ } catch (Exception $e) {
+ $newtag->delete();
+ $profile_list->delete();
+ throw $e;
return false;
}
+
+ $profile_list->taggedCount(true);
+ self::blowCaches($tagger, $tagged);
}
- foreach ($to_insert as $instag) {
- $profile_tag->tag = $instag;
- $result = $profile_tag->insert();
+ return $newtag;
+ }
+
+ static function unTag($tagger, $tagged, $tag) {
+ $ptag = Profile_tag::pkeyGet(array('tagger' => $tagger,
+ 'tagged' => $tagged,
+ 'tag' => $tag));
+ if (!$ptag) {
+ return true;
+ }
+
+ if (Event::handle('StartUntagProfile', array($ptag))) {
+ $orig = clone($ptag);
+ $result = $ptag->delete();
if (!$result) {
- common_log_db_error($profile_tag, 'INSERT', __FILE__);
+ common_log_db_error($this, 'DELETE', __FILE__);
return false;
}
+ Event::handle('EndUntagProfile', array($orig));
+ if ($result) {
+ $profile_list = Profile_list::pkeyGet(array('tag' => $tag, 'tagger' => $tagger));
+ $profile_list->taggedCount(true);
+ self::blowCaches($tagger, $tagged);
+ return true;
+ }
+ return false;
}
+ }
+
+ // @fixme: move this to Profile_list?
+ static function cleanup($profile_list) {
+ $ptag = new Profile_tag();
+ $ptag->tagger = $profile_list->tagger;
+ $ptag->tag = $profile_list->tag;
+ $ptag->find();
+
+ while($ptag->fetch()) {
+ if (Event::handle('StartUntagProfile', array($ptag))) {
+ $orig = clone($ptag);
+ $result = $ptag->delete();
+ if (!$result) {
+ common_log_db_error($this, 'DELETE', __FILE__);
+ }
+ Event::handle('EndUntagProfile', array($orig));
+ }
+ }
+ }
- $profile_tag->query('COMMIT');
+ // move a tag!
+ static function moveTag($orig, $new) {
+ $tags = new Profile_tag();
+ $qry = 'UPDATE profile_tag SET ' .
+ 'tag = "%s", tagger = "%s" ' .
+ 'WHERE tag = "%s" ' .
+ 'AND tagger = "%s"';
+ $result = $tags->query(sprintf($qry, $new->tag, $new->tagger,
+ $orig->tag, $orig->tagger));
+ if (!$result) {
+ common_log_db_error($tags, 'UPDATE', __FILE__);
+ return false;
+ }
return true;
}
- # Return profiles with a given tag
- static function getTagged($tagger, $tag) {
- $profile = new Profile();
- $profile->query('SELECT profile.* ' .
- 'FROM profile JOIN profile_tag ' .
- 'ON profile.id = profile_tag.tagged ' .
- 'WHERE profile_tag.tagger = ' . $tagger . ' ' .
- 'AND profile_tag.tag = "' . $tag . '" ');
- $tagged = array();
- while ($profile->fetch()) {
- $tagged[] = clone($profile);
+ static function blowCaches($tagger, $tagged) {
+ foreach (array(0, 1) as $perm) {
+ self::blow(sprintf('profile_tag:tagger_tagged_privacy:%d-%d-%d', $tagger, $tagged, $perm));
}
- return $tagged;
+ return true;
}
}