]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/Profile_list.php
Update/add translator documentation.
[quix0rs-gnu-social.git] / classes / Profile_list.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Affero General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
13  * GNU Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program.     If not, see <http://www.gnu.org/licenses/>.
17  *
18  * @category Notices
19  * @package  StatusNet
20  * @author   Shashi Gowda <connect2shashi@gmail.com>
21  * @license  GNU Affero General Public License http://www.gnu.org/licenses/
22  */
23
24 if (!defined('STATUSNET') && !defined('LACONICA')) {
25     exit(1);
26 }
27
28 /**
29  * Table Definition for profile_list
30  */
31 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
32
33 class Profile_list extends Memcached_DataObject
34 {
35     ###START_AUTOCODE
36     /* the code below is auto generated do not remove the above tag */
37
38     public $__table = 'profile_list';                      // table name
39     public $id;                              // int(4)  primary_key not_null
40     public $tagger;                          // int(4)
41     public $tag;                             // varchar(64)
42     public $description;                     // text
43     public $private;                         // tinyint(1)
44     public $created;                         // datetime   not_null default_0000-00-00%2000%3A00%3A00
45     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
46     public $uri;                             // varchar(255)  unique_key
47     public $mainpage;                        // varchar(255)
48     public $tagged_count;                    // smallint
49     public $subscriber_count;                // smallint
50
51     /* Static get */
52     function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Profile_list',$k,$v); }
53
54     /* the code above is auto generated do not remove the tag below */
55     ###END_AUTOCODE
56
57     /**
58      * return a profile_list record, given its tag and tagger.
59      *
60      * @param array $kv ideally array('tag' => $tag, 'tagger' => $tagger)
61      *
62      * @return Profile_list a Profile_list object with the given tag and tagger.
63      */
64
65     function pkeyGet($kv)
66     {
67         return Memcached_DataObject::pkeyGet('Profile_list', $kv);
68     }
69
70     /**
71      * get the tagger of this profile_list object
72      *
73      * @return Profile the tagger
74      */
75
76     function getTagger()
77     {
78         return Profile::staticGet('id', $this->tagger);
79     }
80
81     /**
82      * return a string to identify this
83      * profile_list in the user interface etc.
84      *
85      * @return String
86      */
87
88     function getBestName()
89     {
90         return $this->tag;
91     }
92
93     /**
94      * return a uri string for this profile_list
95      *
96      * @return String uri
97      */
98
99     function getUri()
100     {
101         $uri = null;
102         if (Event::handle('StartProfiletagGetUri', array($this, &$uri))) {
103             if (!empty($this->uri)) {
104                 $uri = $this->uri;
105             } else {
106                 $uri = common_local_url('profiletagbyid',
107                                         array('id' => $this->id, 'tagger_id' => $this->tagger));
108             }
109         }
110         Event::handle('EndProfiletagGetUri', array($this, &$uri));
111         return $uri;
112     }
113
114     /**
115      * return a url to the homepage of this item
116      *
117      * @return String home url
118      */
119
120     function homeUrl()
121     {
122         $url = null;
123         if (Event::handle('StartUserPeopletagHomeUrl', array($this, &$url))) {
124             // normally stored in mainpage, but older ones may be null
125             if (!empty($this->mainpage)) {
126                 $url = $this->mainpage;
127             } else {
128                 $url = common_local_url('showprofiletag',
129                                         array('tagger' => $this->getTagger()->nickname,
130                                               'tag'    => $this->tag));
131             }
132         }
133         Event::handle('EndUserPeopletagHomeUrl', array($this, &$url));
134         return $url;
135     }
136
137     /**
138      * return an immutable url for this object
139      *
140      * @return String permalink
141      */
142
143     function permalink()
144     {
145         $url = null;
146         if (Event::handle('StartProfiletagPermalink', array($this, &$url))) {
147             $url = common_local_url('profiletagbyid',
148                                     array('id' => $this->id));
149         }
150         Event::handle('EndProfiletagPermalink', array($this, &$url));
151         return $url;
152     }
153
154     /**
155      * Query notices by users associated with this tag,
156      * but first check the cache before hitting the DB.
157      *
158      * @param integer $offset   offset
159      * @param integer $limit    maximum no of results
160      * @param integer $since_id=null    since this id
161      * @param integer $max_id=null  maximum id in result
162      *
163      * @return Notice the query
164      */
165
166     function getNotices($offset, $limit, $since_id=null, $max_id=null)
167     {
168         $stream = new PeopletagNoticeStream($this);
169
170         return $stream->getNotices($offset, $limit, $since_id, $max_id);
171     }
172
173     /**
174      * Query notices by users associated with this tag from the database.
175      *
176      * @param integer $offset   offset
177      * @param integer $limit    maximum no of results
178      * @param integer $since_id=null    since this id
179      * @param integer $max_id=null  maximum id in result
180      *
181      * @return array array of notice ids.
182      */
183
184     function _streamDirect($offset, $limit, $since_id, $max_id)
185     {
186         $inbox = new Profile_tag_inbox();
187
188         $inbox->profile_tag_id = $this->id;
189
190         $inbox->selectAdd();
191         $inbox->selectAdd('notice_id');
192
193         if ($since_id != 0) {
194             $inbox->whereAdd('notice_id > ' . $since_id);
195         }
196
197         if ($max_id != 0) {
198             $inbox->whereAdd('notice_id <= ' . $max_id);
199         }
200
201         $inbox->orderBy('notice_id DESC');
202
203         if (!is_null($offset)) {
204             $inbox->limit($offset, $limit);
205         }
206
207         $ids = array();
208
209         if ($inbox->find()) {
210             while ($inbox->fetch()) {
211                 $ids[] = $inbox->notice_id;
212             }
213         }
214
215         return $ids;
216     }
217
218     /**
219      * Get subscribers (local and remote) to this people tag
220      * Order by reverse chronology
221      *
222      * @param integer $offset   offset
223      * @param integer $limit    maximum no of results
224      * @param integer $since_id=null    since unix timestamp
225      * @param integer $upto=null  maximum unix timestamp when subscription was made
226      *
227      * @return Profile results
228      */
229
230     function getSubscribers($offset=0, $limit=null, $since=0, $upto=0)
231     {
232         $subs = new Profile();
233         $sub = new Profile_tag_subscription();
234         $sub->profile_tag_id = $this->id;
235
236         $subs->joinAdd($sub);
237         $subs->selectAdd('unix_timestamp(profile_tag_subscription.' .
238                          'created) as "cursor"');
239
240         if ($since != 0) {
241             $subs->whereAdd('cursor > ' . $since);
242         }
243
244         if ($upto != 0) {
245             $subs->whereAdd('cursor <= ' . $upto);
246         }
247
248         if ($limit != null) {
249             $subs->limit($offset, $limit);
250         }
251
252         $subs->orderBy('profile_tag_subscription.created DESC');
253         $subs->find();
254
255         return $subs;
256     }
257
258     /**
259      * Get all and only local subscribers to this people tag
260      * used for distributing notices to user inboxes.
261      *
262      * @return array ids of users
263      */
264
265     function getUserSubscribers()
266     {
267         // XXX: cache this
268
269         $user = new User();
270         if(common_config('db','quote_identifiers'))
271             $user_table = '"user"';
272         else $user_table = 'user';
273
274         $qry =
275           'SELECT id ' .
276           'FROM '. $user_table .' JOIN profile_tag_subscription '.
277           'ON '. $user_table .'.id = profile_tag_subscription.profile_id ' .
278           'WHERE profile_tag_subscription.profile_tag_id = %d ';
279
280         $user->query(sprintf($qry, $this->id));
281
282         $ids = array();
283
284         while ($user->fetch()) {
285             $ids[] = $user->id;
286         }
287
288         $user->free();
289
290         return $ids;
291     }
292
293     /**
294      * Check to see if a given profile has
295      * subscribed to this people tag's timeline
296      *
297      * @param mixed $id User or Profile object or integer id
298      *
299      * @return boolean subscription status
300      */
301
302     function hasSubscriber($id)
303     {
304         if (!is_numeric($id)) {
305             $id = $id->id;
306         }
307
308         $sub = Profile_tag_subscription::pkeyGet(array('profile_tag_id' => $this->id,
309                                                        'profile_id'     => $id));
310         return !empty($sub);
311     }
312
313     /**
314      * Get profiles tagged with this people tag,
315      * include modified timestamp as a "cursor" field
316      * order by descending order of modified time
317      *
318      * @param integer $offset   offset
319      * @param integer $limit    maximum no of results
320      * @param integer $since_id=null    since unix timestamp
321      * @param integer $upto=null  maximum unix timestamp when subscription was made
322      *
323      * @return Profile results
324      */
325
326     function getTagged($offset=0, $limit=null, $since=0, $upto=0)
327     {
328         $tagged = new Profile();
329         $tagged->joinAdd(array('id', 'profile_tag:tagged'));
330
331         #@fixme: postgres
332         $tagged->selectAdd('unix_timestamp(profile_tag.modified) as "cursor"');
333         $tagged->whereAdd('profile_tag.tagger = '.$this->tagger);
334         $tagged->whereAdd("profile_tag.tag = '{$this->tag}'");
335
336         if ($since != 0) {
337             $tagged->whereAdd('cursor > ' . $since);
338         }
339
340         if ($upto != 0) {
341             $tagged->whereAdd('cursor <= ' . $upto);
342         }
343
344         if ($limit != null) {
345             $tagged->limit($offset, $limit);
346         }
347
348         $tagged->orderBy('profile_tag.modified DESC');
349         $tagged->find();
350
351         return $tagged;
352     }
353
354     /**
355      * Gracefully delete one or many people tags
356      * along with their members and subscriptions data
357      *
358      * @return boolean success
359      */
360
361     function delete()
362     {
363         // force delete one item at a time.
364         if (empty($this->id)) {
365             $this->find();
366             while ($this->fetch()) {
367                 $this->delete();
368             }
369         }
370
371         Profile_tag::cleanup($this);
372         Profile_tag_subscription::cleanup($this);
373
374         return parent::delete();
375     }
376
377     /**
378      * Update a people tag gracefully
379      * also change "tag" fields in profile_tag table
380      *
381      * @param Profile_list $orig    Object's original form
382      *
383      * @return boolean success
384      */
385
386     function update($orig=null)
387     {
388         $result = true;
389
390         if (!is_object($orig) && !$orig instanceof Profile_list) {
391             parent::update($orig);
392         }
393
394         // if original tag was different
395         // check to see if the new tag already exists
396         // if not, rename the tag correctly
397         if($orig->tag != $this->tag || $orig->tagger != $this->tagger) {
398             $existing = Profile_list::getByTaggerAndTag($this->tagger, $this->tag);
399             if(!empty($existing)) {
400                 // TRANS: Server exception.
401                 throw new ServerException(_('The tag you are trying to rename ' .
402                                             'to already exists.'));
403             }
404             // move the tag
405             // XXX: allow OStatus plugin to send out profile tag
406             $result = Profile_tag::moveTag($orig, $this);
407         }
408         parent::update($orig);
409         return $result;
410     }
411
412     /**
413      * return an xml string representing this people tag
414      * as the author of an atom feed
415      *
416      * @return string atom author element
417      */
418
419     function asAtomAuthor()
420     {
421         $xs = new XMLStringer(true);
422
423         $tagger = $this->getTagger();
424         $xs->elementStart('author');
425         $xs->element('name', null, '@' . $tagger->nickname . '/' . $this->tag);
426         $xs->element('uri', null, $this->permalink());
427         $xs->elementEnd('author');
428
429         return $xs->getString();
430     }
431
432     /**
433      * return an xml string to represent this people tag
434      * as the subject of an activitystreams feed.
435      *
436      * @return string activitystreams subject
437      */
438
439     function asActivitySubject()
440     {
441         return $this->asActivityNoun('subject');
442     }
443
444     /**
445      * return an xml string to represent this people tag
446      * as a noun in an activitystreams feed.
447      *
448      * @param string $element the xml tag
449      *
450      * @return string activitystreams noun
451      */
452
453     function asActivityNoun($element)
454     {
455         $noun = ActivityObject::fromPeopletag($this);
456         return $noun->asString('activity:' . $element);
457     }
458
459     /**
460      * get the cached number of profiles tagged with this
461      * people tag, re-count if the argument is true.
462      *
463      * @param boolean $recount  whether to ignore cache
464      *
465      * @return integer count
466      */
467
468     function taggedCount($recount=false)
469     {
470         if (!$recount) {
471             return $this->tagged_count;
472         }
473
474         $tags = new Profile_tag();
475         $tags->tag = $this->tag;
476         $tags->tagger = $this->tagger;
477         $orig = clone($this);
478         $this->tagged_count = (int) $tags->count('distinct tagged');
479         $this->update($orig);
480
481         return $this->tagged_count;
482     }
483
484     /**
485      * get the cached number of profiles subscribed to this
486      * people tag, re-count if the argument is true.
487      *
488      * @param boolean $recount  whether to ignore cache
489      *
490      * @return integer count
491      */
492
493     function subscriberCount($recount=false)
494     {
495         if ($recount) {
496             return $this->subscriber_count;
497         }
498
499         $sub = new Profile_tag_subscription();
500         $sub->profile_tag_id = $this->id;
501         $orig = clone($this);
502         $this->subscriber_count = (int) $sub->count('distinct profile_id');
503         $this->update($orig);
504
505         return $this->subscriber_count;
506     }
507
508     /**
509      * get the Profile_list object by the
510      * given tagger and with given tag
511      *
512      * @param integer $tagger   the id of the creator profile
513      * @param integer $tag      the tag
514      *
515      * @return integer count
516      */
517
518     static function getByTaggerAndTag($tagger, $tag)
519     {
520         $ptag = Profile_list::pkeyGet(array('tagger' => $tagger, 'tag' => $tag));
521         return $ptag;
522     }
523
524     /**
525      * create a profile_list record for a tag, tagger pair
526      * if it doesn't exist, return it.
527      *
528      * @param integer $tagger   the tagger
529      * @param string  $tag      the tag
530      * @param string  $description description
531      * @param boolean $private  protected or not
532      *
533      * @return Profile_list the people tag object
534      */
535
536     static function ensureTag($tagger, $tag, $description=null, $private=false)
537     {
538         $ptag = Profile_list::getByTaggerAndTag($tagger, $tag);
539
540         if(empty($ptag->id)) {
541             $args = array(
542                 'tag' => $tag,
543                 'tagger' => $tagger,
544                 'description' => $description,
545                 'private' => $private
546             );
547
548             $new_tag = Profile_list::saveNew($args);
549
550             return $new_tag;
551         }
552         return $ptag;
553     }
554
555     /**
556      * get the maximum number of characters
557      * that can be used in the description of
558      * a people tag.
559      *
560      * determined by $config['peopletag']['desclimit']
561      * if not set, falls back to $config['site']['textlimit']
562      *
563      * @return integer maximum number of characters
564      */
565
566     static function maxDescription()
567     {
568         $desclimit = common_config('peopletag', 'desclimit');
569         // null => use global limit (distinct from 0!)
570         if (is_null($desclimit)) {
571             $desclimit = common_config('site', 'textlimit');
572         }
573         return $desclimit;
574     }
575
576     /**
577      * check if the length of given text exceeds
578      * character limit.
579      *
580      * @param string $desc  the description
581      *
582      * @return boolean is the descripition too long?
583      */
584
585     static function descriptionTooLong($desc)
586     {
587         $desclimit = self::maxDescription();
588         return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
589     }
590
591     /**
592      * save a new people tag, this should be always used
593      * since it makes uri, homeurl, created and modified
594      * timestamps and performs checks.
595      *
596      * @param array $fields an array with fields and their values
597      *
598      * @return mixed Profile_list on success, false on fail
599      */
600     static function saveNew($fields) {
601         extract($fields);
602
603         $ptag = new Profile_list();
604
605         $ptag->query('BEGIN');
606
607         if (empty($tagger)) {
608             // TRANS: Server exception saving new tag without having a tagger specified.
609             throw new Exception(_('No tagger specified.'));
610         }
611
612         if (empty($tag)) {
613             // TRANS: Server exception saving new tag without having a tag specified.
614             throw new Exception(_('No tag specified.'));
615         }
616
617         if (empty($mainpage)) {
618             $mainpage = null;
619         }
620
621         if (empty($uri)) {
622             // fill in later...
623             $uri = null;
624         }
625
626         if (empty($mainpage)) {
627             $mainpage = null;
628         }
629
630         if (empty($description)) {
631             $description = null;
632         }
633
634         if (empty($private)) {
635             $private = false;
636         }
637
638         $ptag->tagger      = $tagger;
639         $ptag->tag         = $tag;
640         $ptag->description = $description;
641         $ptag->private     = $private;
642         $ptag->uri         = $uri;
643         $ptag->mainpage    = $mainpage;
644         $ptag->created     = common_sql_now();
645         $ptag->modified    = common_sql_now();
646
647         $result = $ptag->insert();
648
649         if (!$result) {
650             common_log_db_error($ptag, 'INSERT', __FILE__);
651             // TRANS: Server exception saving new tag.
652             throw new ServerException(_('Could not create profile tag.'));
653         }
654
655         if (!isset($uri) || empty($uri)) {
656             $orig = clone($ptag);
657             $ptag->uri = common_local_url('profiletagbyid', array('id' => $ptag->id, 'tagger_id' => $ptag->tagger));
658             $result = $ptag->update($orig);
659             if (!$result) {
660                 common_log_db_error($ptag, 'UPDATE', __FILE__);
661             // TRANS: Server exception saving new tag.
662                 throw new ServerException(_('Could not set profile tag URI.'));
663             }
664         }
665
666         if (!isset($mainpage) || empty($mainpage)) {
667             $orig = clone($ptag);
668             $user = User::staticGet('id', $ptag->tagger);
669             if(!empty($user)) {
670                 $ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'tagger' => $user->nickname));
671             } else {
672                 $ptag->mainpage = $uri; // assume this is a remote peopletag and the uri works
673             }
674
675             $result = $ptag->update($orig);
676             if (!$result) {
677                 common_log_db_error($ptag, 'UPDATE', __FILE__);
678                 // TRANS: Server exception saving new tag.
679                 throw new ServerException(_('Could not set profile tag mainpage.'));
680             }
681         }
682         return $ptag;
683     }
684
685     /**
686      * get all items at given cursor position for api
687      *
688      * @param callback $fn  a function that takes the following arguments in order:
689      *                      $offset, $limit, $since_id, $max_id
690      *                      and returns a Profile_list object after making the DB query
691      * @param array $args   arguments required for $fn
692      * @param integer $cursor   the cursor
693      * @param integer $count    max. number of results
694      *
695      * Algorithm:
696      * - if cursor is 0, return empty list
697      * - if cursor is -1, get first 21 items, next_cursor = 20th prev_cursor = 0
698      * - if cursor is +ve get 22 consecutive items before starting at cursor
699      *   - return items[1..20] if items[0] == cursor else return items[0..21]
700      *   - prev_cursor = items[1]
701      *   - next_cursor = id of the last item being returned
702      *
703      * - if cursor is -ve get 22 consecutive items after cursor starting at cursor
704      *   - return items[1..20]
705      *
706      * @returns array (array (mixed items), int next_cursor, int previous_cursor)
707      */
708
709      // XXX: This should be in Memcached_DataObject... eventually.
710
711     static function getAtCursor($fn, $args, $cursor, $count=20)
712     {
713         $items = array();
714
715         $since_id = 0;
716         $max_id = 0;
717         $next_cursor = 0;
718         $prev_cursor = 0;
719
720         if($cursor > 0) {
721             // if cursor is +ve fetch $count+2 items before cursor starting at cursor
722             $max_id = $cursor;
723             $fn_args = array_merge($args, array(0, $count+2, 0, $max_id));
724             $list = call_user_func_array($fn, $fn_args);
725             while($list->fetch()) {
726                 $items[] = clone($list);
727             }
728
729             if ((isset($items[0]->cursor) && $items[0]->cursor == $cursor) ||
730                 $items[0]->id == $cursor) {
731                 array_shift($items);
732                 $prev_cursor = isset($items[0]->cursor) ?
733                     -$items[0]->cursor : -$items[0]->id;
734             } else {
735                 if (count($items) > $count+1) {
736                     array_shift($items);
737                 }
738                 // this means the cursor item has been deleted, check to see if there are more
739                 $fn_args = array_merge($args, array(0, 1, $cursor));
740                 $more = call_user_func($fn, $fn_args);
741                 if (!$more->fetch() || empty($more)) {
742                     // no more items.
743                     $prev_cursor = 0;
744                 } else {
745                     $prev_cursor = isset($items[0]->cursor) ?
746                         -$items[0]->cursor : -$items[0]->id;
747                 }
748             }
749
750             if (count($items)==$count+1) {
751                 // this means there is a next page.
752                 $next = array_pop($items);
753                 $next_cursor = isset($next->cursor) ?
754                     $items[$count-1]->cursor : $items[$count-1]->id;
755             }
756
757         } else if($cursor < -1) {
758             // if cursor is -ve fetch $count+2 items created after -$cursor-1
759             $cursor = abs($cursor);
760             $since_id = $cursor-1;
761
762             $fn_args = array_merge($args, array(0, $count+2, $since_id));
763             $list = call_user_func_array($fn, $fn_args);
764             while($list->fetch()) {
765                 $items[] = clone($list);
766             }
767
768             $end = count($items)-1;
769             if ((isset($items[$end]->cursor) && $items[$end]->cursor == $cursor) ||
770                 $items[$end]->id == $cursor) {
771                 array_pop($items);
772                 $next_cursor = isset($items[$end-1]->cursor) ?
773                     $items[$end-1]->cursor : $items[$end-1]->id;
774             } else {
775                 $next_cursor = isset($items[$end]->cursor) ?
776                     $items[$end]->cursor : $items[$end]->id;
777                 if ($end > $count) array_pop($items); // excess item.
778
779                 // check if there are more items for next page
780                 $fn_args = array_merge($args, array(0, 1, 0, $cursor));
781                 $more = call_user_func_array($fn, $fn_args);
782                 if (!$more->fetch() || empty($more)) {
783                     $next_cursor = 0;
784                 }
785             }
786
787             if (count($items) == $count+1) {
788                 // this means there is a previous page.
789                 $prev = array_shift($items);
790                 $prev_cursor = isset($prev->cursor) ?
791                     -$items[0]->cursor : -$items[0]->id;
792             }
793         } else if($cursor == -1) {
794             $fn_args = array_merge($args, array(0, $count+1));
795             $list = call_user_func_array($fn, $fn_args);
796
797             while($list->fetch()) {
798                 $items[] = clone($list);
799             }
800
801             if (count($items)==$count+1) {
802                 $next = array_pop($items);
803                 if(isset($next->cursor)) {
804                     $next_cursor = $items[$count-1]->cursor;
805                 } else {
806                     $next_cursor = $items[$count-1]->id;
807                 }
808             }
809
810         }
811         return array($items, $next_cursor, $prev_cursor);
812     }
813
814     /**
815      * save a collection of people tags into the cache
816      *
817      * @param string $ckey  cache key
818      * @param Profile_list &$tag the results to store
819      * @param integer $offset   offset for slicing results
820      * @param integer $limit    maximum number of results
821      *
822      * @return boolean success
823      */
824
825     static function setCache($ckey, &$tag, $offset=0, $limit=null) {
826         $cache = Cache::instance();
827         if (empty($cache)) {
828             return false;
829         }
830         $str = '';
831         $tags = array();
832         while ($tag->fetch()) {
833             $str .= $tag->tagger . ':' . $tag->tag . ';';
834             $tags[] = clone($tag);
835         }
836         $str = substr($str, 0, -1);
837         if ($offset>=0 && !is_null($limit)) {
838             $tags = array_slice($tags, $offset, $limit);
839         }
840
841         $tag = new ArrayWrapper($tags);
842
843         return self::cacheSet($ckey, $str);
844     }
845
846     /**
847      * get people tags from the cache
848      *
849      * @param string $ckey  cache key
850      * @param integer $offset   offset for slicing
851      * @param integer $limit    limit
852      *
853      * @return Profile_list results
854      */
855
856     static function getCached($ckey, $offset=0, $limit=null) {
857
858         $keys_str = self::cacheGet($ckey);
859         if ($keys_str === false) {
860             return false;
861         }
862
863         $pairs = explode(';', $keys_str);
864         $keys = array();
865         foreach ($pairs as $pair) {
866             $keys[] = explode(':', $pair);
867         }
868
869         if ($offset>=0 && !is_null($limit)) {
870             $keys = array_slice($keys, $offset, $limit);
871         }
872         return self::getByKeys($keys);
873     }
874
875     /**
876      * get Profile_list objects from the database
877      * given their (tag, tagger) key pairs.
878      *
879      * @param array $keys   array of array(tagger, tag)
880      *
881      * @return Profile_list results
882      */
883
884     static function getByKeys($keys) {
885         $cache = Cache::instance();
886
887         if (!empty($cache)) {
888             $tags = array();
889
890             foreach ($keys as $key) {
891                 $t = Profile_list::getByTaggerAndTag($key[0], $key[1]);
892                 if (!empty($t)) {
893                     $tags[] = $t;
894                 }
895             }
896             return new ArrayWrapper($tags);
897         } else {
898             $tag = new Profile_list();
899             if (empty($keys)) {
900                 //if no IDs requested, just return the tag object
901                 return $tag;
902             }
903
904             $pairs = array();
905             foreach ($keys as $key) {
906                 $pairs[] = '(' . $key[0] . ', "' . $key[1] . '")';
907             }
908
909             $tag->whereAdd('(tagger, tag) in (' . implode(', ', $pairs) . ')');
910
911             $tag->find();
912
913             $temp = array();
914
915             while ($tag->fetch()) {
916                 $temp[$tag->tagger.'-'.$tag->tag] = clone($tag);
917             }
918
919             $wrapped = array();
920
921             foreach ($keys as $key) {
922                 $id = $key[0].'-'.$key[1];
923                 if (array_key_exists($id, $temp)) {
924                     $wrapped[] = $temp[$id];
925                 }
926             }
927
928             return new ArrayWrapper($wrapped);
929         }
930     }
931 }