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