]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/Profile.php
00e076a624744a677650177134401bd3aca322a3
[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 getConnectedApps($offset = 0, $limit = null)
413     {
414         $qry =
415           'SELECT u.* ' .
416           'FROM oauth_application_user u, oauth_application a ' .
417           'WHERE u.profile_id = %d ' .
418           'AND a.id = u.application_id ' .
419           'AND u.access_type > 0 ' .
420           'ORDER BY u.created DESC ';
421
422         if ($offset > 0) {
423             if (common_config('db','type') == 'pgsql') {
424                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
425             } else {
426                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
427             }
428         }
429
430         $apps = new Oauth_application_user();
431
432         $cnt = $apps->query(sprintf($qry, $this->id));
433
434         return $apps;
435     }
436
437     function subscriptionCount()
438     {
439         $c = common_memcache();
440
441         if (!empty($c)) {
442             $cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id));
443             if (is_integer($cnt)) {
444                 return (int) $cnt;
445             }
446         }
447
448         $sub = new Subscription();
449         $sub->subscriber = $this->id;
450
451         $cnt = (int) $sub->count('distinct subscribed');
452
453         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
454
455         if (!empty($c)) {
456             $c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt);
457         }
458
459         return $cnt;
460     }
461
462     function subscriberCount()
463     {
464         $c = common_memcache();
465         if (!empty($c)) {
466             $cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id));
467             if (is_integer($cnt)) {
468                 return (int) $cnt;
469             }
470         }
471
472         $sub = new Subscription();
473         $sub->subscribed = $this->id;
474         $sub->whereAdd('subscriber != subscribed');
475         $cnt = (int) $sub->count('distinct subscriber');
476
477         if (!empty($c)) {
478             $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
479         }
480
481         return $cnt;
482     }
483
484     /**
485      * Is this profile subscribed to another profile?
486      *
487      * @param Profile $other
488      * @return boolean
489      */
490     function isSubscribed($other)
491     {
492         return Subscription::exists($this, $other);
493     }
494
495     /**
496      * Are these two profiles subscribed to each other?
497      *
498      * @param Profile $other
499      * @return boolean
500      */
501     function mutuallySubscribed($other)
502     {
503         return $this->isSubscribed($other) &&
504           $other->isSubscribed($this);
505     }
506
507     function hasFave($notice)
508     {
509         $cache = common_memcache();
510
511         // XXX: Kind of a hack.
512
513         if (!empty($cache)) {
514             // This is the stream of favorite notices, in rev chron
515             // order. This forces it into cache.
516
517             $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW);
518
519             // If it's in the list, then it's a fave
520
521             if (in_array($notice->id, $ids)) {
522                 return true;
523             }
524
525             // If we're not past the end of the cache window,
526             // then the cache has all available faves, so this one
527             // is not a fave.
528
529             if (count($ids) < NOTICE_CACHE_WINDOW) {
530                 return false;
531             }
532
533             // Otherwise, cache doesn't have all faves;
534             // fall through to the default
535         }
536
537         $fave = Fave::pkeyGet(array('user_id' => $this->id,
538                                     'notice_id' => $notice->id));
539         return ((is_null($fave)) ? false : true);
540     }
541
542     function faveCount()
543     {
544         $c = common_memcache();
545         if (!empty($c)) {
546             $cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id));
547             if (is_integer($cnt)) {
548                 return (int) $cnt;
549             }
550         }
551
552         $faves = new Fave();
553         $faves->user_id = $this->id;
554         $cnt = (int) $faves->count('distinct notice_id');
555
556         if (!empty($c)) {
557             $c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
558         }
559
560         return $cnt;
561     }
562
563     function noticeCount()
564     {
565         $c = common_memcache();
566
567         if (!empty($c)) {
568             $cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id));
569             if (is_integer($cnt)) {
570                 return (int) $cnt;
571             }
572         }
573
574         $notices = new Notice();
575         $notices->profile_id = $this->id;
576         $cnt = (int) $notices->count('distinct id');
577
578         if (!empty($c)) {
579             $c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
580         }
581
582         return $cnt;
583     }
584
585     function blowFavesCache()
586     {
587         $cache = common_memcache();
588         if ($cache) {
589             // Faves don't happen chronologically, so we need to blow
590             // ;last cache, too
591             $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id));
592             $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last'));
593             $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id));
594             $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
595         }
596         $this->blowFaveCount();
597     }
598
599     function blowSubscriberCount()
600     {
601         $c = common_memcache();
602         if (!empty($c)) {
603             $c->delete(common_cache_key('profile:subscriber_count:'.$this->id));
604         }
605     }
606
607     function blowSubscriptionCount()
608     {
609         $c = common_memcache();
610         if (!empty($c)) {
611             $c->delete(common_cache_key('profile:subscription_count:'.$this->id));
612         }
613     }
614
615     function blowFaveCount()
616     {
617         $c = common_memcache();
618         if (!empty($c)) {
619             $c->delete(common_cache_key('profile:fave_count:'.$this->id));
620         }
621     }
622
623     function blowNoticeCount()
624     {
625         $c = common_memcache();
626         if (!empty($c)) {
627             $c->delete(common_cache_key('profile:notice_count:'.$this->id));
628         }
629     }
630
631     static function maxBio()
632     {
633         $biolimit = common_config('profile', 'biolimit');
634         // null => use global limit (distinct from 0!)
635         if (is_null($biolimit)) {
636             $biolimit = common_config('site', 'textlimit');
637         }
638         return $biolimit;
639     }
640
641     static function bioTooLong($bio)
642     {
643         $biolimit = self::maxBio();
644         return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
645     }
646
647     function delete()
648     {
649         $this->_deleteNotices();
650         $this->_deleteSubscriptions();
651         $this->_deleteMessages();
652         $this->_deleteTags();
653         $this->_deleteBlocks();
654         $this->delete_avatars();
655
656         // Warning: delete() will run on the batch objects,
657         // not on individual objects.
658         $related = array('Reply',
659                          'Group_member',
660                          );
661         Event::handle('ProfileDeleteRelated', array($this, &$related));
662
663         foreach ($related as $cls) {
664             $inst = new $cls();
665             $inst->profile_id = $this->id;
666             $inst->delete();
667         }
668
669         parent::delete();
670     }
671
672     function _deleteNotices()
673     {
674         $notice = new Notice();
675         $notice->profile_id = $this->id;
676
677         if ($notice->find()) {
678             while ($notice->fetch()) {
679                 $other = clone($notice);
680                 $other->delete();
681             }
682         }
683     }
684
685     function _deleteSubscriptions()
686     {
687         $sub = new Subscription();
688         $sub->subscriber = $this->id;
689
690         $sub->find();
691
692         while ($sub->fetch()) {
693             $other = Profile::staticGet('id', $sub->subscribed);
694             if (empty($other)) {
695                 continue;
696             }
697             if ($other->id == $this->id) {
698                 continue;
699             }
700             Subscription::cancel($this, $other);
701         }
702
703         $subd = new Subscription();
704         $subd->subscribed = $this->id;
705         $subd->find();
706
707         while ($subd->fetch()) {
708             $other = Profile::staticGet('id', $subd->subscriber);
709             if (empty($other)) {
710                 continue;
711             }
712             if ($other->id == $this->id) {
713                 continue;
714             }
715             Subscription::cancel($other, $this);
716         }
717
718         $self = new Subscription();
719
720         $self->subscriber = $this->id;
721         $self->subscribed = $this->id;
722
723         $self->delete();
724     }
725
726     function _deleteMessages()
727     {
728         $msg = new Message();
729         $msg->from_profile = $this->id;
730         $msg->delete();
731
732         $msg = new Message();
733         $msg->to_profile = $this->id;
734         $msg->delete();
735     }
736
737     function _deleteTags()
738     {
739         $tag = new Profile_tag();
740         $tag->tagged = $this->id;
741         $tag->delete();
742     }
743
744     function _deleteBlocks()
745     {
746         $block = new Profile_block();
747         $block->blocked = $this->id;
748         $block->delete();
749
750         $block = new Group_block();
751         $block->blocked = $this->id;
752         $block->delete();
753     }
754
755     // XXX: identical to Notice::getLocation.
756
757     function getLocation()
758     {
759         $location = null;
760
761         if (!empty($this->location_id) && !empty($this->location_ns)) {
762             $location = Location::fromId($this->location_id, $this->location_ns);
763         }
764
765         if (is_null($location)) { // no ID, or Location::fromId() failed
766             if (!empty($this->lat) && !empty($this->lon)) {
767                 $location = Location::fromLatLon($this->lat, $this->lon);
768             }
769         }
770
771         if (is_null($location)) { // still haven't found it!
772             if (!empty($this->location)) {
773                 $location = Location::fromName($this->location);
774             }
775         }
776
777         return $location;
778     }
779
780     function hasRole($name)
781     {
782         $has_role = false;
783         if (Event::handle('StartHasRole', array($this, $name, &$has_role))) {
784             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
785                                                 'role' => $name));
786             $has_role = !empty($role);
787             Event::handle('EndHasRole', array($this, $name, $has_role));
788         }
789         return $has_role;
790     }
791
792     function grantRole($name)
793     {
794         if (Event::handle('StartGrantRole', array($this, $name))) {
795
796             $role = new Profile_role();
797
798             $role->profile_id = $this->id;
799             $role->role       = $name;
800             $role->created    = common_sql_now();
801
802             $result = $role->insert();
803
804             if (!$result) {
805                 throw new Exception("Can't save role '$name' for profile '{$this->id}'");
806             }
807
808             Event::handle('EndGrantRole', array($this, $name));
809         }
810
811         return $result;
812     }
813
814     function revokeRole($name)
815     {
816         if (Event::handle('StartRevokeRole', array($this, $name))) {
817
818             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
819                                                 'role' => $name));
820
821             if (empty($role)) {
822                 // TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
823                 // TRANS: %1$s is the role name, %2$s is the user ID (number).
824                 throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
825             }
826
827             $result = $role->delete();
828
829             if (!$result) {
830                 common_log_db_error($role, 'DELETE', __FILE__);
831                 // TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
832                 // TRANS: %1$s is the role name, %2$s is the user ID (number).
833                 throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
834             }
835
836             Event::handle('EndRevokeRole', array($this, $name));
837
838             return true;
839         }
840     }
841
842     function isSandboxed()
843     {
844         return $this->hasRole(Profile_role::SANDBOXED);
845     }
846
847     function isSilenced()
848     {
849         return $this->hasRole(Profile_role::SILENCED);
850     }
851
852     function sandbox()
853     {
854         $this->grantRole(Profile_role::SANDBOXED);
855     }
856
857     function unsandbox()
858     {
859         $this->revokeRole(Profile_role::SANDBOXED);
860     }
861
862     function silence()
863     {
864         $this->grantRole(Profile_role::SILENCED);
865     }
866
867     function unsilence()
868     {
869         $this->revokeRole(Profile_role::SILENCED);
870     }
871
872     /**
873      * Does this user have the right to do X?
874      *
875      * With our role-based authorization, this is merely a lookup for whether the user
876      * has a particular role. The implementation currently uses a switch statement
877      * to determine if the user has the pre-defined role to exercise the right. Future
878      * implementations may allow per-site roles, and different mappings of roles to rights.
879      *
880      * @param $right string Name of the right, usually a constant in class Right
881      * @return boolean whether the user has the right in question
882      */
883     function hasRight($right)
884     {
885         $result = false;
886
887         if ($this->hasRole(Profile_role::DELETED)) {
888             return false;
889         }
890
891         if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
892             switch ($right)
893             {
894             case Right::DELETEOTHERSNOTICE:
895             case Right::MAKEGROUPADMIN:
896             case Right::SANDBOXUSER:
897             case Right::SILENCEUSER:
898             case Right::DELETEUSER:
899             case Right::DELETEGROUP:
900                 $result = $this->hasRole(Profile_role::MODERATOR);
901                 break;
902             case Right::CONFIGURESITE:
903                 $result = $this->hasRole(Profile_role::ADMINISTRATOR);
904                 break;
905             case Right::GRANTROLE:
906             case Right::REVOKEROLE:
907                 $result = $this->hasRole(Profile_role::OWNER);
908                 break;
909             case Right::NEWNOTICE:
910             case Right::NEWMESSAGE:
911             case Right::SUBSCRIBE:
912             case Right::CREATEGROUP:
913                 $result = !$this->isSilenced();
914                 break;
915             case Right::PUBLICNOTICE:
916             case Right::EMAILONREPLY:
917             case Right::EMAILONSUBSCRIBE:
918             case Right::EMAILONFAVE:
919                 $result = !$this->isSandboxed();
920                 break;
921             default:
922                 $result = false;
923                 break;
924             }
925         }
926         return $result;
927     }
928
929     function hasRepeated($notice_id)
930     {
931         // XXX: not really a pkey, but should work
932
933         $notice = Memcached_DataObject::pkeyGet('Notice',
934                                                 array('profile_id' => $this->id,
935                                                       'repeat_of' => $notice_id));
936
937         return !empty($notice);
938     }
939
940     /**
941      * Returns an XML string fragment with limited profile information
942      * as an Atom <author> element.
943      *
944      * Assumes that Atom has been previously set up as the base namespace.
945      *
946      * @param Profile $cur the current authenticated user
947      *
948      * @return string
949      */
950     function asAtomAuthor($cur = null)
951     {
952         $xs = new XMLStringer(true);
953
954         $xs->elementStart('author');
955         $xs->element('name', null, $this->nickname);
956         $xs->element('uri', null, $this->getUri());
957         if ($cur != null) {
958             $attrs = Array();
959             $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
960             $attrs['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
961             $xs->element('statusnet:profile_info', $attrs, null);
962         }
963         $xs->elementEnd('author');
964
965         return $xs->getString();
966     }
967
968     /**
969      * Returns an XML string fragment with profile information as an
970      * Activity Streams <activity:actor> element.
971      *
972      * Assumes that 'activity' namespace has been previously defined.
973      *
974      * @return string
975      */
976     function asActivityActor()
977     {
978         return $this->asActivityNoun('actor');
979     }
980
981     /**
982      * Returns an XML string fragment with profile information as an
983      * Activity Streams noun object with the given element type.
984      *
985      * Assumes that 'activity', 'georss', and 'poco' namespace has been
986      * previously defined.
987      *
988      * @param string $element one of 'actor', 'subject', 'object', 'target'
989      *
990      * @return string
991      */
992     function asActivityNoun($element)
993     {
994         $noun = ActivityObject::fromProfile($this);
995         return $noun->asString('activity:' . $element);
996     }
997
998     /**
999      * Returns the best URI for a profile. Plugins may override.
1000      *
1001      * @return string $uri
1002      */
1003     function getUri()
1004     {
1005         $uri = null;
1006
1007         // give plugins a chance to set the URI
1008         if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
1009
1010             // check for a local user first
1011             $user = User::staticGet('id', $this->id);
1012
1013             if (!empty($user)) {
1014                 $uri = $user->uri;
1015             } else {
1016                 // return OMB profile if any
1017                 $remote = Remote_profile::staticGet('id', $this->id);
1018                 if (!empty($remote)) {
1019                     $uri = $remote->uri;
1020                 }
1021             }
1022             Event::handle('EndGetProfileUri', array($this, &$uri));
1023         }
1024
1025         return $uri;
1026     }
1027
1028     function hasBlocked($other)
1029     {
1030         $block = Profile_block::get($this->id, $other->id);
1031
1032         if (empty($block)) {
1033             $result = false;
1034         } else {
1035             $result = true;
1036         }
1037
1038         return $result;
1039     }
1040
1041     function getAtomFeed()
1042     {
1043         $feed = null;
1044
1045         if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
1046             $user = User::staticGet('id', $this->id);
1047             if (!empty($user)) {
1048                 $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
1049                                                                   'format' => 'atom'));
1050             }
1051             Event::handle('EndProfileGetAtomFeed', array($this, $feed));
1052         }
1053
1054         return $feed;
1055     }
1056
1057     static function fromURI($uri)
1058     {
1059         $profile = null;
1060
1061         if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
1062             // Get a local user or remote (OMB 0.1) profile
1063             $user = User::staticGet('uri', $uri);
1064             if (!empty($user)) {
1065                 $profile = $user->getProfile();
1066             } else {
1067                 $remote_profile = Remote_profile::staticGet('uri', $uri);
1068                 if (!empty($remote_profile)) {
1069                     $profile = Profile::staticGet('id', $remote_profile->profile_id);
1070                 }
1071             }
1072             Event::handle('EndGetProfileFromURI', array($uri, $profile));
1073         }
1074
1075         return $profile;
1076     }
1077 }