]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/Profile.php
Tweaking request_queue -> group_join_queue, easier to deal with the indexes and keys...
[quix0rs-gnu-social.git] / classes / Profile.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22 /**
23  * Table Definition for profile
24  */
25 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
26
27 class Profile extends Memcached_DataObject
28 {
29     ###START_AUTOCODE
30     /* the code below is auto generated do not remove the above tag */
31
32     public $__table = 'profile';                         // table name
33     public $id;                              // int(4)  primary_key not_null
34     public $nickname;                        // varchar(64)  multiple_key not_null
35     public $fullname;                        // varchar(255)  multiple_key
36     public $profileurl;                      // varchar(255)
37     public $homepage;                        // varchar(255)  multiple_key
38     public $bio;                             // text()  multiple_key
39     public $location;                        // varchar(255)  multiple_key
40     public $lat;                             // decimal(10,7)
41     public $lon;                             // decimal(10,7)
42     public $location_id;                     // int(4)
43     public $location_ns;                     // int(4)
44     public $created;                         // datetime()   not_null
45     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
46
47     /* Static get */
48     function staticGet($k,$v=NULL) {
49         return Memcached_DataObject::staticGet('Profile',$k,$v);
50     }
51
52     /* the code above is auto generated do not remove the tag below */
53     ###END_AUTOCODE
54
55     function getUser()
56     {
57         return User::staticGet('id', $this->id);
58     }
59
60     function getAvatar($width, $height=null)
61     {
62         if (is_null($height)) {
63             $height = $width;
64         }
65         return Avatar::pkeyGet(array('profile_id' => $this->id,
66                                      'width' => $width,
67                                      'height' => $height));
68     }
69
70     function getOriginalAvatar()
71     {
72         $avatar = DB_DataObject::factory('avatar');
73         $avatar->profile_id = $this->id;
74         $avatar->original = true;
75         if ($avatar->find(true)) {
76             return $avatar;
77         } else {
78             return null;
79         }
80     }
81
82     function setOriginal($filename)
83     {
84         $imagefile = new ImageFile($this->id, Avatar::path($filename));
85
86         $avatar = new Avatar();
87         $avatar->profile_id = $this->id;
88         $avatar->width = $imagefile->width;
89         $avatar->height = $imagefile->height;
90         $avatar->mediatype = image_type_to_mime_type($imagefile->type);
91         $avatar->filename = $filename;
92         $avatar->original = true;
93         $avatar->url = Avatar::url($filename);
94         $avatar->created = DB_DataObject_Cast::dateTime(); # current time
95
96         # XXX: start a transaction here
97
98         if (!$this->delete_avatars() || !$avatar->insert()) {
99             @unlink(Avatar::path($filename));
100             return null;
101         }
102
103         foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
104             # We don't do a scaled one if original is our scaled size
105             if (!($avatar->width == $size && $avatar->height == $size)) {
106                 $scaled_filename = $imagefile->resize($size);
107
108                 //$scaled = DB_DataObject::factory('avatar');
109                 $scaled = new Avatar();
110                 $scaled->profile_id = $this->id;
111                 $scaled->width = $size;
112                 $scaled->height = $size;
113                 $scaled->original = false;
114                 $scaled->mediatype = image_type_to_mime_type($imagefile->type);
115                 $scaled->filename = $scaled_filename;
116                 $scaled->url = Avatar::url($scaled_filename);
117                 $scaled->created = DB_DataObject_Cast::dateTime(); # current time
118
119                 if (!$scaled->insert()) {
120                     return null;
121                 }
122             }
123         }
124
125         return $avatar;
126     }
127
128     /**
129      * Delete attached avatars for this user from the database and filesystem.
130      * This should be used instead of a batch delete() to ensure that files
131      * get removed correctly.
132      *
133      * @param boolean $original true to delete only the original-size file
134      * @return <type>
135      */
136     function delete_avatars($original=true)
137     {
138         $avatar = new Avatar();
139         $avatar->profile_id = $this->id;
140         $avatar->find();
141         while ($avatar->fetch()) {
142             if ($avatar->original) {
143                 if ($original == false) {
144                     continue;
145                 }
146             }
147             $avatar->delete();
148         }
149         return true;
150     }
151
152     /**
153      * Gets either the full name (if filled) or the nickname.
154      *
155      * @return string
156      */
157     function getBestName()
158     {
159         return ($this->fullname) ? $this->fullname : $this->nickname;
160     }
161
162     /**
163      * Gets the full name (if filled) with nickname as a parenthetical, or the nickname alone
164      * if no fullname is provided.
165      *
166      * @return string
167      */
168     function getFancyName()
169     {
170         if ($this->fullname) {
171             // TRANS: Full name of a profile or group followed by nickname in parens
172             return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname);
173         } else {
174             return $this->nickname;
175         }
176     }
177
178     /**
179      * Get the most recent notice posted by this user, if any.
180      *
181      * @return mixed Notice or null
182      */
183
184     function getCurrentNotice()
185     {
186         $notice = $this->getNotices(0, 1);
187
188         if ($notice->fetch()) {
189             if ($notice instanceof ArrayWrapper) {
190                 // hack for things trying to work with single notices
191                 return $notice->_items[0];
192             }
193             return $notice;
194         } else {
195             return null;
196         }
197     }
198
199     function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
200     {
201         $ids = Notice::stream(array($this, '_streamTaggedDirect'),
202                               array($tag),
203                               'profile:notice_ids_tagged:' . $this->id . ':' . $tag,
204                               $offset, $limit, $since_id, $max_id);
205         return Notice::getStreamByIds($ids);
206     }
207
208     function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
209     {
210         // XXX: I'm not sure this is going to be any faster. It probably isn't.
211         $ids = Notice::stream(array($this, '_streamDirect'),
212                               array(),
213                               'profile:notice_ids:' . $this->id,
214                               $offset, $limit, $since_id, $max_id);
215
216         return Notice::getStreamByIds($ids);
217     }
218
219     function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id)
220     {
221         // XXX It would be nice to do this without a join
222         // (necessary to do it efficiently on accounts with long history)
223
224         $notice = new Notice();
225
226         $query =
227           "select id from notice join notice_tag on id=notice_id where tag='".
228           $notice->escape($tag) .
229           "' and profile_id=" . intval($this->id);
230
231         $since = Notice::whereSinceId($since_id, 'id', 'notice.created');
232         if ($since) {
233             $query .= " and ($since)";
234         }
235
236         $max = Notice::whereMaxId($max_id, 'id', 'notice.created');
237         if ($max) {
238             $query .= " and ($max)";
239         }
240
241         $query .= ' order by notice.created DESC, id DESC';
242
243         if (!is_null($offset)) {
244             $query .= " LIMIT " . intval($limit) . " OFFSET " . intval($offset);
245         }
246
247         $notice->query($query);
248
249         $ids = array();
250
251         while ($notice->fetch()) {
252             $ids[] = $notice->id;
253         }
254
255         return $ids;
256     }
257
258     function _streamDirect($offset, $limit, $since_id, $max_id)
259     {
260         $notice = new Notice();
261
262         $notice->profile_id = $this->id;
263
264         $notice->selectAdd();
265         $notice->selectAdd('id');
266
267         Notice::addWhereSinceId($notice, $since_id);
268         Notice::addWhereMaxId($notice, $max_id);
269
270         $notice->orderBy('created DESC, id DESC');
271
272         if (!is_null($offset)) {
273             $notice->limit($offset, $limit);
274         }
275
276         $notice->find();
277
278         $ids = array();
279
280         while ($notice->fetch()) {
281             $ids[] = $notice->id;
282         }
283
284         return $ids;
285     }
286
287     function isMember($group)
288     {
289         $mem = new Group_member();
290
291         $mem->group_id = $group->id;
292         $mem->profile_id = $this->id;
293
294         if ($mem->find()) {
295             return true;
296         } else {
297             return false;
298         }
299     }
300
301     function isAdmin($group)
302     {
303         $mem = new Group_member();
304
305         $mem->group_id = $group->id;
306         $mem->profile_id = $this->id;
307         $mem->is_admin = 1;
308
309         if ($mem->find()) {
310             return true;
311         } else {
312             return false;
313         }
314     }
315
316     function getGroups($offset=0, $limit=null)
317     {
318         $qry =
319           'SELECT user_group.* ' .
320           'FROM user_group JOIN group_member '.
321           'ON user_group.id = group_member.group_id ' .
322           'WHERE group_member.profile_id = %d ' .
323           'ORDER BY group_member.created DESC ';
324
325         if ($offset>0 && !is_null($limit)) {
326             if ($offset) {
327                 if (common_config('db','type') == 'pgsql') {
328                     $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
329                 } else {
330                     $qry .= ' LIMIT ' . $offset . ', ' . $limit;
331                 }
332             }
333         }
334
335         $groups = new User_group();
336
337         $cnt = $groups->query(sprintf($qry, $this->id));
338
339         return $groups;
340     }
341
342     /**
343      * Request to join the given group.
344      * May throw exceptions on failure.
345      *
346      * @param User_group $group
347      * @return mixed: Group_member on success, Group_join_queue if pending approval, null on some cancels?
348      */
349     function joinGroup(User_group $group)
350     {
351         $ok = null;
352         if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) {
353             $ok = Group_join_queue::saveNew($this, $group);
354         } else {
355             if (Event::handle('StartJoinGroup', array($group, $this))) {
356                 $ok = Group_member::join($group->id, $this->id);
357                 Event::handle('EndJoinGroup', array($group, $this));
358             }
359         }
360         return $ok;
361     }
362
363     /**
364      * Leave a group that this profile is a member of.
365      *
366      * @param User_group $group 
367      */
368     function leaveGroup(User_group $group)
369     {
370         if (Event::handle('StartLeaveGroup', array($group, $this))) {
371             Group_member::leave($group->id, $this->id);
372             Event::handle('EndLeaveGroup', array($group, $this));
373         }
374     }
375
376     function avatarUrl($size=AVATAR_PROFILE_SIZE)
377     {
378         $avatar = $this->getAvatar($size);
379         if ($avatar) {
380             return $avatar->displayUrl();
381         } else {
382             return Avatar::defaultImage($size);
383         }
384     }
385
386     function getSubscriptions($offset=0, $limit=null)
387     {
388         $subs = Subscription::bySubscriber($this->id,
389                                            $offset,
390                                            $limit);
391
392         $profiles = array();
393
394         while ($subs->fetch()) {
395             $profile = Profile::staticGet($subs->subscribed);
396             if ($profile) {
397                 $profiles[] = $profile;
398             }
399         }
400
401         return new ArrayWrapper($profiles);
402     }
403
404     function getSubscribers($offset=0, $limit=null)
405     {
406         $subs = Subscription::bySubscribed($this->id,
407                                            $offset,
408                                            $limit);
409
410         $profiles = array();
411
412         while ($subs->fetch()) {
413             $profile = Profile::staticGet($subs->subscriber);
414             if ($profile) {
415                 $profiles[] = $profile;
416             }
417         }
418
419         return new ArrayWrapper($profiles);
420     }
421
422     function subscriptionCount()
423     {
424         $c = Cache::instance();
425
426         if (!empty($c)) {
427             $cnt = $c->get(Cache::key('profile:subscription_count:'.$this->id));
428             if (is_integer($cnt)) {
429                 return (int) $cnt;
430             }
431         }
432
433         $sub = new Subscription();
434         $sub->subscriber = $this->id;
435
436         $cnt = (int) $sub->count('distinct subscribed');
437
438         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
439
440         if (!empty($c)) {
441             $c->set(Cache::key('profile:subscription_count:'.$this->id), $cnt);
442         }
443
444         return $cnt;
445     }
446
447     function subscriberCount()
448     {
449         $c = Cache::instance();
450         if (!empty($c)) {
451             $cnt = $c->get(Cache::key('profile:subscriber_count:'.$this->id));
452             if (is_integer($cnt)) {
453                 return (int) $cnt;
454             }
455         }
456
457         $sub = new Subscription();
458         $sub->subscribed = $this->id;
459         $sub->whereAdd('subscriber != subscribed');
460         $cnt = (int) $sub->count('distinct subscriber');
461
462         if (!empty($c)) {
463             $c->set(Cache::key('profile:subscriber_count:'.$this->id), $cnt);
464         }
465
466         return $cnt;
467     }
468
469     /**
470      * Is this profile subscribed to another profile?
471      *
472      * @param Profile $other
473      * @return boolean
474      */
475     function isSubscribed($other)
476     {
477         return Subscription::exists($this, $other);
478     }
479
480     /**
481      * Are these two profiles subscribed to each other?
482      *
483      * @param Profile $other
484      * @return boolean
485      */
486     function mutuallySubscribed($other)
487     {
488         return $this->isSubscribed($other) &&
489           $other->isSubscribed($this);
490     }
491
492     function hasFave($notice)
493     {
494         $cache = Cache::instance();
495
496         // XXX: Kind of a hack.
497
498         if (!empty($cache)) {
499             // This is the stream of favorite notices, in rev chron
500             // order. This forces it into cache.
501
502             $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW);
503
504             // If it's in the list, then it's a fave
505
506             if (in_array($notice->id, $ids)) {
507                 return true;
508             }
509
510             // If we're not past the end of the cache window,
511             // then the cache has all available faves, so this one
512             // is not a fave.
513
514             if (count($ids) < NOTICE_CACHE_WINDOW) {
515                 return false;
516             }
517
518             // Otherwise, cache doesn't have all faves;
519             // fall through to the default
520         }
521
522         $fave = Fave::pkeyGet(array('user_id' => $this->id,
523                                     'notice_id' => $notice->id));
524         return ((is_null($fave)) ? false : true);
525     }
526
527     function faveCount()
528     {
529         $c = Cache::instance();
530         if (!empty($c)) {
531             $cnt = $c->get(Cache::key('profile:fave_count:'.$this->id));
532             if (is_integer($cnt)) {
533                 return (int) $cnt;
534             }
535         }
536
537         $faves = new Fave();
538         $faves->user_id = $this->id;
539         $cnt = (int) $faves->count('distinct notice_id');
540
541         if (!empty($c)) {
542             $c->set(Cache::key('profile:fave_count:'.$this->id), $cnt);
543         }
544
545         return $cnt;
546     }
547
548     function noticeCount()
549     {
550         $c = Cache::instance();
551
552         if (!empty($c)) {
553             $cnt = $c->get(Cache::key('profile:notice_count:'.$this->id));
554             if (is_integer($cnt)) {
555                 return (int) $cnt;
556             }
557         }
558
559         $notices = new Notice();
560         $notices->profile_id = $this->id;
561         $cnt = (int) $notices->count('distinct id');
562
563         if (!empty($c)) {
564             $c->set(Cache::key('profile:notice_count:'.$this->id), $cnt);
565         }
566
567         return $cnt;
568     }
569
570     function blowFavesCache()
571     {
572         $cache = Cache::instance();
573         if ($cache) {
574             // Faves don't happen chronologically, so we need to blow
575             // ;last cache, too
576             $cache->delete(Cache::key('fave:ids_by_user:'.$this->id));
577             $cache->delete(Cache::key('fave:ids_by_user:'.$this->id.';last'));
578             $cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id));
579             $cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id.';last'));
580         }
581         $this->blowFaveCount();
582     }
583
584     function blowSubscriberCount()
585     {
586         $c = Cache::instance();
587         if (!empty($c)) {
588             $c->delete(Cache::key('profile:subscriber_count:'.$this->id));
589         }
590     }
591
592     function blowSubscriptionCount()
593     {
594         $c = Cache::instance();
595         if (!empty($c)) {
596             $c->delete(Cache::key('profile:subscription_count:'.$this->id));
597         }
598     }
599
600     function blowFaveCount()
601     {
602         $c = Cache::instance();
603         if (!empty($c)) {
604             $c->delete(Cache::key('profile:fave_count:'.$this->id));
605         }
606     }
607
608     function blowNoticeCount()
609     {
610         $c = Cache::instance();
611         if (!empty($c)) {
612             $c->delete(Cache::key('profile:notice_count:'.$this->id));
613         }
614     }
615
616     static function maxBio()
617     {
618         $biolimit = common_config('profile', 'biolimit');
619         // null => use global limit (distinct from 0!)
620         if (is_null($biolimit)) {
621             $biolimit = common_config('site', 'textlimit');
622         }
623         return $biolimit;
624     }
625
626     static function bioTooLong($bio)
627     {
628         $biolimit = self::maxBio();
629         return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
630     }
631
632     function delete()
633     {
634         $this->_deleteNotices();
635         $this->_deleteSubscriptions();
636         $this->_deleteMessages();
637         $this->_deleteTags();
638         $this->_deleteBlocks();
639         $this->delete_avatars();
640
641         // Warning: delete() will run on the batch objects,
642         // not on individual objects.
643         $related = array('Reply',
644                          'Group_member',
645                          );
646         Event::handle('ProfileDeleteRelated', array($this, &$related));
647
648         foreach ($related as $cls) {
649             $inst = new $cls();
650             $inst->profile_id = $this->id;
651             $inst->delete();
652         }
653
654         parent::delete();
655     }
656
657     function _deleteNotices()
658     {
659         $notice = new Notice();
660         $notice->profile_id = $this->id;
661
662         if ($notice->find()) {
663             while ($notice->fetch()) {
664                 $other = clone($notice);
665                 $other->delete();
666             }
667         }
668     }
669
670     function _deleteSubscriptions()
671     {
672         $sub = new Subscription();
673         $sub->subscriber = $this->id;
674
675         $sub->find();
676
677         while ($sub->fetch()) {
678             $other = Profile::staticGet('id', $sub->subscribed);
679             if (empty($other)) {
680                 continue;
681             }
682             if ($other->id == $this->id) {
683                 continue;
684             }
685             Subscription::cancel($this, $other);
686         }
687
688         $subd = new Subscription();
689         $subd->subscribed = $this->id;
690         $subd->find();
691
692         while ($subd->fetch()) {
693             $other = Profile::staticGet('id', $subd->subscriber);
694             if (empty($other)) {
695                 continue;
696             }
697             if ($other->id == $this->id) {
698                 continue;
699             }
700             Subscription::cancel($other, $this);
701         }
702
703         $self = new Subscription();
704
705         $self->subscriber = $this->id;
706         $self->subscribed = $this->id;
707
708         $self->delete();
709     }
710
711     function _deleteMessages()
712     {
713         $msg = new Message();
714         $msg->from_profile = $this->id;
715         $msg->delete();
716
717         $msg = new Message();
718         $msg->to_profile = $this->id;
719         $msg->delete();
720     }
721
722     function _deleteTags()
723     {
724         $tag = new Profile_tag();
725         $tag->tagged = $this->id;
726         $tag->delete();
727     }
728
729     function _deleteBlocks()
730     {
731         $block = new Profile_block();
732         $block->blocked = $this->id;
733         $block->delete();
734
735         $block = new Group_block();
736         $block->blocked = $this->id;
737         $block->delete();
738     }
739
740     // XXX: identical to Notice::getLocation.
741
742     function getLocation()
743     {
744         $location = null;
745
746         if (!empty($this->location_id) && !empty($this->location_ns)) {
747             $location = Location::fromId($this->location_id, $this->location_ns);
748         }
749
750         if (is_null($location)) { // no ID, or Location::fromId() failed
751             if (!empty($this->lat) && !empty($this->lon)) {
752                 $location = Location::fromLatLon($this->lat, $this->lon);
753             }
754         }
755
756         if (is_null($location)) { // still haven't found it!
757             if (!empty($this->location)) {
758                 $location = Location::fromName($this->location);
759             }
760         }
761
762         return $location;
763     }
764
765     function hasRole($name)
766     {
767         $has_role = false;
768         if (Event::handle('StartHasRole', array($this, $name, &$has_role))) {
769             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
770                                                 'role' => $name));
771             $has_role = !empty($role);
772             Event::handle('EndHasRole', array($this, $name, $has_role));
773         }
774         return $has_role;
775     }
776
777     function grantRole($name)
778     {
779         if (Event::handle('StartGrantRole', array($this, $name))) {
780
781             $role = new Profile_role();
782
783             $role->profile_id = $this->id;
784             $role->role       = $name;
785             $role->created    = common_sql_now();
786
787             $result = $role->insert();
788
789             if (!$result) {
790                 throw new Exception("Can't save role '$name' for profile '{$this->id}'");
791             }
792
793             if ($name == 'owner') {
794                 User::blow('user:site_owner');
795             }
796
797             Event::handle('EndGrantRole', array($this, $name));
798         }
799
800         return $result;
801     }
802
803     function revokeRole($name)
804     {
805         if (Event::handle('StartRevokeRole', array($this, $name))) {
806
807             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
808                                                 'role' => $name));
809
810             if (empty($role)) {
811                 // TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
812                 // TRANS: %1$s is the role name, %2$s is the user ID (number).
813                 throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
814             }
815
816             $result = $role->delete();
817
818             if (!$result) {
819                 common_log_db_error($role, 'DELETE', __FILE__);
820                 // TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
821                 // TRANS: %1$s is the role name, %2$s is the user ID (number).
822                 throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
823             }
824
825             if ($name == 'owner') {
826                 User::blow('user:site_owner');
827             }
828
829             Event::handle('EndRevokeRole', array($this, $name));
830
831             return true;
832         }
833     }
834
835     function isSandboxed()
836     {
837         return $this->hasRole(Profile_role::SANDBOXED);
838     }
839
840     function isSilenced()
841     {
842         return $this->hasRole(Profile_role::SILENCED);
843     }
844
845     function sandbox()
846     {
847         $this->grantRole(Profile_role::SANDBOXED);
848     }
849
850     function unsandbox()
851     {
852         $this->revokeRole(Profile_role::SANDBOXED);
853     }
854
855     function silence()
856     {
857         $this->grantRole(Profile_role::SILENCED);
858     }
859
860     function unsilence()
861     {
862         $this->revokeRole(Profile_role::SILENCED);
863     }
864
865     /**
866      * Does this user have the right to do X?
867      *
868      * With our role-based authorization, this is merely a lookup for whether the user
869      * has a particular role. The implementation currently uses a switch statement
870      * to determine if the user has the pre-defined role to exercise the right. Future
871      * implementations may allow per-site roles, and different mappings of roles to rights.
872      *
873      * @param $right string Name of the right, usually a constant in class Right
874      * @return boolean whether the user has the right in question
875      */
876     function hasRight($right)
877     {
878         $result = false;
879
880         if ($this->hasRole(Profile_role::DELETED)) {
881             return false;
882         }
883
884         if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
885             switch ($right)
886             {
887             case Right::DELETEOTHERSNOTICE:
888             case Right::MAKEGROUPADMIN:
889             case Right::SANDBOXUSER:
890             case Right::SILENCEUSER:
891             case Right::DELETEUSER:
892             case Right::DELETEGROUP:
893                 $result = $this->hasRole(Profile_role::MODERATOR);
894                 break;
895             case Right::CONFIGURESITE:
896                 $result = $this->hasRole(Profile_role::ADMINISTRATOR);
897                 break;
898             case Right::GRANTROLE:
899             case Right::REVOKEROLE:
900                 $result = $this->hasRole(Profile_role::OWNER);
901                 break;
902             case Right::NEWNOTICE:
903             case Right::NEWMESSAGE:
904             case Right::SUBSCRIBE:
905             case Right::CREATEGROUP:
906                 $result = !$this->isSilenced();
907                 break;
908             case Right::PUBLICNOTICE:
909             case Right::EMAILONREPLY:
910             case Right::EMAILONSUBSCRIBE:
911             case Right::EMAILONFAVE:
912                 $result = !$this->isSandboxed();
913                 break;
914             case Right::WEBLOGIN:
915                 $result = !$this->isSilenced();
916                 break;
917             case Right::API:
918                 $result = !$this->isSilenced();
919                 break;
920             case Right::BACKUPACCOUNT:
921                 $result = common_config('profile', 'backup');
922                 break;
923             case Right::RESTOREACCOUNT:
924                 $result = common_config('profile', 'restore');
925                 break;
926             case Right::DELETEACCOUNT:
927                 $result = common_config('profile', 'delete');
928                 break;
929             case Right::MOVEACCOUNT:
930                 $result = common_config('profile', 'move');
931                 break;
932             default:
933                 $result = false;
934                 break;
935             }
936         }
937         return $result;
938     }
939
940     function hasRepeated($notice_id)
941     {
942         // XXX: not really a pkey, but should work
943
944         $notice = Memcached_DataObject::pkeyGet('Notice',
945                                                 array('profile_id' => $this->id,
946                                                       'repeat_of' => $notice_id));
947
948         return !empty($notice);
949     }
950
951     /**
952      * Returns an XML string fragment with limited profile information
953      * as an Atom <author> element.
954      *
955      * Assumes that Atom has been previously set up as the base namespace.
956      *
957      * @param Profile $cur the current authenticated user
958      *
959      * @return string
960      */
961     function asAtomAuthor($cur = null)
962     {
963         $xs = new XMLStringer(true);
964
965         $xs->elementStart('author');
966         $xs->element('name', null, $this->nickname);
967         $xs->element('uri', null, $this->getUri());
968         if ($cur != null) {
969             $attrs = Array();
970             $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
971             $attrs['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
972             $xs->element('statusnet:profile_info', $attrs, null);
973         }
974         $xs->elementEnd('author');
975
976         return $xs->getString();
977     }
978
979     /**
980      * Extra profile info for atom entries
981      *
982      * Clients use some extra profile info in the atom stream.
983      * This gives it to them.
984      *
985      * @param User $cur Current user
986      *
987      * @return array representation of <statusnet:profile_info> element or null
988      */
989
990     function profileInfo($cur)
991     {
992         $profileInfoAttr = array('local_id' => $this->id);
993
994         if ($cur != null) {
995             // Whether the current user is a subscribed to this profile
996             $profileInfoAttr['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
997             // Whether the current user is has blocked this profile
998             $profileInfoAttr['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
999         }
1000
1001         return array('statusnet:profile_info', $profileInfoAttr, null);
1002     }
1003
1004     /**
1005      * Returns an XML string fragment with profile information as an
1006      * Activity Streams <activity:actor> element.
1007      *
1008      * Assumes that 'activity' namespace has been previously defined.
1009      *
1010      * @return string
1011      */
1012     function asActivityActor()
1013     {
1014         return $this->asActivityNoun('actor');
1015     }
1016
1017     /**
1018      * Returns an XML string fragment with profile information as an
1019      * Activity Streams noun object with the given element type.
1020      *
1021      * Assumes that 'activity', 'georss', and 'poco' namespace has been
1022      * previously defined.
1023      *
1024      * @param string $element one of 'actor', 'subject', 'object', 'target'
1025      *
1026      * @return string
1027      */
1028     function asActivityNoun($element)
1029     {
1030         $noun = ActivityObject::fromProfile($this);
1031         return $noun->asString('activity:' . $element);
1032     }
1033
1034     /**
1035      * Returns the best URI for a profile. Plugins may override.
1036      *
1037      * @return string $uri
1038      */
1039     function getUri()
1040     {
1041         $uri = null;
1042
1043         // give plugins a chance to set the URI
1044         if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
1045
1046             // check for a local user first
1047             $user = User::staticGet('id', $this->id);
1048
1049             if (!empty($user)) {
1050                 $uri = $user->uri;
1051             } else {
1052                 // return OMB profile if any
1053                 $remote = Remote_profile::staticGet('id', $this->id);
1054                 if (!empty($remote)) {
1055                     $uri = $remote->uri;
1056                 }
1057             }
1058             Event::handle('EndGetProfileUri', array($this, &$uri));
1059         }
1060
1061         return $uri;
1062     }
1063
1064     function hasBlocked($other)
1065     {
1066         $block = Profile_block::get($this->id, $other->id);
1067
1068         if (empty($block)) {
1069             $result = false;
1070         } else {
1071             $result = true;
1072         }
1073
1074         return $result;
1075     }
1076
1077     function getAtomFeed()
1078     {
1079         $feed = null;
1080
1081         if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
1082             $user = User::staticGet('id', $this->id);
1083             if (!empty($user)) {
1084                 $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
1085                                                                   'format' => 'atom'));
1086             }
1087             Event::handle('EndProfileGetAtomFeed', array($this, $feed));
1088         }
1089
1090         return $feed;
1091     }
1092
1093     static function fromURI($uri)
1094     {
1095         $profile = null;
1096
1097         if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
1098             // Get a local user or remote (OMB 0.1) profile
1099             $user = User::staticGet('uri', $uri);
1100             if (!empty($user)) {
1101                 $profile = $user->getProfile();
1102             } else {
1103                 $remote_profile = Remote_profile::staticGet('uri', $uri);
1104                 if (!empty($remote_profile)) {
1105                     $profile = Profile::staticGet('id', $remote_profile->profile_id);
1106                 }
1107             }
1108             Event::handle('EndGetProfileFromURI', array($uri, $profile));
1109         }
1110
1111         return $profile;
1112     }
1113 }