]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/Profile.php
Merge branch 'limitdist2' into 1.0.x
[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 (%1$s) followed by nickname (%2$s) in parentheses.
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     function getCurrentNotice()
184     {
185         $notice = $this->getNotices(0, 1);
186
187         if ($notice->fetch()) {
188             if ($notice instanceof ArrayWrapper) {
189                 // hack for things trying to work with single notices
190                 return $notice->_items[0];
191             }
192             return $notice;
193         } else {
194             return null;
195         }
196     }
197
198     function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
199     {
200         $stream = new TaggedProfileNoticeStream($this, $tag);
201
202         return $stream->getNotices($offset, $limit, $since_id, $max_id);
203     }
204
205     function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
206     {
207         $stream = new ProfileNoticeStream($this);
208
209         return $stream->getNotices($offset, $limit, $since_id, $max_id);
210     }
211
212     function isMember($group)
213     {
214         $mem = new Group_member();
215
216         $mem->group_id = $group->id;
217         $mem->profile_id = $this->id;
218
219         if ($mem->find()) {
220             return true;
221         } else {
222             return false;
223         }
224     }
225
226     function isAdmin($group)
227     {
228         $mem = new Group_member();
229
230         $mem->group_id = $group->id;
231         $mem->profile_id = $this->id;
232         $mem->is_admin = 1;
233
234         if ($mem->find()) {
235             return true;
236         } else {
237             return false;
238         }
239     }
240
241     function isPendingMember($group)
242     {
243         $request = Group_join_queue::pkeyGet(array('profile_id' => $this->id,
244                                                    'group_id' => $group->id));
245         return !empty($request);
246     }
247
248     function getGroups($offset=0, $limit=null)
249     {
250         $qry =
251           'SELECT user_group.* ' .
252           'FROM user_group JOIN group_member '.
253           'ON user_group.id = group_member.group_id ' .
254           'WHERE group_member.profile_id = %d ' .
255           'ORDER BY group_member.created DESC ';
256
257         if ($offset>0 && !is_null($limit)) {
258             if ($offset) {
259                 if (common_config('db','type') == 'pgsql') {
260                     $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
261                 } else {
262                     $qry .= ' LIMIT ' . $offset . ', ' . $limit;
263                 }
264             }
265         }
266
267         $groups = new User_group();
268
269         $cnt = $groups->query(sprintf($qry, $this->id));
270
271         return $groups;
272     }
273
274     /**
275      * Request to join the given group.
276      * May throw exceptions on failure.
277      *
278      * @param User_group $group
279      * @return mixed: Group_member on success, Group_join_queue if pending approval, null on some cancels?
280      */
281     function joinGroup(User_group $group)
282     {
283         $join = null;
284         if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) {
285             $join = Group_join_queue::saveNew($this, $group);
286         } else {
287             if (Event::handle('StartJoinGroup', array($group, $this))) {
288                 $join = Group_member::join($group->id, $this->id);
289                 Event::handle('EndJoinGroup', array($group, $this));
290             }
291         }
292         if ($join) {
293             // Send any applicable notifications...
294             $join->notify();
295         }
296         return $join;
297     }
298
299     /**
300      * Leave a group that this profile is a member of.
301      *
302      * @param User_group $group
303      */
304     function leaveGroup(User_group $group)
305     {
306         if (Event::handle('StartLeaveGroup', array($group, $this))) {
307             Group_member::leave($group->id, $this->id);
308             Event::handle('EndLeaveGroup', array($group, $this));
309         }
310     }
311
312     function avatarUrl($size=AVATAR_PROFILE_SIZE)
313     {
314         $avatar = $this->getAvatar($size);
315         if ($avatar) {
316             return $avatar->displayUrl();
317         } else {
318             return Avatar::defaultImage($size);
319         }
320     }
321
322     function getSubscriptions($offset=0, $limit=null)
323     {
324         $subs = Subscription::bySubscriber($this->id,
325                                            $offset,
326                                            $limit);
327
328         $profiles = array();
329
330         while ($subs->fetch()) {
331             $profile = Profile::staticGet($subs->subscribed);
332             if ($profile) {
333                 $profiles[] = $profile;
334             }
335         }
336
337         return new ArrayWrapper($profiles);
338     }
339
340     function getSubscribers($offset=0, $limit=null)
341     {
342         $subs = Subscription::bySubscribed($this->id,
343                                            $offset,
344                                            $limit);
345
346         $profiles = array();
347
348         while ($subs->fetch()) {
349             $profile = Profile::staticGet($subs->subscriber);
350             if ($profile) {
351                 $profiles[] = $profile;
352             }
353         }
354
355         return new ArrayWrapper($profiles);
356     }
357
358     /**
359      * Get pending subscribers, who have not yet been approved.
360      *
361      * @param int $offset
362      * @param int $limit
363      * @return Profile
364      */
365     function getRequests($offset=0, $limit=null)
366     {
367         $qry =
368           'SELECT profile.* ' .
369           'FROM profile JOIN subscription_queue '.
370           'ON profile.id = subscription_queue.subscriber ' .
371           'WHERE subscription_queue.subscribed = %d ' .
372           'ORDER BY subscription_queue.created DESC ';
373
374         if ($limit != null) {
375             if (common_config('db','type') == 'pgsql') {
376                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
377             } else {
378                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
379             }
380         }
381
382         $members = new Profile();
383
384         $members->query(sprintf($qry, $this->id));
385         return $members;
386     }
387
388     function subscriptionCount()
389     {
390         $c = Cache::instance();
391
392         if (!empty($c)) {
393             $cnt = $c->get(Cache::key('profile:subscription_count:'.$this->id));
394             if (is_integer($cnt)) {
395                 return (int) $cnt;
396             }
397         }
398
399         $sub = new Subscription();
400         $sub->subscriber = $this->id;
401
402         $cnt = (int) $sub->count('distinct subscribed');
403
404         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
405
406         if (!empty($c)) {
407             $c->set(Cache::key('profile:subscription_count:'.$this->id), $cnt);
408         }
409
410         return $cnt;
411     }
412
413     function subscriberCount()
414     {
415         $c = Cache::instance();
416         if (!empty($c)) {
417             $cnt = $c->get(Cache::key('profile:subscriber_count:'.$this->id));
418             if (is_integer($cnt)) {
419                 return (int) $cnt;
420             }
421         }
422
423         $sub = new Subscription();
424         $sub->subscribed = $this->id;
425         $sub->whereAdd('subscriber != subscribed');
426         $cnt = (int) $sub->count('distinct subscriber');
427
428         if (!empty($c)) {
429             $c->set(Cache::key('profile:subscriber_count:'.$this->id), $cnt);
430         }
431
432         return $cnt;
433     }
434
435     /**
436      * Is this profile subscribed to another profile?
437      *
438      * @param Profile $other
439      * @return boolean
440      */
441     function isSubscribed($other)
442     {
443         return Subscription::exists($this, $other);
444     }
445
446     /**
447      * Check if a pending subscription request is outstanding for this...
448      *
449      * @param Profile $other
450      * @return boolean
451      */
452     function hasPendingSubscription($other)
453     {
454         return Subscription_queue::exists($this, $other);
455     }
456
457     /**
458      * Are these two profiles subscribed to each other?
459      *
460      * @param Profile $other
461      * @return boolean
462      */
463     function mutuallySubscribed($other)
464     {
465         return $this->isSubscribed($other) &&
466           $other->isSubscribed($this);
467     }
468
469     function hasFave($notice)
470     {
471         $cache = Cache::instance();
472
473         // XXX: Kind of a hack.
474
475         if (!empty($cache)) {
476             // This is the stream of favorite notices, in rev chron
477             // order. This forces it into cache.
478
479             $ids = Fave::idStream($this->id, 0, CachingNoticeStream::CACHE_WINDOW);
480
481             // If it's in the list, then it's a fave
482
483             if (in_array($notice->id, $ids)) {
484                 return true;
485             }
486
487             // If we're not past the end of the cache window,
488             // then the cache has all available faves, so this one
489             // is not a fave.
490
491             if (count($ids) < CachingNoticeStream::CACHE_WINDOW) {
492                 return false;
493             }
494
495             // Otherwise, cache doesn't have all faves;
496             // fall through to the default
497         }
498
499         $fave = Fave::pkeyGet(array('user_id' => $this->id,
500                                     'notice_id' => $notice->id));
501         return ((is_null($fave)) ? false : true);
502     }
503
504     function faveCount()
505     {
506         $c = Cache::instance();
507         if (!empty($c)) {
508             $cnt = $c->get(Cache::key('profile:fave_count:'.$this->id));
509             if (is_integer($cnt)) {
510                 return (int) $cnt;
511             }
512         }
513
514         $faves = new Fave();
515         $faves->user_id = $this->id;
516         $cnt = (int) $faves->count('distinct notice_id');
517
518         if (!empty($c)) {
519             $c->set(Cache::key('profile:fave_count:'.$this->id), $cnt);
520         }
521
522         return $cnt;
523     }
524
525     function noticeCount()
526     {
527         $c = Cache::instance();
528
529         if (!empty($c)) {
530             $cnt = $c->get(Cache::key('profile:notice_count:'.$this->id));
531             if (is_integer($cnt)) {
532                 return (int) $cnt;
533             }
534         }
535
536         $notices = new Notice();
537         $notices->profile_id = $this->id;
538         $cnt = (int) $notices->count('distinct id');
539
540         if (!empty($c)) {
541             $c->set(Cache::key('profile:notice_count:'.$this->id), $cnt);
542         }
543
544         return $cnt;
545     }
546
547     function blowFavesCache()
548     {
549         $cache = Cache::instance();
550         if ($cache) {
551             // Faves don't happen chronologically, so we need to blow
552             // ;last cache, too
553             $cache->delete(Cache::key('fave:ids_by_user:'.$this->id));
554             $cache->delete(Cache::key('fave:ids_by_user:'.$this->id.';last'));
555             $cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id));
556             $cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id.';last'));
557         }
558         $this->blowFaveCount();
559     }
560
561     function blowSubscriberCount()
562     {
563         $c = Cache::instance();
564         if (!empty($c)) {
565             $c->delete(Cache::key('profile:subscriber_count:'.$this->id));
566         }
567     }
568
569     function blowSubscriptionCount()
570     {
571         $c = Cache::instance();
572         if (!empty($c)) {
573             $c->delete(Cache::key('profile:subscription_count:'.$this->id));
574         }
575     }
576
577     function blowFaveCount()
578     {
579         $c = Cache::instance();
580         if (!empty($c)) {
581             $c->delete(Cache::key('profile:fave_count:'.$this->id));
582         }
583     }
584
585     function blowNoticeCount()
586     {
587         $c = Cache::instance();
588         if (!empty($c)) {
589             $c->delete(Cache::key('profile:notice_count:'.$this->id));
590         }
591     }
592
593     static function maxBio()
594     {
595         $biolimit = common_config('profile', 'biolimit');
596         // null => use global limit (distinct from 0!)
597         if (is_null($biolimit)) {
598             $biolimit = common_config('site', 'textlimit');
599         }
600         return $biolimit;
601     }
602
603     static function bioTooLong($bio)
604     {
605         $biolimit = self::maxBio();
606         return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
607     }
608
609     function delete()
610     {
611         $this->_deleteNotices();
612         $this->_deleteSubscriptions();
613         $this->_deleteMessages();
614         $this->_deleteTags();
615         $this->_deleteBlocks();
616         $this->delete_avatars();
617
618         // Warning: delete() will run on the batch objects,
619         // not on individual objects.
620         $related = array('Reply',
621                          'Group_member',
622                          );
623         Event::handle('ProfileDeleteRelated', array($this, &$related));
624
625         foreach ($related as $cls) {
626             $inst = new $cls();
627             $inst->profile_id = $this->id;
628             $inst->delete();
629         }
630
631         parent::delete();
632     }
633
634     function _deleteNotices()
635     {
636         $notice = new Notice();
637         $notice->profile_id = $this->id;
638
639         if ($notice->find()) {
640             while ($notice->fetch()) {
641                 $other = clone($notice);
642                 $other->delete();
643             }
644         }
645     }
646
647     function _deleteSubscriptions()
648     {
649         $sub = new Subscription();
650         $sub->subscriber = $this->id;
651
652         $sub->find();
653
654         while ($sub->fetch()) {
655             $other = Profile::staticGet('id', $sub->subscribed);
656             if (empty($other)) {
657                 continue;
658             }
659             if ($other->id == $this->id) {
660                 continue;
661             }
662             Subscription::cancel($this, $other);
663         }
664
665         $subd = new Subscription();
666         $subd->subscribed = $this->id;
667         $subd->find();
668
669         while ($subd->fetch()) {
670             $other = Profile::staticGet('id', $subd->subscriber);
671             if (empty($other)) {
672                 continue;
673             }
674             if ($other->id == $this->id) {
675                 continue;
676             }
677             Subscription::cancel($other, $this);
678         }
679
680         $self = new Subscription();
681
682         $self->subscriber = $this->id;
683         $self->subscribed = $this->id;
684
685         $self->delete();
686     }
687
688     function _deleteMessages()
689     {
690         $msg = new Message();
691         $msg->from_profile = $this->id;
692         $msg->delete();
693
694         $msg = new Message();
695         $msg->to_profile = $this->id;
696         $msg->delete();
697     }
698
699     function _deleteTags()
700     {
701         $tag = new Profile_tag();
702         $tag->tagged = $this->id;
703         $tag->delete();
704     }
705
706     function _deleteBlocks()
707     {
708         $block = new Profile_block();
709         $block->blocked = $this->id;
710         $block->delete();
711
712         $block = new Group_block();
713         $block->blocked = $this->id;
714         $block->delete();
715     }
716
717     // XXX: identical to Notice::getLocation.
718
719     function getLocation()
720     {
721         $location = null;
722
723         if (!empty($this->location_id) && !empty($this->location_ns)) {
724             $location = Location::fromId($this->location_id, $this->location_ns);
725         }
726
727         if (is_null($location)) { // no ID, or Location::fromId() failed
728             if (!empty($this->lat) && !empty($this->lon)) {
729                 $location = Location::fromLatLon($this->lat, $this->lon);
730             }
731         }
732
733         if (is_null($location)) { // still haven't found it!
734             if (!empty($this->location)) {
735                 $location = Location::fromName($this->location);
736             }
737         }
738
739         return $location;
740     }
741
742     function hasRole($name)
743     {
744         $has_role = false;
745         if (Event::handle('StartHasRole', array($this, $name, &$has_role))) {
746             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
747                                                 'role' => $name));
748             $has_role = !empty($role);
749             Event::handle('EndHasRole', array($this, $name, $has_role));
750         }
751         return $has_role;
752     }
753
754     function grantRole($name)
755     {
756         if (Event::handle('StartGrantRole', array($this, $name))) {
757
758             $role = new Profile_role();
759
760             $role->profile_id = $this->id;
761             $role->role       = $name;
762             $role->created    = common_sql_now();
763
764             $result = $role->insert();
765
766             if (!$result) {
767                 throw new Exception("Can't save role '$name' for profile '{$this->id}'");
768             }
769
770             if ($name == 'owner') {
771                 User::blow('user:site_owner');
772             }
773
774             Event::handle('EndGrantRole', array($this, $name));
775         }
776
777         return $result;
778     }
779
780     function revokeRole($name)
781     {
782         if (Event::handle('StartRevokeRole', array($this, $name))) {
783
784             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
785                                                 'role' => $name));
786
787             if (empty($role)) {
788                 // TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
789                 // TRANS: %1$s is the role name, %2$s is the user ID (number).
790                 throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
791             }
792
793             $result = $role->delete();
794
795             if (!$result) {
796                 common_log_db_error($role, 'DELETE', __FILE__);
797                 // TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
798                 // TRANS: %1$s is the role name, %2$s is the user ID (number).
799                 throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
800             }
801
802             if ($name == 'owner') {
803                 User::blow('user:site_owner');
804             }
805
806             Event::handle('EndRevokeRole', array($this, $name));
807
808             return true;
809         }
810     }
811
812     function isSandboxed()
813     {
814         return $this->hasRole(Profile_role::SANDBOXED);
815     }
816
817     function isSilenced()
818     {
819         return $this->hasRole(Profile_role::SILENCED);
820     }
821
822     function sandbox()
823     {
824         $this->grantRole(Profile_role::SANDBOXED);
825     }
826
827     function unsandbox()
828     {
829         $this->revokeRole(Profile_role::SANDBOXED);
830     }
831
832     function silence()
833     {
834         $this->grantRole(Profile_role::SILENCED);
835     }
836
837     function unsilence()
838     {
839         $this->revokeRole(Profile_role::SILENCED);
840     }
841
842     /**
843      * Does this user have the right to do X?
844      *
845      * With our role-based authorization, this is merely a lookup for whether the user
846      * has a particular role. The implementation currently uses a switch statement
847      * to determine if the user has the pre-defined role to exercise the right. Future
848      * implementations may allow per-site roles, and different mappings of roles to rights.
849      *
850      * @param $right string Name of the right, usually a constant in class Right
851      * @return boolean whether the user has the right in question
852      */
853     function hasRight($right)
854     {
855         $result = false;
856
857         if ($this->hasRole(Profile_role::DELETED)) {
858             return false;
859         }
860
861         if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
862             switch ($right)
863             {
864             case Right::DELETEOTHERSNOTICE:
865             case Right::MAKEGROUPADMIN:
866             case Right::SANDBOXUSER:
867             case Right::SILENCEUSER:
868             case Right::DELETEUSER:
869             case Right::DELETEGROUP:
870                 $result = $this->hasRole(Profile_role::MODERATOR);
871                 break;
872             case Right::CONFIGURESITE:
873                 $result = $this->hasRole(Profile_role::ADMINISTRATOR);
874                 break;
875             case Right::GRANTROLE:
876             case Right::REVOKEROLE:
877                 $result = $this->hasRole(Profile_role::OWNER);
878                 break;
879             case Right::NEWNOTICE:
880             case Right::NEWMESSAGE:
881             case Right::SUBSCRIBE:
882             case Right::CREATEGROUP:
883                 $result = !$this->isSilenced();
884                 break;
885             case Right::PUBLICNOTICE:
886             case Right::EMAILONREPLY:
887             case Right::EMAILONSUBSCRIBE:
888             case Right::EMAILONFAVE:
889                 $result = !$this->isSandboxed();
890                 break;
891             case Right::WEBLOGIN:
892                 $result = !$this->isSilenced();
893                 break;
894             case Right::API:
895                 $result = !$this->isSilenced();
896                 break;
897             case Right::BACKUPACCOUNT:
898                 $result = common_config('profile', 'backup');
899                 break;
900             case Right::RESTOREACCOUNT:
901                 $result = common_config('profile', 'restore');
902                 break;
903             case Right::DELETEACCOUNT:
904                 $result = common_config('profile', 'delete');
905                 break;
906             case Right::MOVEACCOUNT:
907                 $result = common_config('profile', 'move');
908                 break;
909             default:
910                 $result = false;
911                 break;
912             }
913         }
914         return $result;
915     }
916
917     function hasRepeated($notice_id)
918     {
919         // XXX: not really a pkey, but should work
920
921         $notice = Memcached_DataObject::pkeyGet('Notice',
922                                                 array('profile_id' => $this->id,
923                                                       'repeat_of' => $notice_id));
924
925         return !empty($notice);
926     }
927
928     /**
929      * Returns an XML string fragment with limited profile information
930      * as an Atom <author> element.
931      *
932      * Assumes that Atom has been previously set up as the base namespace.
933      *
934      * @param Profile $cur the current authenticated user
935      *
936      * @return string
937      */
938     function asAtomAuthor($cur = null)
939     {
940         $xs = new XMLStringer(true);
941
942         $xs->elementStart('author');
943         $xs->element('name', null, $this->nickname);
944         $xs->element('uri', null, $this->getUri());
945         if ($cur != null) {
946             $attrs = Array();
947             $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
948             $attrs['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
949             $xs->element('statusnet:profile_info', $attrs, null);
950         }
951         $xs->elementEnd('author');
952
953         return $xs->getString();
954     }
955
956     /**
957      * Extra profile info for atom entries
958      *
959      * Clients use some extra profile info in the atom stream.
960      * This gives it to them.
961      *
962      * @param User $cur Current user
963      *
964      * @return array representation of <statusnet:profile_info> element or null
965      */
966
967     function profileInfo($cur)
968     {
969         $profileInfoAttr = array('local_id' => $this->id);
970
971         if ($cur != null) {
972             // Whether the current user is a subscribed to this profile
973             $profileInfoAttr['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
974             // Whether the current user is has blocked this profile
975             $profileInfoAttr['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
976         }
977
978         return array('statusnet:profile_info', $profileInfoAttr, null);
979     }
980
981     /**
982      * Returns an XML string fragment with profile information as an
983      * Activity Streams <activity:actor> element.
984      *
985      * Assumes that 'activity' namespace has been previously defined.
986      *
987      * @return string
988      */
989     function asActivityActor()
990     {
991         return $this->asActivityNoun('actor');
992     }
993
994     /**
995      * Returns an XML string fragment with profile information as an
996      * Activity Streams noun object with the given element type.
997      *
998      * Assumes that 'activity', 'georss', and 'poco' namespace has been
999      * previously defined.
1000      *
1001      * @param string $element one of 'actor', 'subject', 'object', 'target'
1002      *
1003      * @return string
1004      */
1005     function asActivityNoun($element)
1006     {
1007         $noun = ActivityObject::fromProfile($this);
1008         return $noun->asString('activity:' . $element);
1009     }
1010
1011     /**
1012      * Returns the best URI for a profile. Plugins may override.
1013      *
1014      * @return string $uri
1015      */
1016     function getUri()
1017     {
1018         $uri = null;
1019
1020         // give plugins a chance to set the URI
1021         if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
1022
1023             // check for a local user first
1024             $user = User::staticGet('id', $this->id);
1025
1026             if (!empty($user)) {
1027                 $uri = $user->uri;
1028             } else {
1029                 // return OMB profile if any
1030                 $remote = Remote_profile::staticGet('id', $this->id);
1031                 if (!empty($remote)) {
1032                     $uri = $remote->uri;
1033                 }
1034             }
1035             Event::handle('EndGetProfileUri', array($this, &$uri));
1036         }
1037
1038         return $uri;
1039     }
1040
1041     function hasBlocked($other)
1042     {
1043         $block = Profile_block::get($this->id, $other->id);
1044
1045         if (empty($block)) {
1046             $result = false;
1047         } else {
1048             $result = true;
1049         }
1050
1051         return $result;
1052     }
1053
1054     function getAtomFeed()
1055     {
1056         $feed = null;
1057
1058         if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
1059             $user = User::staticGet('id', $this->id);
1060             if (!empty($user)) {
1061                 $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
1062                                                                   'format' => 'atom'));
1063             }
1064             Event::handle('EndProfileGetAtomFeed', array($this, $feed));
1065         }
1066
1067         return $feed;
1068     }
1069
1070     static function fromURI($uri)
1071     {
1072         $profile = null;
1073
1074         if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
1075             // Get a local user or remote (OMB 0.1) profile
1076             $user = User::staticGet('uri', $uri);
1077             if (!empty($user)) {
1078                 $profile = $user->getProfile();
1079             } else {
1080                 $remote_profile = Remote_profile::staticGet('uri', $uri);
1081                 if (!empty($remote_profile)) {
1082                     $profile = Profile::staticGet('id', $remote_profile->profile_id);
1083                 }
1084             }
1085             Event::handle('EndGetProfileFromURI', array($uri, $profile));
1086         }
1087
1088         return $profile;
1089     }
1090
1091     function canRead(Notice $notice)
1092     {
1093         if ($notice->scope & Notice::SITE_SCOPE) {
1094             $user = $this->getUser();
1095             if (empty($user)) {
1096                 return false;
1097             }
1098         }
1099
1100         if ($notice->scope & Notice::ADDRESSEE_SCOPE) {
1101             $replies = $notice->getReplies();
1102
1103             if (!in_array($this->id, $replies)) {
1104                 $groups = $notice->getGroups();
1105
1106                 $foundOne = false;
1107
1108                 foreach ($groups as $group) {
1109                     if ($this->isMember($group)) {
1110                         $foundOne = true;
1111                         break;
1112                     }
1113                 }
1114
1115                 if (!$foundOne) {
1116                     return false;
1117                 }
1118             }
1119         }
1120
1121         if ($notice->scope & Notice::FOLLOWER_SCOPE) {
1122             $author = $notice->getProfile();
1123             if (!Subscription::exists($this, $author)) {
1124                 return false;
1125             }
1126         }
1127
1128         return true;
1129     }
1130 }