]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/Profile.php
Merge branch 'testing' into 0.9.x
[quix0rs-gnu-social.git] / classes / Profile.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22 /**
23  * Table Definition for profile
24  */
25 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
26
27 class Profile extends Memcached_DataObject
28 {
29     ###START_AUTOCODE
30     /* the code below is auto generated do not remove the above tag */
31
32     public $__table = 'profile';                         // table name
33     public $id;                              // int(4)  primary_key not_null
34     public $nickname;                        // varchar(64)  multiple_key not_null
35     public $fullname;                        // varchar(255)  multiple_key
36     public $profileurl;                      // varchar(255)
37     public $homepage;                        // varchar(255)  multiple_key
38     public $bio;                             // text()  multiple_key
39     public $location;                        // varchar(255)  multiple_key
40     public $lat;                             // decimal(10,7)
41     public $lon;                             // decimal(10,7)
42     public $location_id;                     // int(4)
43     public $location_ns;                     // int(4)
44     public $created;                         // datetime()   not_null
45     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
46
47     /* Static get */
48     function staticGet($k,$v=NULL) {
49         return Memcached_DataObject::staticGet('Profile',$k,$v);
50     }
51
52     /* the code above is auto generated do not remove the tag below */
53     ###END_AUTOCODE
54
55     function getUser()
56     {
57         return User::staticGet('id', $this->id);
58     }
59
60     function getAvatar($width, $height=null)
61     {
62         if (is_null($height)) {
63             $height = $width;
64         }
65         return Avatar::pkeyGet(array('profile_id' => $this->id,
66                                      'width' => $width,
67                                      'height' => $height));
68     }
69
70     function getOriginalAvatar()
71     {
72         $avatar = DB_DataObject::factory('avatar');
73         $avatar->profile_id = $this->id;
74         $avatar->original = true;
75         if ($avatar->find(true)) {
76             return $avatar;
77         } else {
78             return null;
79         }
80     }
81
82     function setOriginal($filename)
83     {
84         $imagefile = new ImageFile($this->id, Avatar::path($filename));
85
86         $avatar = new Avatar();
87         $avatar->profile_id = $this->id;
88         $avatar->width = $imagefile->width;
89         $avatar->height = $imagefile->height;
90         $avatar->mediatype = image_type_to_mime_type($imagefile->type);
91         $avatar->filename = $filename;
92         $avatar->original = true;
93         $avatar->url = Avatar::url($filename);
94         $avatar->created = DB_DataObject_Cast::dateTime(); # current time
95
96         # XXX: start a transaction here
97
98         if (!$this->delete_avatars() || !$avatar->insert()) {
99             @unlink(Avatar::path($filename));
100             return null;
101         }
102
103         foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
104             # We don't do a scaled one if original is our scaled size
105             if (!($avatar->width == $size && $avatar->height == $size)) {
106
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
468         $cnt = (int) $sub->count('distinct subscriber');
469
470         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
471
472         if (!empty($c)) {
473             $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
474         }
475
476         return $cnt;
477     }
478
479     function faveCount()
480     {
481         $c = common_memcache();
482         if (!empty($c)) {
483             $cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id));
484             if (is_integer($cnt)) {
485                 return (int) $cnt;
486             }
487         }
488
489         $faves = new Fave();
490         $faves->user_id = $this->id;
491         $cnt = (int) $faves->count('distinct notice_id');
492
493         if (!empty($c)) {
494             $c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
495         }
496
497         return $cnt;
498     }
499
500     function noticeCount()
501     {
502         $c = common_memcache();
503
504         if (!empty($c)) {
505             $cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id));
506             if (is_integer($cnt)) {
507                 return (int) $cnt;
508             }
509         }
510
511         $notices = new Notice();
512         $notices->profile_id = $this->id;
513         $cnt = (int) $notices->count('distinct id');
514
515         if (!empty($c)) {
516             $c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
517         }
518
519         return $cnt;
520     }
521
522     function blowSubscriberCount()
523     {
524         $c = common_memcache();
525         if (!empty($c)) {
526             $c->delete(common_cache_key('profile:subscriber_count:'.$this->id));
527         }
528     }
529
530     function blowSubscriptionCount()
531     {
532         $c = common_memcache();
533         if (!empty($c)) {
534             $c->delete(common_cache_key('profile:subscription_count:'.$this->id));
535         }
536     }
537
538     function blowFaveCount()
539     {
540         $c = common_memcache();
541         if (!empty($c)) {
542             $c->delete(common_cache_key('profile:fave_count:'.$this->id));
543         }
544     }
545
546     function blowNoticeCount()
547     {
548         $c = common_memcache();
549         if (!empty($c)) {
550             $c->delete(common_cache_key('profile:notice_count:'.$this->id));
551         }
552     }
553
554     static function maxBio()
555     {
556         $biolimit = common_config('profile', 'biolimit');
557         // null => use global limit (distinct from 0!)
558         if (is_null($biolimit)) {
559             $biolimit = common_config('site', 'textlimit');
560         }
561         return $biolimit;
562     }
563
564     static function bioTooLong($bio)
565     {
566         $biolimit = self::maxBio();
567         return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
568     }
569
570     function delete()
571     {
572         $this->_deleteNotices();
573         $this->_deleteSubscriptions();
574         $this->_deleteMessages();
575         $this->_deleteTags();
576         $this->_deleteBlocks();
577
578         $related = array('Avatar',
579                          'Reply',
580                          'Group_member',
581                          );
582         Event::handle('ProfileDeleteRelated', array($this, &$related));
583
584         foreach ($related as $cls) {
585             $inst = new $cls();
586             $inst->profile_id = $this->id;
587             $inst->delete();
588         }
589
590         parent::delete();
591     }
592
593     function _deleteNotices()
594     {
595         $notice = new Notice();
596         $notice->profile_id = $this->id;
597
598         if ($notice->find()) {
599             while ($notice->fetch()) {
600                 $other = clone($notice);
601                 $other->delete();
602             }
603         }
604     }
605
606     function _deleteSubscriptions()
607     {
608         $sub = new Subscription();
609         $sub->subscriber = $this->id;
610
611         $sub->find();
612
613         while ($sub->fetch()) {
614             $other = Profile::staticGet('id', $sub->subscribed);
615             if (empty($other)) {
616                 continue;
617             }
618             if ($other->id == $this->id) {
619                 continue;
620             }
621             Subscription::cancel($this, $other);
622         }
623
624         $subd = new Subscription();
625         $subd->subscribed = $this->id;
626         $subd->find();
627
628         while ($subd->fetch()) {
629             $other = Profile::staticGet('id', $subd->subscriber);
630             if (empty($other)) {
631                 continue;
632             }
633             if ($other->id == $this->id) {
634                 continue;
635             }
636             Subscription::cancel($other, $this);
637         }
638
639         $self = new Subscription();
640
641         $self->subscriber = $this->id;
642         $self->subscribed = $this->id;
643
644         $self->delete();
645     }
646
647     function _deleteMessages()
648     {
649         $msg = new Message();
650         $msg->from_profile = $this->id;
651         $msg->delete();
652
653         $msg = new Message();
654         $msg->to_profile = $this->id;
655         $msg->delete();
656     }
657
658     function _deleteTags()
659     {
660         $tag = new Profile_tag();
661         $tag->tagged = $this->id;
662         $tag->delete();
663     }
664
665     function _deleteBlocks()
666     {
667         $block = new Profile_block();
668         $block->blocked = $this->id;
669         $block->delete();
670
671         $block = new Group_block();
672         $block->blocked = $this->id;
673         $block->delete();
674     }
675
676     // XXX: identical to Notice::getLocation.
677
678     function getLocation()
679     {
680         $location = null;
681
682         if (!empty($this->location_id) && !empty($this->location_ns)) {
683             $location = Location::fromId($this->location_id, $this->location_ns);
684         }
685
686         if (is_null($location)) { // no ID, or Location::fromId() failed
687             if (!empty($this->lat) && !empty($this->lon)) {
688                 $location = Location::fromLatLon($this->lat, $this->lon);
689             }
690         }
691
692         if (is_null($location)) { // still haven't found it!
693             if (!empty($this->location)) {
694                 $location = Location::fromName($this->location);
695             }
696         }
697
698         return $location;
699     }
700
701     function hasRole($name)
702     {
703         $has_role = false;
704         if (Event::handle('StartHasRole', array($this, $name, &$has_role))) {
705             $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
706                                                 'role' => $name));
707             $has_role = !empty($role);
708             Event::handle('EndHasRole', array($this, $name, $has_role));
709         }
710         return $has_role;
711     }
712
713     function grantRole($name)
714     {
715         $role = new Profile_role();
716
717         $role->profile_id = $this->id;
718         $role->role       = $name;
719         $role->created    = common_sql_now();
720
721         $result = $role->insert();
722
723         if (!$result) {
724             common_log_db_error($role, 'INSERT', __FILE__);
725             return false;
726         }
727
728         return true;
729     }
730
731     function revokeRole($name)
732     {
733         $role = Profile_role::pkeyGet(array('profile_id' => $this->id,
734                                             'role' => $name));
735
736         if (empty($role)) {
737             // TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
738             // TRANS: %1$s is the role name, %2$s is the user ID (number).
739             throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
740         }
741
742         $result = $role->delete();
743
744         if (!$result) {
745             common_log_db_error($role, 'DELETE', __FILE__);
746             // TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
747             // TRANS: %1$s is the role name, %2$s is the user ID (number).
748             throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
749         }
750
751         return true;
752     }
753
754     function isSandboxed()
755     {
756         return $this->hasRole(Profile_role::SANDBOXED);
757     }
758
759     function isSilenced()
760     {
761         return $this->hasRole(Profile_role::SILENCED);
762     }
763
764     function sandbox()
765     {
766         $this->grantRole(Profile_role::SANDBOXED);
767     }
768
769     function unsandbox()
770     {
771         $this->revokeRole(Profile_role::SANDBOXED);
772     }
773
774     function silence()
775     {
776         $this->grantRole(Profile_role::SILENCED);
777     }
778
779     function unsilence()
780     {
781         $this->revokeRole(Profile_role::SILENCED);
782     }
783
784     /**
785      * Does this user have the right to do X?
786      *
787      * With our role-based authorization, this is merely a lookup for whether the user
788      * has a particular role. The implementation currently uses a switch statement
789      * to determine if the user has the pre-defined role to exercise the right. Future
790      * implementations may allow per-site roles, and different mappings of roles to rights.
791      *
792      * @param $right string Name of the right, usually a constant in class Right
793      * @return boolean whether the user has the right in question
794      */
795
796     function hasRight($right)
797     {
798         $result = false;
799         if ($this->hasRole(Profile_role::DELETED)) {
800             return false;
801         }
802         if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
803             switch ($right)
804             {
805             case Right::DELETEOTHERSNOTICE:
806             case Right::MAKEGROUPADMIN:
807             case Right::SANDBOXUSER:
808             case Right::SILENCEUSER:
809             case Right::DELETEUSER:
810                 $result = $this->hasRole(Profile_role::MODERATOR);
811                 break;
812             case Right::CONFIGURESITE:
813                 $result = $this->hasRole(Profile_role::ADMINISTRATOR);
814                 break;
815             case Right::GRANTROLE:
816             case Right::REVOKEROLE:
817                 $result = $this->hasRole(Profile_role::OWNER);
818                 break;
819             case Right::NEWNOTICE:
820             case Right::NEWMESSAGE:
821             case Right::SUBSCRIBE:
822                 $result = !$this->isSilenced();
823                 break;
824             case Right::PUBLICNOTICE:
825             case Right::EMAILONREPLY:
826             case Right::EMAILONSUBSCRIBE:
827             case Right::EMAILONFAVE:
828                 $result = !$this->isSandboxed();
829                 break;
830             default:
831                 $result = false;
832                 break;
833             }
834         }
835         return $result;
836     }
837
838     function hasRepeated($notice_id)
839     {
840         // XXX: not really a pkey, but should work
841
842         $notice = Memcached_DataObject::pkeyGet('Notice',
843                                                 array('profile_id' => $this->id,
844                                                       'repeat_of' => $notice_id));
845
846         return !empty($notice);
847     }
848
849     /**
850      * Returns an XML string fragment with limited profile information
851      * as an Atom <author> element.
852      *
853      * Assumes that Atom has been previously set up as the base namespace.
854      *
855      * @param Profile $cur the current authenticated user
856      *
857      * @return string
858      */
859     function asAtomAuthor($cur = null)
860     {
861         $xs = new XMLStringer(true);
862
863         $xs->elementStart('author');
864         $xs->element('name', null, $this->nickname);
865         $xs->element('uri', null, $this->getUri());
866         if ($cur != null) {
867             $attrs = Array();
868             $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
869             $attrs['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
870             $xs->element('statusnet:profile_info', $attrs, null);
871         }
872         $xs->elementEnd('author');
873
874         return $xs->getString();
875     }
876
877     /**
878      * Returns an XML string fragment with profile information as an
879      * Activity Streams <activity:actor> element.
880      *
881      * Assumes that 'activity' namespace has been previously defined.
882      *
883      * @return string
884      */
885     function asActivityActor()
886     {
887         return $this->asActivityNoun('actor');
888     }
889
890     /**
891      * Returns an XML string fragment with profile information as an
892      * Activity Streams noun object with the given element type.
893      *
894      * Assumes that 'activity', 'georss', and 'poco' namespace has been
895      * previously defined.
896      *
897      * @param string $element one of 'actor', 'subject', 'object', 'target'
898      *
899      * @return string
900      */
901     function asActivityNoun($element)
902     {
903         $noun = ActivityObject::fromProfile($this);
904         return $noun->asString('activity:' . $element);
905     }
906
907     /**
908      * Returns the best URI for a profile. Plugins may override.
909      *
910      * @return string $uri
911      */
912     function getUri()
913     {
914         $uri = null;
915
916         // give plugins a chance to set the URI
917         if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
918
919             // check for a local user first
920             $user = User::staticGet('id', $this->id);
921
922             if (!empty($user)) {
923                 $uri = $user->uri;
924             } else {
925                 // return OMB profile if any
926                 $remote = Remote_profile::staticGet('id', $this->id);
927                 if (!empty($remote)) {
928                     $uri = $remote->uri;
929                 }
930             }
931             Event::handle('EndGetProfileUri', array($this, &$uri));
932         }
933
934         return $uri;
935     }
936
937     function hasBlocked($other)
938     {
939         $block = Profile_block::get($this->id, $other->id);
940
941         if (empty($block)) {
942             $result = false;
943         } else {
944             $result = true;
945         }
946
947         return $result;
948     }
949
950     function getAtomFeed()
951     {
952         $feed = null;
953
954         if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
955             $user = User::staticGet('id', $this->id);
956             if (!empty($user)) {
957                 $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
958                                                                   'format' => 'atom'));
959             }
960             Event::handle('EndProfileGetAtomFeed', array($this, $feed));
961         }
962
963         return $feed;
964     }
965 }