]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - classes/Profile.php
Misses this file to merge. I like the comments.
[quix0rs-gnu-social.git] / classes / Profile.php
index 8a5be5f358b58916662310e25e341cb702d8e183..db8326f0f6959dbd090e39dda6ef7bfe1f77e60e 100644 (file)
@@ -22,8 +22,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 /**
  * Table Definition for profile
  */
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
-
 class Profile extends Managed_DataObject
 {
     ###START_AUTOCODE
@@ -32,11 +30,11 @@ class Profile extends Managed_DataObject
     public $__table = 'profile';                         // table name
     public $id;                              // int(4)  primary_key not_null
     public $nickname;                        // varchar(64)  multiple_key not_null
-    public $fullname;                        // varchar(255)  multiple_key
-    public $profileurl;                      // varchar(255)
-    public $homepage;                        // varchar(255)  multiple_key
+    public $fullname;                        // varchar(191)  multiple_key   not 255 because utf8mb4 takes more space
+    public $profileurl;                      // varchar(191)                 not 255 because utf8mb4 takes more space
+    public $homepage;                        // varchar(191)  multiple_key   not 255 because utf8mb4 takes more space
     public $bio;                             // text()  multiple_key
-    public $location;                        // varchar(255)  multiple_key
+    public $location;                        // varchar(191)  multiple_key   not 255 because utf8mb4 takes more space
     public $lat;                             // decimal(10,7)
     public $lon;                             // decimal(10,7)
     public $location_id;                     // int(4)
@@ -44,11 +42,6 @@ class Profile extends Managed_DataObject
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 
-    /* Static get */
-    function staticGet($k,$v=NULL) {
-        return Memcached_DataObject::staticGet('Profile',$k,$v);
-    }
-
     public static function schemaDef()
     {
         $def = array(
@@ -56,11 +49,11 @@ class Profile extends Managed_DataObject
             'fields' => array(
                 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
                 'nickname' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username', 'collate' => 'utf8_general_ci'),
-                'fullname' => array('type' => 'varchar', 'length' => 255, 'description' => 'display name', 'collate' => 'utf8_general_ci'),
-                'profileurl' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL, cached so we dont regenerate'),
-                'homepage' => array('type' => 'varchar', 'length' => 255, 'description' => 'identifying URL', 'collate' => 'utf8_general_ci'),
+                'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name', 'collate' => 'utf8_general_ci'),
+                'profileurl' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'),
+                'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'identifying URL', 'collate' => 'utf8_general_ci'),
                 'bio' => array('type' => 'text', 'description' => 'descriptive biography', 'collate' => 'utf8_general_ci'),
-                'location' => array('type' => 'varchar', 'length' => 255, 'description' => 'physical location', 'collate' => 'utf8_general_ci'),
+                'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'physical location', 'collate' => 'utf8_general_ci'),
                 'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'),
                 'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'),
                 'location_id' => array('type' => 'int', 'description' => 'location id if possible'),
@@ -83,96 +76,91 @@ class Profile extends Managed_DataObject
 
         return $def;
     }
-
-       function multiGet($keyCol, $keyVals, $skipNulls=true)
-       {
-           return parent::multiGet('Profile', $keyCol, $keyVals, $skipNulls);
-       }
        
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    protected $_user = -1;  // Uninitialized value distinct from null
-
-    function getUser()
+    public static function getByEmail($email)
     {
-        if (is_int($this->_user) && $this->_user == -1) {
-            $this->_user = User::staticGet('id', $this->id);
+        // in the future, profiles should have emails stored...
+        $user = User::getKV('email', $email);
+        if (!($user instanceof User)) {
+            throw new NoSuchUserException(array('email'=>$email));
         }
+        return $user->getProfile();
+    } 
 
-        return $this->_user;
-    }
-
-    protected $_avatars;
+    protected $_user = array();
 
