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