]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/Profile.php
Move blowFavesCache() to Profile
[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
107                 $scaled_filename = $imagefile->resize($size);
108
109                 //$scaled = DB_DataObject::factory('avatar');
110                 $scaled = new Avatar();
111                 $scaled->profile_id = $this->id;
112                 $scaled->width = $size;
113                 $scaled->height = $size;
114                 $scaled->original = false;
115                 $scaled->mediatype = image_type_to_mime_type($imagefile->type);
116                 $scaled->filename = $scaled_filename;
117                 $scaled->url = Avatar::url($scaled_filename);
118                 $scaled->created = DB_DataObject_Cast::dateTime(); # current time
119
120                 if (!$scaled->insert()) {
121                     return null;
122                 }
123             }
124         }
125
126         return $avatar;
127     }
128
129     function delete_avatars($original=true)
130     {
131         $avatar = new Avatar();
132         $avatar->profile_id = $this->id;
133         $avatar->find();
134         while ($avatar->fetch()) {
135             if ($avatar->original) {
136                 if ($original == false) {
137                     continue;
138                 }
139             }
140             $avatar->delete();
141         }
142         return true;
143     }
144
145     function getBestName()
146     {
147         return ($this->fullname) ? $this->fullname : $this->nickname;
148     }
149
150     /**
151      * Get the most recent notice posted by this user, if any.
152      *
153      * @return mixed Notice or null
154      */
155
156     function getCurrentNotice()
157     {
158         $notice = $this->getNotices(0, 1);
159
160         if ($notice->fetch()) {
161             return $notice;
162         } else {
163             return null;
164         }
165     }
166
167     function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
168     {
169         $ids = Notice::stream(array($this, '_streamTaggedDirect'),
170                               array($tag),
171                               'profile:notice_ids_tagged:' . $this->id . ':' . $tag,
172                               $offset, $limit, $since_id, $max_id);
173         return Notice::getStreamByIds($ids);
174     }
175
176     function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
177     {
178         // XXX: I'm not sure this is going to be any faster. It probably isn't.
179         $ids = Notice::stream(array($this, '_streamDirect'),
180                               array(),
181                               'profile:notice_ids:' . $this->id,
182                               $offset, $limit, $since_id, $max_id);
183
184         return Notice::getStreamByIds($ids);
185     }
186
187     function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id)
188     {
189         // XXX It would be nice to do this without a join
190
191         $notice = new Notice();
192
193         $query =
194           "select id from notice join notice_tag on id=notice_id where tag='".
195           $notice->escape($tag) .
196           "' and profile_id=" . $notice->escape($this->id);
197
198         if ($since_id != 0) {
199             $query .= " and id > $since_id";
200         }
201
202         if ($max_id != 0) {
203             $query .= " and id < $max_id";
204         }
205
206         $query .= ' order by id DESC';
207
208         if (!is_null($offset)) {
209             $query .= " LIMIT $limit OFFSET $offset";
210         }
211
212         $notice->query($query);
213
214         $ids = array();
215
216         while ($notice->fetch()) {
217             $ids[] = $notice->id;
218         }
219
220         return $ids;
221     }
222
223     function _streamDirect($offset, $limit, $since_id, $max_id)
224     {
225         $notice = new Notice();
226
227         // Temporary hack until notice_profile_id_idx is updated
228         // to (profile_id, id) instead of (profile_id, created, id).
229         // It's been falling back to PRIMARY instead, which is really
230         // very inefficient for a profile that hasn't posted in a few
231         // months. Even though forcing the index will cause a filesort,
232         // it's usually going to be better.
233         if (common_config('db', 'type') == 'mysql') {
234             $index = '';
235             $query =
236               "select id from notice force index (notice_profile_id_idx) ".
237               "where profile_id=" . $notice->escape($this->id);
238
239             if ($since_id != 0) {
240                 $query .= " and id > $since_id";
241             }
242
243             if ($max_id != 0) {
244                 $query .= " and id < $max_id";
245             }
246
247             $query .= ' order by id DESC';
248
249             if (!is_null($offset)) {
250                 $query .= " LIMIT $limit OFFSET $offset";
251             }
252
253             $notice->query($query);
254         } else {
255             $index = '';
256
257             $notice->profile_id = $this->id;
258
259             $notice->selectAdd();
260             $notice->selectAdd('id');
261
262             if ($since_id != 0) {
263                 $notice->whereAdd('id > ' . $since_id);
264             }
265
266             if ($max_id != 0) {
267                 $notice->whereAdd('id <= ' . $max_id);
268             }
269
270             $notice->orderBy('id DESC');
271
272             if (!is_null($offset)) {
273                 $notice->limit($offset, $limit);
274             }
275
276             $notice->find();
277         }
278
279         $ids = array();
280
281         while ($notice->fetch()) {
282             $ids[] = $notice->id;
283         }
284
285         return $ids;
286     }
287
288     function isMember($group)
289     {
290         $mem = new Group_member();
291
292         $mem->group_id = $group->id;
293         $mem->profile_id = $this->id;
294
295         if ($mem->find()) {
296             return true;
297         } else {
298             return false;
299         }
300     }
301
302     function isAdmin($group)
303     {
304         $mem = new Group_member();
305
306         $mem->group_id = $group->id;
307         $mem->profile_id = $this->id;
308         $mem->is_admin = 1;
309
310         if ($mem->find()) {
311             return true;
312         } else {
313             return false;
314         }
315     }
316
317     function getGroups($offset=0, $limit=null)
318     {
319         $qry =
320           'SELECT user_group.* ' .
321           'FROM user_group JOIN group_member '.
322           'ON user_group.id = group_member.group_id ' .
323           'WHERE group_member.profile_id = %d ' .
324           'ORDER BY group_member.created DESC ';
325
326         if ($offset>0 && !is_null($limit)) {
327             if ($offset) {
328                 if (common_config('db','type') == 'pgsql') {
329                     $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
330                 } else {
331                     $qry .= ' LIMIT ' . $offset . ', ' . $limit;
332                 }
333             }
334         }
335
336         $groups = new User_group();
337
338         $cnt = $groups->query(sprintf($qry, $this->id));
339
340         return $groups;
341     }
342
343     function avatarUrl($size=AVATAR_PROFILE_SIZE)
344     {
345         $avatar = $this->getAvatar($size);
346         if ($avatar) {
347             return $avatar->displayUrl();
348         } else {
349             return Avatar::defaultImage($size);
350         }
351     }
352
353     function getSubscriptions($offset=0, $limit=null)
354     {
355         $qry =
356           'SELECT profile.* ' .
357           'FROM profile JOIN subscription ' .
358           'ON profile.id = subscription.subscribed ' .
359           'WHERE subscription.subscriber = %d ' .
360           'AND subscription.subscribed != subscription.subscriber ' .
361           'ORDER BY subscription.created DESC ';
362
363         if ($offset>0 && !is_null($limit)){
364             if (common_config('db','type') == 'pgsql') {
365                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
366             } else {
367                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
368             }
369         }
370
371         $profile = new Profile();
372
373         $profile->query(sprintf($qry, $this->id));
374
375         return $profile;
376     }
377
378     function getSubscribers($offset=0, $limit=null)
379     {
380         $qry =
381           'SELECT profile.* ' .
382           'FROM profile JOIN subscription ' .
383           'ON profile.id = subscription.subscriber ' .
384           'WHERE subscription.subscribed = %d ' .
385           'AND subscription.subscribed != subscription.subscriber ' .
386           'ORDER BY subscription.created DESC ';
387
388         if ($offset>0 && !is_null($limit)){
389             if ($offset) {
390                 if (common_config('db','type') == 'pgsql') {
391                     $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
392                 } else {
393                     $qry .= ' LIMIT ' . $offset . ', ' . $limit;
394                 }
395             }
396         }
397
398         $profile = new Profile();
399
400         $cnt = $profile->query(sprintf($qry, $this->id));
401
402         return $profile;
403     }
404
405     function getApplications($offset = 0, $limit = null)
406     {
407         $qry =
408           'SELECT a.* ' .
409           'FROM oauth_application_user u, oauth_application a ' .
410           'WHERE u.profile_id = %d ' .
411           'AND a.id = u.application_id ' .
412           'AND u.access_type > 0 ' .
413           'ORDER BY u.created DESC ';
414
415         if ($offset > 0) {
416             if (common_config('db','type') == 'pgsql') {
417                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
418             } else {
419                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
420             }
421         }
422
423         $application = new Oauth_application();
424
425         $cnt = $application->query(sprintf($qry, $this->id));
426
427         return $application;
428     }
429
430     function subscriptionCount()
431     {
432         $c = common_memcache();
433
434         if (!empty($c)) {
435             $cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id));
436             if (is_integer($cnt)) {
437                 return (int) $cnt;
438             }
439         }
440
441         $sub = new Subscription();
442         $sub->subscriber = $this->id;
443
444         $cnt = (int) $sub->count('distinct subscribed');
445
446         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
447
448         if (!empty($c)) {
449             $c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt);
450         }
451
452         return $cnt;
453     }
454
455     function subscriberCount()
456     {
457         $c = common_memcache();
458         if (!empty($c)) {
459             $cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id));
460             if (is_integer($cnt)) {
461                 return (int) $cnt;
462             }
463         }
464
465         $sub = new Subscription();
466         $sub->subscribed = $this->id;
467         $sub->whereAdd('subscriber != subscribed');
468         $cnt = (int) $sub->count('distinct subscriber');
469
470         if (!empty($c)) {
471             $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
472         }
473
474         return $cnt;
475     }
476
477     function hasFave($notice)
478     {
479         $cache = common_memcache();
480
481         // XXX: Kind of a hack.
482
483         if (!empty($cache)) {
484             // This is the stream of favorite notices, in rev chron
485             // order. This forces it into cache.
486
487             $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW);
488
489             // If it's in the list, then it's a fave
490
491             if (in_array($notice->id, $ids)) {
492                 return true;
493             }
494
495             // If we're not past the end of the cache window,
496             // then the cache has all available faves, so this one
497             // is not a fave.
498
499             if (count($ids) < NOTICE_CACHE_WINDOW) {
500                 return false;
501             }
502
503             // Otherwise, cache doesn't have all faves;
504             // fall through to the default
505         }
506
507         $fave = Fave::pkeyGet(array('user_id' => $this->id,
508                                     'notice_id' => $notice->id));
509         return ((is_null($fave)) ? false : true);
510     }
511
512     function faveCount()
513     {
514         $c = common_memcache();
515         if (!empty($c)) {
516             $cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id));
517             if (is_integer($cnt)) {
518                 return (int) $cnt;
519             }
520         }
521
522         $faves = new Fave();
523         $faves->user_id = $this->id;
524         $cnt = (int) $faves->count('distinct notice_id');
525
526         if (!empty($c)) {
527             $c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
528         }
529
530         return $cnt;
531     }
532
533     function noticeCount()
534     {
535         $c = common_memcache();
536
537         if (!empty($c)) {
538             $cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id));
539             if (is_integer($cnt)) {
540                 return (int) $cnt;
541             }
542         }
543
544         $notices = new Notice();
545         $notices->profile_id = $this->id;
546         $cnt = (int) $notices->count('distinct id');
547
548         if (!empty($c)) {
549             $c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
550         }
551
552         return $cnt;
553     }
554
555     function blowFavesCache()
556     {
557         $cache = common_memcache();
558         if ($cache) {
559             // Faves don't happen chronologically, so we need to blow
560             // ;last cache, too
561             $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id));
562             $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last'));
563             $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id));
564             $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
565         }
566         $this->blowFaveCount();
567     }
568
569     function blowSubscriberCount()
570     {
571         $c = common_memcache();
572         if (!empty($c)) {
573             $c->delete(common_cache_key('profile:subscriber_count:'.$this->id));
574         }
575     }
576
577     function blowSubscriptionCount()
578     {
579         $c = common_memcache();
580         if (!empty($c)) {
581             $c->delete(common_cache_key('profile:subscription_count:'.$this->id));
582         }
583     }
584
585     function blowFaveCount()
586     {
587         $c = common_memcache();
588         if (!empty($c)) {
589             $c->delete(common_cache_key('profile:fave_count:'.$this->id));
590         }
591     }
592
593     function blowNoticeCount()
594     {
595         $c = common_memcache();
596         if (!empty($c)) {
597             $c->delete(common_cache_key('profile:notice_count:'.$this->id));
598         }
599     }
600
601     static function maxBio()
602     {
603         $biolimit = common_config('profile', 'biolimit');
604         // null => use global limit (distinct from 0!)
605         if (is_null($biolimit)) {
606             $biolimit = common_config('site', 'textlimit');
607         }
608         return $biolimit;
609     }
610
611     static function bioTooLong($bio)
612     {
613         $biolimit = self::maxBio();
614         return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
615     }
616
617     function delete()
618     {
619         $this->_deleteNotices();
620         $this->_deleteSubscriptions();
621         $this->_deleteMessages();
622         $this->_deleteTags();
623         $this->_deleteBlocks();
624
625         $related = array('Avatar',
626                          'Reply',
627                          'Group_member',
628                          );
629         Event::handle('ProfileDeleteRelated', array($this, &$related));
630
631         foreach ($related as $cls) {
632             $inst = new $cls();
633             $inst->profile_id = $this->id;
634             $inst->delete();
635         }
636
637         parent::delete();
638     }
639
640     function _deleteNotices()
641     {
642         $notice = new Notice();
643         $notice->profile_id = $this->id;
644
645         if ($notice->find()) {
646             while ($notice->fetch()) {
647                 $other = clone($notice);
648                 $other->delete();
649             }
650         }
651     }
652
653     function _deleteSubscriptions()
654     {
655         $sub = new Subscription();
656         $sub->subscriber = $this->id;
657
658         $sub->find();
659
660         while ($sub->fetch()) {
661             $other = Profile::staticGet('id', $sub->subscribed);
662             if (empty($other)) {
663                 continue;
664             }
665             if ($other->id == $this->id) {
666                 continue;
667             }
668             Subscription::cancel($this, $other);
669         }
670
671         $subd = new Subscription();
672         $subd->subscribed = $this->id;
673         $subd->find();
674
675         while ($subd->fetch()) {
676             $other = Profile::staticGet('id', $subd->subscriber);
677             if (empty($other)) {
678                 continue;
679             }
680             if ($other->id == $this->id) {
681                 continue;
682             }
683             Subscription::cancel($other, $this);
684         }
685
686         $self = new Subscription();
687
688         $self->subscriber = $this->id;
689         $self->subscribed = $this->id;
690
691         $self->delete();
692     }
693
694     function _deleteMessages()
695     {
696         $msg = new Message();
697         $msg->from_profile = $this->id;
698         $msg->delete();
699
700         $msg = new Message();
701         $msg->to_profile = $this->id;
702         $msg->delete();
703     }
704
705     function _deleteTags()
706     {
707         $tag = new Profile_tag();
708         $tag->tagged = $this->id;
709         $tag->delete();
710     }
711
712     function _deleteBlocks()
713     {
714         $block = new Profile_block();
715         $block->blocked = $this->id;
716         $block->delete();
717
718         $block = new Group_block();
719         $block->blocked = $this->id;
720         $block->delete();
721     }
722
723     // XXX: identical to Notice::getLocation.
724
725     function getLocation()
726     {
727         $location = null;
728
729         if (!empty($this->location_id) && !empty($this->location_ns)) {
730             $location = Location::fromId($this->location_id, $this->location_ns);
731         }
732
733         if (is_null($location)) { // no ID, or Location::fromId() failed
734             if (!empty($this->lat) && !empty($this->lon)) {
735                 $location = Location::fromLatLon($this->lat, $this->lon);
736             }
737         }
738
739         if (is_null($location)) { // still haven't found it!
740             if (!empty($this->location)) {
741                 $location = Location::fromName($this->location);
742             }
743         }
744
745         return $location;
746     }
747
748     function hasRole($name)
749     {
750         $has_role = false;
751         if (Event::handle('StartHasRole', array($this, $name, &$has_role))) {
752             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
753                                                 'role' => $name));
754             $has_role = !empty($role);
755             Event::handle('EndHasRole', array($this, $name, $has_role));
756         }
757         return $has_role;
758     }
759
760     function grantRole($name)
761     {
762         $role = new Profile_role();
763
764         $role->profile_id = $this->id;
765         $role->role       = $name;
766         $role->created    = common_sql_now();
767
768         $result = $role->insert();
769
770         if (!$result) {
771             common_log_db_error($role, 'INSERT', __FILE__);
772             return false;
773         }
774
775         return true;
776     }
777
778     function revokeRole($name)
779     {
780         $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
781                                             'role' => $name));
782
783         if (empty($role)) {
784             // TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
785             // TRANS: %1$s is the role name, %2$s is the user ID (number).
786             throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
787         }
788
789         $result = $role->delete();
790
791         if (!$result) {
792             common_log_db_error($role, 'DELETE', __FILE__);
793             // TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
794             // TRANS: %1$s is the role name, %2$s is the user ID (number).
795             throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
796         }
797
798         return true;
799     }
800
801     function isSandboxed()
802     {
803         return $this->hasRole(Profile_role::SANDBOXED);
804     }
805
806     function isSilenced()
807     {
808         return $this->hasRole(Profile_role::SILENCED);
809     }
810
811     function sandbox()
812     {
813         $this->grantRole(Profile_role::SANDBOXED);
814     }
815
816     function unsandbox()
817     {
818         $this->revokeRole(Profile_role::SANDBOXED);
819     }
820
821     function silence()
822     {
823         $this->grantRole(Profile_role::SILENCED);
824     }
825
826     function unsilence()
827     {
828         $this->revokeRole(Profile_role::SILENCED);
829     }
830
831     /**
832      * Does this user have the right to do X?
833      *
834      * With our role-based authorization, this is merely a lookup for whether the user
835      * has a particular role. The implementation currently uses a switch statement
836      * to determine if the user has the pre-defined role to exercise the right. Future
837      * implementations may allow per-site roles, and different mappings of roles to rights.
838      *
839      * @param $right string Name of the right, usually a constant in class Right
840      * @return boolean whether the user has the right in question
841      */
842
843     function hasRight($right)
844     {
845         $result = false;
846         if ($this->hasRole(Profile_role::DELETED)) {
847             return false;
848         }
849         if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
850             switch ($right)
851             {
852             case Right::DELETEOTHERSNOTICE:
853             case Right::MAKEGROUPADMIN:
854             case Right::SANDBOXUSER:
855             case Right::SILENCEUSER:
856             case Right::DELETEUSER:
857                 $result = $this->hasRole(Profile_role::MODERATOR);
858                 break;
859             case Right::CONFIGURESITE:
860                 $result = $this->hasRole(Profile_role::ADMINISTRATOR);
861                 break;
862             case Right::GRANTROLE:
863             case Right::REVOKEROLE:
864                 $result = $this->hasRole(Profile_role::OWNER);
865                 break;
866             case Right::NEWNOTICE:
867             case Right::NEWMESSAGE:
868             case Right::SUBSCRIBE:
869                 $result = !$this->isSilenced();
870                 break;
871             case Right::PUBLICNOTICE:
872             case Right::EMAILONREPLY:
873             case Right::EMAILONSUBSCRIBE:
874             case Right::EMAILONFAVE:
875                 $result = !$this->isSandboxed();
876                 break;
877             default:
878                 $result = false;
879                 break;
880             }
881         }
882         return $result;
883     }
884
885     function hasRepeated($notice_id)
886     {
887         // XXX: not really a pkey, but should work
888
889         $notice = Memcached_DataObject::pkeyGet('Notice',
890                                                 array('profile_id' => $this->id,
891                                                       'repeat_of' => $notice_id));
892
893         return !empty($notice);
894     }
895
896     /**
897      * Returns an XML string fragment with limited profile information
898      * as an Atom <author> element.
899      *
900      * Assumes that Atom has been previously set up as the base namespace.
901      *
902      * @param Profile $cur the current authenticated user
903      *
904      * @return string
905      */
906     function asAtomAuthor($cur = null)
907     {
908         $xs = new XMLStringer(true);
909
910         $xs->elementStart('author');
911         $xs->element('name', null, $this->nickname);
912         $xs->element('uri', null, $this->getUri());
913         if ($cur != null) {
914             $attrs = Array();
915             $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
916             $attrs['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
917             $xs->element('statusnet:profile_info', $attrs, null);
918         }
919         $xs->elementEnd('author');
920
921         return $xs->getString();
922     }
923
924     /**
925      * Returns an XML string fragment with profile information as an
926      * Activity Streams <activity:actor> element.
927      *
928      * Assumes that 'activity' namespace has been previously defined.
929      *
930      * @return string
931      */
932     function asActivityActor()
933     {
934         return $this->asActivityNoun('actor');
935     }
936
937     /**
938      * Returns an XML string fragment with profile information as an
939      * Activity Streams noun object with the given element type.
940      *
941      * Assumes that 'activity', 'georss', and 'poco' namespace has been
942      * previously defined.
943      *
944      * @param string $element one of 'actor', 'subject', 'object', 'target'
945      *
946      * @return string
947      */
948     function asActivityNoun($element)
949     {
950         $noun = ActivityObject::fromProfile($this);
951         return $noun->asString('activity:' . $element);
952     }
953
954     /**
955      * Returns the best URI for a profile. Plugins may override.
956      *
957      * @return string $uri
958      */
959     function getUri()
960     {
961         $uri = null;
962
963         // give plugins a chance to set the URI
964         if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
965
966             // check for a local user first
967             $user = User::staticGet('id', $this->id);
968
969             if (!empty($user)) {
970                 $uri = $user->uri;
971             } else {
972                 // return OMB profile if any
973                 $remote = Remote_profile::staticGet('id', $this->id);
974                 if (!empty($remote)) {
975                     $uri = $remote->uri;
976                 }
977             }
978             Event::handle('EndGetProfileUri', array($this, &$uri));
979         }
980
981         return $uri;
982     }
983
984     function hasBlocked($other)
985     {
986         $block = Profile_block::get($this->id, $other->id);
987
988         if (empty($block)) {
989             $result = false;
990         } else {
991             $result = true;
992         }
993
994         return $result;
995     }
996
997     function getAtomFeed()
998     {
999         $feed = null;
1000
1001         if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
1002             $user = User::staticGet('id', $this->id);
1003             if (!empty($user)) {
1004                 $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
1005                                                                   'format' => 'atom'));
1006             }
1007             Event::handle('EndProfileGetAtomFeed', array($this, $feed));
1008         }
1009
1010         return $feed;
1011     }
1012
1013     static function fromURI($uri)
1014     {
1015         $profile = null;
1016
1017         if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
1018             // Get a local user or remote (OMB 0.1) profile
1019             $user = User::staticGet('uri', $uri);
1020             if (!empty($user)) {
1021                 $profile = $user->getProfile();
1022             } else {
1023                 $remote_profile = Remote_profile::staticGet('uri', $uri);
1024                 if (!empty($remote_profile)) {
1025                     $profile = Profile::staticGet('id', $remote_profile->profile_id);
1026                 }
1027             }
1028             Event::handle('EndGetProfileFromURI', array($uri, $profile));
1029         }
1030
1031         return $profile;
1032     }
1033 }