-    function getAvatar($width, $height=null)
+    public function getUser()
     {
-        if (is_null($height)) {
-            $height = $width;
+        if (!isset($this->_user[$this->id])) {
+            $user = User::getKV('id', $this->id);
+            if (!$user instanceof User) {
+                throw new NoSuchUserException(array('id'=>$this->id));
+            }
+            $this->_user[$this->id] = $user;
         }
+        return $this->_user[$this->id];
+    }
 
-        $avatar = $this->_getAvatar($width);
-
-        if (empty($avatar)) {
+    protected $_group = array();
 
-            if (Event::handle('StartProfileGetAvatar', array($this, $width, &$avatar))) {
-                $avatar = Avatar::pkeyGet(
-                    array(
-                        'profile_id' => $this->id,
-                        'width'      => $width,
-                        'height'     => $height
-                    )
-                );
-                Event::handle('EndProfileGetAvatar', array($this, $width, &$avatar));
+    public function getGroup()
+    {
+        if (!isset($this->_group[$this->id])) {
+            $group = User_group::getKV('profile_id', $this->id);
+            if (!$group instanceof User_group) {
+                throw new NoSuchGroupException(array('profile_id'=>$this->id));
             }
-
-            $this->_fillAvatar($width, $avatar);
+            $this->_group[$this->id] = $group;
         }
-
-        return $avatar;
+        return $this->_group[$this->id];
     }
 
-    // XXX: @Fix me gargargar
-    function _getAvatar($width)
+    public function isGroup()
     {
-        if (empty($this->_avatars)) {
-            $this->_avatars = array();
-        }
-
-        // GAR! I cannot figure out where _avatars gets pre-filled with the avatar from
-        // the previously used profile! Please shoot me now! --Zach
-        if (array_key_exists($width, $this->_avatars)) {
-            // Don't return cached avatar unless it's really for this profile
-            if ($this->_avatars[$width]->profile_id == $this->id) {
-                return $this->_avatars[$width];
-            }
+        try {
+            $this->getGroup();
+            return true;
+        } catch (NoSuchGroupException $e) {
+            return false;
         }
-
-        return null;
     }
 
-    function _fillAvatar($width, $avatar)
+    public function isLocal()
     {
-      //common_debug("Storing avatar of width: {$avatar->width} and profile_id {$avatar->profile_id} in profile {$this->id}.");
-        $this->_avatars[$width] = $avatar;
-
+        try {
+            $this->getUser();
+        } catch (NoSuchUserException $e) {
+            return false;
+        }
+        return true;
     }
 
-    function getOriginalAvatar()
+    public function getObjectType()
     {
-        $avatar = DB_DataObject::factory('avatar');
-        $avatar->profile_id = $this->id;
-        $avatar->original = true;
-        if ($avatar->find(true)) {
-            return $avatar;
+        // FIXME: More types... like peopletags and whatever
+        if ($this->isGroup()) {
+            return ActivityObject::GROUP;
         } else {
-            return null;
+            return ActivityObject::PERSON;
         }
     }
 
-    function setOriginal($filename)
+    public function getAvatar($width, $height=null)
     {
-        $imagefile = new ImageFile($this->id, Avatar::path($filename));
+        return Avatar::byProfile($this, $width, $height);
+    }
+
+    public function setOriginal($filename)
+    {
+        if ($this->isGroup()) {
+            // Until Group avatars are handled just like profile avatars.
+            return $this->getGroup()->setOriginal($filename);
+        }
+
+        $imagefile = new ImageFile(null, Avatar::path($filename));
 
         $avatar = new Avatar();
         $avatar->profile_id = $this->id;
@@ -182,72 +170,40 @@ class Profile extends Managed_DataObject
         $avatar->filename = $filename;
         $avatar->original = true;
         $avatar->url = Avatar::url($filename);
-        $avatar->created = DB_DataObject_Cast::dateTime(); # current time
+        $avatar->created = common_sql_now();
 
         // XXX: start a transaction here
-
-        if (!$this->delete_avatars() || !$avatar->insert()) {
+        if (!Avatar::deleteFromProfile($this, true) || !$avatar->insert()) {
+            // If we can't delete the old avatars, let's abort right here.
             @unlink(Avatar::path($filename));
             return null;
         }
 
-        foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
-            // We don't do a scaled one if original is our scaled size
-            if (!($avatar->width == $size && $avatar->height == $size)) {
-                $scaled_filename = $imagefile->resize($size);
-
-                //$scaled = DB_DataObject::factory('avatar');
-                $scaled = new Avatar();
-                $scaled->profile_id = $this->id;
-                $scaled->width = $size;
-                $scaled->height = $size;
-                $scaled->original = false;
-                $scaled->mediatype = image_type_to_mime_type($imagefile->type);
-                $scaled->filename = $scaled_filename;
-                $scaled->url = Avatar::url($scaled_filename);
-                $scaled->created = DB_DataObject_Cast::dateTime(); # current time
-
-                if (!$scaled->insert()) {
-                    return null;
-                }
-            }
-        }
-
         return $avatar;
     }
 
     /**
-     * Delete attached avatars for this user from the database and filesystem.
-     * This should be used instead of a batch delete() to ensure that files
-     * get removed correctly.
+     * Gets either the full name (if filled) or the nickname.
      *
-     * @param boolean $original true to delete only the original-size file
-     * @return <type>
+     * @return string
      */
-    function delete_avatars($original=true)
+    function getBestName()
     {
-        $avatar = new Avatar();
-        $avatar->profile_id = $this->id;
-        $avatar->find();
-        while ($avatar->fetch()) {
-            if ($avatar->original) {
-                if ($original == false) {
-                    continue;
-                }
-            }
-            $avatar->delete();
-        }
-        return true;
+        return ($this->fullname) ? $this->fullname : $this->nickname;
     }
 
     /**
-     * Gets either the full name (if filled) or the nickname.
-     *
-     * @return string
+     * Takes the currently scoped profile into account to give a name 
+     * to list in notice streams. Preferences may differ between profiles.
      */
-    function getBestName()
+    function getStreamName()
     {
-        return ($this->fullname) ? $this->fullname : $this->nickname;
+        $user = common_current_user();
+        if ($user instanceof User && $user->streamNicknames()) {
+            return $this->nickname;
+        }
+
+        return $this->getBestName();
     }
 
     /**
@@ -281,9 +237,9 @@ class Profile extends Managed_DataObject
                 return $notice->_items[0];
             }
             return $notice;
-        } else {
-            return null;
         }
+        
+        return null;
     }
 
     function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
@@ -293,26 +249,25 @@ class Profile extends Managed_DataObject
         return $stream->getNotices($offset, $limit, $since_id, $max_id);
     }
 
-    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
+    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, Profile $scoped=null)
     {
-        $stream = new ProfileNoticeStream($this);
+        $stream = new ProfileNoticeStream($this, $scoped);
 
         return $stream->getNotices($offset, $limit, $since_id, $max_id);
     }
 
-    function isMember($group)
+    function isMember(User_group $group)
     {
        $groups = $this->getGroups(0, null);
-       $gs = $groups->fetchAll();
-       foreach ($gs as $g) {
-           if ($group->id == $g->id) {
+        while ($groups instanceof User_group && $groups->fetch()) {
+           if ($groups->id == $group->id) {
                return true;
            }
        }
        return false;
     }
 
-    function isAdmin($group)
+    function isAdmin(User_group $group)
     {
         $gm = Group_member::pkeyGet(array('profile_id' => $this->id,
                                           'group_id' => $group->id));
@@ -354,7 +309,18 @@ class Profile extends Managed_DataObject
             $ids = array_slice($ids, $offset, $limit);
         }
 
-        return User_group::multiGet('id', $ids);
+        try {
+            return User_group::multiGet('id', $ids);
+        } catch (NoResultException $e) {
+            return null;    // throw exception when we handle it everywhere
+        }
+    }
+
+    function getGroupCount() {
+        $groups = $this->getGroups(0, null);
+        return $groups instanceof User_group
+                ? $groups->N
+                : 0;
     }
 
     function isTagged($peopletag)
@@ -445,7 +411,7 @@ class Profile extends Managed_DataObject
         $lists = array();
 
         foreach ($ids as $id) {
-            $list = Profile_list::staticGet('id', $id);
+            $list = Profile_list::getKV('id', $id);
             if (!empty($list) &&
                 ($showPrivate || !$list->private)) {
 
@@ -554,7 +520,7 @@ class Profile extends Managed_DataObject
         $lists = new Profile_list();
         $subs = new Profile_tag_subscription();
 
-        $lists->joinAdd('id', 'profile_tag_subscription:profile_tag_id');
+        $lists->joinAdd(array('id', 'profile_tag_subscription:profile_tag_id'));
 
         #@fixme: postgres (round(date_part('epoch', my_date)))
         $lists->selectAdd('unix_timestamp(profile_tag_subscription.created) as "cursor"');
@@ -625,73 +591,75 @@ class Profile extends Managed_DataObject
 
     function avatarUrl($size=AVATAR_PROFILE_SIZE)
     {
-        $avatar = $this->getAvatar($size);
-        if ($avatar) {
-            return $avatar->displayUrl();
-        } else {
-            return Avatar::defaultImage($size);
-        }
+        return Avatar::urlByProfile($this, $size);
     }
 
-    function getSubscriptions($offset=0, $limit=null)
+    function getSubscribed($offset=0, $limit=null)
     {
-        $subs = Subscription::bySubscriber($this->id,
-                                           $offset,
-                                           $limit);
-
-        $profiles = array();
-
-        while ($subs->fetch()) {
-            $profile = Profile::staticGet($subs->subscribed);
-            if ($profile) {
-                $profiles[] = $profile;
-            }
+        $subs = Subscription::getSubscribedIDs($this->id, $offset, $limit);
+        try {
+            $profiles = Profile::multiGet('id', $subs);
+        } catch (NoResultException $e) {
+            return $e->obj;
         }
-
-        return new ArrayWrapper($profiles);
+        return $profiles;
     }
 
     function getSubscribers($offset=0, $limit=null)
     {
-        $subs = Subscription::bySubscribed($this->id,
-                                           $offset,
-                                           $limit);
+        $subs = Subscription::getSubscriberIDs($this->id, $offset, $limit);
+        try {
+            $profiles = Profile::multiGet('id', $subs);
+        } catch (NoResultException $e) {
+            return $e->obj;
+        }
+        return $profiles;
+    }
 
-        $profiles = array();
+    function getTaggedSubscribers($tag, $offset=0, $limit=null)
+    {
+        $qry =
+          'SELECT profile.* ' .
+          'FROM profile JOIN subscription ' .
+          'ON profile.id = subscription.subscriber ' .
+          'JOIN profile_tag ON (profile_tag.tagged = subscription.subscriber ' .
+          'AND profile_tag.tagger = subscription.subscribed) ' .
+          'WHERE subscription.subscribed = %d ' .
+          "AND profile_tag.tag = '%s' " .
+          'AND subscription.subscribed != subscription.subscriber ' .
+          'ORDER BY subscription.created DESC ';
 
-        while ($subs->fetch()) {
-            $profile = Profile::staticGet($subs->subscriber);
-            if ($profile) {
-                $profiles[] = $profile;
-            }
+        if ($offset) {
+            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
         }
 
-        return new ArrayWrapper($profiles);
+        $profile = new Profile();
+
+        $cnt = $profile->query(sprintf($qry, $this->id, $profile->escape($tag)));
+
+        return $profile;
     }
 
-    function getTaggedSubscribers($tag)
+    function getTaggedSubscriptions($tag, $offset=0, $limit=null)
     {
         $qry =
           'SELECT profile.* ' .
-          'FROM profile JOIN (subscription, profile_tag, profile_list) ' .
-          'ON profile.id = subscription.subscriber ' .
-          'AND profile.id = profile_tag.tagged ' .
-          'AND profile_tag.tagger = profile_list.tagger AND profile_tag.tag = profile_list.tag ' .
-          'WHERE subscription.subscribed = %d ' .
+          'FROM profile JOIN subscription ' .
+          'ON profile.id = subscription.subscribed ' .
+          'JOIN profile_tag on (profile_tag.tagged = subscription.subscribed ' .
+          'AND profile_tag.tagger = subscription.subscriber) ' .
+          'WHERE subscription.subscriber = %d ' .
+          "AND profile_tag.tag = '%s' " .
           'AND subscription.subscribed != subscription.subscriber ' .
-          'AND profile_tag.tagger = %d AND profile_tag.tag = "%s" ' .
-          'AND profile_list.private = false ' .
-          'ORDER BY subscription.created DESC';
+          'ORDER BY subscription.created DESC ';
+
+        $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
 
         $profile = new Profile();
-        $tagged = array();
 
-        $cnt = $profile->query(sprintf($qry, $this->id, $this->id, $profile->escape($tag)));
+        $profile->query(sprintf($qry, $this->id, $profile->escape($tag)));
 
-        while ($profile->fetch()) {
-            $tagged[] = clone($profile);
-        }
-        return $tagged;
+        return $profile;
     }
 
     /**
@@ -777,7 +745,7 @@ class Profile extends Managed_DataObject
      * @param Profile $other
      * @return boolean
      */
-    function isSubscribed($other)
+    function isSubscribed(Profile $other)
     {
         return Subscription::exists($this, $other);
     }
@@ -788,7 +756,7 @@ class Profile extends Managed_DataObject
      * @param Profile $other
      * @return boolean
      */
-    function hasPendingSubscription($other)
+    function hasPendingSubscription(Profile $other)
     {
         return Subscription_queue::exists($this, $other);
     }
@@ -799,40 +767,12 @@ class Profile extends Managed_DataObject
      * @param Profile $other
      * @return boolean
      */
-    function mutuallySubscribed($other)
+    function mutuallySubscribed(Profile $other)
     {
         return $this->isSubscribed($other) &&
           $other->isSubscribed($this);
     }
 
-    function hasFave($notice)
-    {
-        $fave = Fave::pkeyGet(array('user_id' => $this->id,
-                                    'notice_id' => $notice->id));
-        return ((is_null($fave)) ? false : true);
-    }
-
-    function faveCount()
-    {
-        $c = Cache::instance();
-        if (!empty($c)) {
-            $cnt = $c->get(Cache::key('profile:fave_count:'.$this->id));
-            if (is_integer($cnt)) {
-                return (int) $cnt;
-            }
-        }
-
-        $faves = new Fave();
-        $faves->user_id = $this->id;
-        $cnt = (int) $faves->count('notice_id');
-
-        if (!empty($c)) {
-            $c->set(Cache::key('profile:fave_count:'.$this->id), $cnt);
-        }
-
-        return $cnt;
-    }
-
     function noticeCount()
     {
         $c = Cache::instance();
@@ -855,20 +795,6 @@ class Profile extends Managed_DataObject
         return $cnt;
     }
 
-    function blowFavesCache()
-    {
-        $cache = Cache::instance();
-        if ($cache) {
-            // Faves don't happen chronologically, so we need to blow
-            // ;last cache, too
-            $cache->delete(Cache::key('fave:ids_by_user:'.$this->id));
-            $cache->delete(Cache::key('fave:ids_by_user:'.$this->id.';last'));
-            $cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id));
-            $cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id.';last'));
-        }
-        $this->blowFaveCount();
-    }
-
     function blowSubscriberCount()
     {
         $c = Cache::instance();
@@ -885,14 +811,6 @@ class Profile extends Managed_DataObject
         }
     }
 
