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