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