-    function blowFaveCount()
-    {
-        $c = Cache::instance();
-        if (!empty($c)) {
-            $c->delete(Cache::key('profile:fave_count:'.$this->id));
-        }
-    }
-
     function blowNoticeCount()
     {
         $c = Cache::instance();
@@ -917,14 +835,37 @@ class Profile extends Managed_DataObject
         return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
     }
 
-    function delete()
+    function update($dataObject=false)
+    {
+        if (is_object($dataObject) && $this->nickname != $dataObject->nickname) {
+            try {
+                $local = $this->getUser();
+                common_debug("Updating User ({$this->id}) nickname from {$dataObject->nickname} to {$this->nickname}");
+                $origuser = clone($local);
+                $local->nickname = $this->nickname;
+                // updateWithKeys throws exception on failure.
+                $local->updateWithKeys($origuser);
+
+                // Clear the site owner, in case nickname changed
+                if ($local->hasRole(Profile_role::OWNER)) {
+                    User::blow('user:site_owner');
+                }
+            } catch (NoSuchUserException $e) {
+                // Nevermind...
+            }
+        }
+
+        return parent::update($dataObject);
+    }
+
+    function delete($useWhere=false)
     {
         $this->_deleteNotices();
         $this->_deleteSubscriptions();
-        $this->_deleteMessages();
         $this->_deleteTags();
         $this->_deleteBlocks();
-        $this->delete_avatars();
+        $this->_deleteAttentions();
+        Avatar::deleteFromProfile($this, true);
 
         // Warning: delete() will run on the batch objects,
         // not on individual objects.
@@ -939,7 +880,7 @@ class Profile extends Managed_DataObject
             $inst->delete();
         }
 
-        parent::delete();
+        return parent::delete($useWhere);
     }
 
     function _deleteNotices()
@@ -963,7 +904,7 @@ class Profile extends Managed_DataObject
         $sub->find();
 
         while ($sub->fetch()) {
-            $other = Profile::staticGet('id', $sub->subscribed);
+            $other = Profile::getKV('id', $sub->subscribed);
             if (empty($other)) {
                 continue;
             }
@@ -978,7 +919,7 @@ class Profile extends Managed_DataObject
         $subd->find();
 
         while ($subd->fetch()) {
-            $other = Profile::staticGet('id', $subd->subscriber);
+            $other = Profile::getKV('id', $subd->subscriber);
             if (empty($other)) {
                 continue;
             }
@@ -996,17 +937,6 @@ class Profile extends Managed_DataObject
         $self->delete();
     }
 
-    function _deleteMessages()
-    {
-        $msg = new Message();
-        $msg->from_profile = $this->id;
-        $msg->delete();
-
-        $msg = new Message();
-        $msg->to_profile = $this->id;
-        $msg->delete();
-    }
-
     function _deleteTags()
     {
         $tag = new Profile_tag();
@@ -1025,9 +955,23 @@ class Profile extends Managed_DataObject
         $block->delete();
     }
 
+    function _deleteAttentions()
+    {
+        $att = new Attention();
+        $att->profile_id = $this->getID();
+
+        if ($att->find()) {
+            while ($att->fetch()) {
+                // Can't do delete() on the object directly since it won't remove all of it
+                $other = clone($att);
+                $other->delete();
+            }
+        }
+    }
+
     // XXX: identical to Notice::getLocation.
 
-    function getLocation()
+    public function getLocation()
     {
         $location = null;
 
@@ -1050,6 +994,29 @@ class Profile extends Managed_DataObject
         return $location;
     }
 
+    public function shareLocation()
+    {
+        $cfg = common_config('location', 'share');
+
+        if ($cfg == 'always') {
+            return true;
+        } else if ($cfg == 'never') {
+            return false;
+        } else { // user
+            $share = common_config('location', 'sharedefault');
+
+            // Check if user has a personal setting for this
+            $prefs = User_location_prefs::getKV('user_id', $this->id);
+
+            if (!empty($prefs)) {
+                $share = $prefs->share_location;
+                $prefs->free();
+            }
+
+            return $share;
+        }
+    }
+
     function hasRole($name)
     {
         $has_role = false;
@@ -1177,7 +1144,7 @@ class Profile extends Managed_DataObject
      * @param $right string Name of the right, usually a constant in class Right
      * @return boolean whether the user has the right in question
      */
-    function hasRight($right)
+    public function hasRight($right)
     {
         $result = false;
 
@@ -1243,13 +1210,13 @@ class Profile extends Managed_DataObject
         return $result;
     }
 
-    function hasRepeated($notice_id)
+    // FIXME: Can't put Notice typing here due to ArrayWrapper
+    public function hasRepeated($notice)
     {
         // XXX: not really a pkey, but should work
 
-        $notice = Memcached_DataObject::pkeyGet('Notice',
-                                                array('profile_id' => $this->id,
-                                                      'repeat_of' => $notice_id));
+        $notice = Notice::pkeyGet(array('profile_id' => $this->id,
+                                        'repeat_of' => $notice->id));
 
         return !empty($notice);
     }
@@ -1288,20 +1255,20 @@ class Profile extends Managed_DataObject
      * Clients use some extra profile info in the atom stream.
      * This gives it to them.
      *
-     * @param User $cur Current user
+     * @param Profile $scoped The currently logged in/scoped profile
      *
      * @return array representation of <statusnet:profile_info> element or null
      */
 
-    function profileInfo($cur)
+    function profileInfo(Profile $scoped=null)
     {
         $profileInfoAttr = array('local_id' => $this->id);
 
-        if ($cur != null) {
+        if ($scoped instanceof Profile) {
             // Whether the current user is a subscribed to this profile
-            $profileInfoAttr['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
+            $profileInfoAttr['following'] = $scoped->isSubscribed($this) ? 'true' : 'false';
             // Whether the current user is has blocked this profile
-            $profileInfoAttr['blocking']  = $cur->hasBlocked($this) ? 'true' : 'false';
+            $profileInfoAttr['blocking']  = $scoped->hasBlocked($this) ? 'true' : 'false';
         }
 
         return array('statusnet:profile_info', $profileInfoAttr, null);
@@ -1333,16 +1300,97 @@ class Profile extends Managed_DataObject
      */
     function asActivityNoun($element)
     {
-        $noun = ActivityObject::fromProfile($this);
+        $noun = $this->asActivityObject();
         return $noun->asString('activity:' . $element);
     }
 
+    public function asActivityObject()
+    {
+        $object = new ActivityObject();
+
+        if (Event::handle('StartActivityObjectFromProfile', array($this, &$object))) {
+            $object->type   = $this->getObjectType();
+            $object->id     = $this->getUri();
+            $object->title  = $this->getBestName();
+            $object->link   = $this->getUrl();
+            $object->summary = $this->getDescription();
+
+            try {
+                $avatar = Avatar::getUploaded($this);
+                $object->avatarLinks[] = AvatarLink::fromAvatar($avatar);
+            } catch (NoAvatarException $e) {
+                // Could not find an original avatar to link
+            }
+
+            $sizes = array(
+                AVATAR_PROFILE_SIZE,
+                AVATAR_STREAM_SIZE,
+                AVATAR_MINI_SIZE
+            );
+
+            foreach ($sizes as $size) {
+                $alink  = null;
+                try {
+                    $avatar = Avatar::byProfile($this, $size);
+                    $alink = AvatarLink::fromAvatar($avatar);
+                } catch (NoAvatarException $e) {
+                    $alink = new AvatarLink();
+                    $alink->type   = 'image/png';
+                    $alink->height = $size;
+                    $alink->width  = $size;
+                    $alink->url    = Avatar::defaultImage($size);
+                }
+
+                $object->avatarLinks[] = $alink;
+            }
+
+            if (isset($this->lat) && isset($this->lon)) {
+                $object->geopoint = (float)$this->lat
+                    . ' ' . (float)$this->lon;
+            }
+
+            $object->poco = PoCo::fromProfile($this);
+
+            if ($this->isLocal()) {
+                $object->extra[] = array('followers', array('url' => common_local_url('subscribers', array('nickname' => $this->getNickname()))));
+            }
+
+            Event::handle('EndActivityObjectFromProfile', array($this, &$object));
+        }
+
+        return $object;
+    }
+
+    /**
+     * Returns the profile's canonical url, not necessarily a uri/unique id
+     *
+     * @return string $profileurl
+     */
+    public function getUrl()
+    {
+        if (empty($this->profileurl) ||
+                !filter_var($this->profileurl, FILTER_VALIDATE_URL)) {
+            throw new InvalidUrlException($this->profileurl);
+        }
+        return $this->profileurl;
+    }
+
+    public function getNickname()
+    {
+        return $this->nickname;
+    }
+
+    public function getDescription()
+    {
+        return $this->bio;
+    }
+
     /**
      * Returns the best URI for a profile. Plugins may override.
      *
      * @return string $uri
      */
-    function getUri()
+    public function getUri()
     {
         $uri = null;
 
@@ -1350,10 +1398,9 @@ class Profile extends Managed_DataObject
         if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
 
             // check for a local user first
-            $user = User::staticGet('id', $this->id);
-
-            if (!empty($user)) {
-                $uri = $user->uri;
+            $user = User::getKV('id', $this->id);
+            if ($user instanceof User) {
+                $uri = $user->getUri();
             }
 
             Event::handle('EndGetProfileUri', array($this, &$uri));
@@ -1362,17 +1409,30 @@ class Profile extends Managed_DataObject
         return $uri;
     }
 
-    function hasBlocked($other)
+    /**
+     * Returns an assumed acct: URI for a profile. Plugins are required.
+     *
+     * @return string $uri
+     */
+    public function getAcctUri()
     {
-        $block = Profile_block::get($this->id, $other->id);
+        $acct = null;
 
-        if (empty($block)) {
-            $result = false;
-        } else {
-            $result = true;
+        if (Event::handle('StartGetProfileAcctUri', array($this, &$acct))) {
+            Event::handle('EndGetProfileAcctUri', array($this, &$acct));
         }
 
-        return $result;
+        if ($acct === null) {
+            throw new ProfileNoAcctUriException($this);
+        }
+
+        return $acct;
+    }
+
+    function hasBlocked($other)
+    {
+        $block = Profile_block::exists($this, $other);
+        return !empty($block);
     }
 
     function getAtomFeed()
@@ -1380,7 +1440,7 @@ class Profile extends Managed_DataObject
         $feed = null;
 
         if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
-            $user = User::staticGet('id', $this->id);
+            $user = User::getKV('id', $this->id);
             if (!empty($user)) {
                 $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
                                                                   'format' => 'atom'));
@@ -1391,19 +1451,35 @@ class Profile extends Managed_DataObject
         return $feed;
     }
 
-    static function fromURI($uri)
+    public function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null)
+    {
+        // TRANS: Exception thrown when trying view "repeated to me".
+        throw new Exception(_('Not implemented since inbox change.'));
+    }
+
+    /*
+     * Get a Profile object by URI. Will call external plugins for help
+     * using the event StartGetProfileFromURI.
+     *
+     * @param string $uri A unique identifier for a resource (profile/group/whatever)
+     */
+    static function fromUri($uri)
     {
         $profile = null;
 
         if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
-            // Get a local user or remote (OMB 0.1) profile
-            $user = User::staticGet('uri', $uri);
-            if (!empty($user)) {
+            // Get a local user when plugin lookup (like OStatus) fails
+            $user = User::getKV('uri', $uri);
+            if ($user instanceof User) {
                 $profile = $user->getProfile();
             }
             Event::handle('EndGetProfileFromURI', array($uri, $profile));
         }
 
+        if (!$profile instanceof Profile) {
+            throw new UnknownUriException($uri);
+        }
+
         return $profile;
     }
 
@@ -1471,37 +1547,60 @@ class Profile extends Managed_DataObject
     function __sleep()
     {
         $vars = parent::__sleep();
-        $skip = array('_user', '_avatars');
+        $skip = array('_user', '_group');
         return array_diff($vars, $skip);
     }
-    
-    static function fillAvatars(&$profiles, $width)
+
+    public function getProfile()
     {
-       $ids = array();
-       foreach ($profiles as $profile) {
-            if (!empty($profile)) {
-                $ids[] = $profile->id;
-            }
-       }
-       
-       $avatars = Avatar::pivotGet('profile_id', $ids, array('width' => $width,
-                                                                                                                         'height' => $width));
-       
-       foreach ($profiles as $profile) {
-            if (!empty($profile)) { // ???
-                $profile->_fillAvatar($width, $avatars[$profile->id]);
-            }
-       }
+        return $this;
     }
-    
-    // Can't seem to find how to fix this.
 
-    function getProfile()
+    /**
+     * This will perform shortenLinks with the connected User object.
+     *
+     * Won't work on remote profiles or groups, so expect a
+     * NoSuchUserException if you don't know it's a local User.
+     *
+     * @param string $text      String to shorten
+     * @param boolean $always   Disrespect minimum length etc.
+     *
+     * @return string link-shortened $text
+     */
+    public function shortenLinks($text, $always=false)
     {
-        return $this;
+        return $this->getUser()->shortenLinks($text, $always);
+    }
+
+    public function isPrivateStream()
+    {
+        // We only know of public remote users as of yet...
+        if (!$this->isLocal()) {
+            return false;
+        }
+        return $this->getUser()->private_stream ? true : false;
+    }
+
+    public function delPref($namespace, $topic) {
+        return Profile_prefs::setData($this, $namespace, $topic, null);
+    }
+
+    public function getPref($namespace, $topic, $default=null) {
+        // If you want an exception to be thrown, call Profile_prefs::getData directly
+        try {
+            return Profile_prefs::getData($this, $namespace, $topic, $default);
+        } catch (NoResultException $e) {
+            return null;
+        }
+    }
+
+    // The same as getPref but will fall back to common_config value for the same namespace/topic
+    public function getConfigPref($namespace, $topic)
+    {
+        return Profile_prefs::getConfigData($this, $namespace, $topic);
     }
 
-    static function pivotGet($key, $values, $otherCols=array()) {
-        return Memcached_DataObject::pivotGet('Profile', $key, $values, $otherCols);
+    public function setPref($namespace, $topic, $data) {
+        return Profile_prefs::setData($this, $namespace, $topic, $data);
     }
 }