]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - plugins/OStatus/classes/Ostatus_profile.php
OStatus: handle update-profile Salmon pings
[quix0rs-gnu-social.git] / plugins / OStatus / classes / Ostatus_profile.php
index 82dbf773dd513c8c093c03a42908cf82c1fca533..33b0ceb9efb99b6877bae1f99e05e867abf7e7e4 100644 (file)
@@ -33,6 +33,7 @@ class Ostatus_profile extends Memcached_DataObject
 
     public $feeduri;
     public $salmonuri;
+    public $avatar; // remote URL of the last avatar we saved
 
     public $created;
     public $modified;
@@ -58,6 +59,7 @@ class Ostatus_profile extends Memcached_DataObject
                      'group_id' => DB_DATAOBJECT_INT,
                      'feeduri' => DB_DATAOBJECT_STR,
                      'salmonuri' =>  DB_DATAOBJECT_STR,
+                     'avatar' =>  DB_DATAOBJECT_STR,
                      'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
                      'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
     }
@@ -74,6 +76,8 @@ class Ostatus_profile extends Memcached_DataObject
                                    255, true, 'UNI'),
                      new ColumnDef('salmonuri', 'text',
                                    null, true),
+                     new ColumnDef('avatar', 'text',
+                                   null, true),
                      new ColumnDef('created', 'datetime',
                                    null, false),
                      new ColumnDef('modified', 'datetime',
@@ -295,18 +299,9 @@ class Ostatus_profile extends Memcached_DataObject
             throw new ServerException("Remote groups can't subscribe to local users");
         }
 
-        // @fixme use regular channels for subbing, once they accept remote profiles
-        $sub = new Subscription();
-        $sub->subscriber = $this->profile_id;
-        $sub->subscribed = $user->id;
-        $sub->created = common_sql_now(); // current time
+        Subscription::start($this->localProfile(), $user->getProfile());
 
-        if ($sub->insert()) {
-            // @fixme use subs_notify() if refactored to take profiles?
-            mail_subscribe_notify_profile($user, $this->localProfile());
-            return true;
-        }
-        return false;
+        return true;
     }
 
     /**
@@ -427,21 +422,57 @@ class Ostatus_profile extends Memcached_DataObject
         return false;
     }
 
-    public function notifyActivity($activity)
+    /**
+     * Send a Salmon notification ping immediately, and confirm that we got
+     * an acceptable response from the remote site.
+     *
+     * @param mixed $entry XML string, Notice, or Activity
+     * @return boolean success
+     */
+    public function notifyActivity($entry)
     {
         if ($this->salmonuri) {
+            $salmon = new Salmon();
+            return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry));
+        }
 
-            $xml = '<?xml version="1.0" encoding="UTF-8" ?' . '>' .
-                          $activity->asString(true);
+        return false;
+    }
 
-            $salmon = new Salmon(); // ?
+    /**
+     * Queue a Salmon notification for later. If queues are disabled we'll
+     * send immediately but won't get the return value.
+     *
+     * @param mixed $entry XML string, Notice, or Activity
+     * @return boolean success
+     */
+    public function notifyDeferred($entry)
+    {
+        if ($this->salmonuri) {
+            $data = array('salmonuri' => $this->salmonuri,
+                          'entry' => $this->notifyPrepXml($entry));
 
-            return $salmon->post($this->salmonuri, $xml);
+            $qm = QueueManager::get();
+            return $qm->enqueue($data, 'salmon');
         }
 
         return false;
     }
 
+    protected function notifyPrepXml($entry)
+    {
+        $preamble = '<?xml version="1.0" encoding="UTF-8" ?' . '>';
+        if (is_string($entry)) {
+            return $entry;
+        } else if ($entry instanceof Activity) {
+            return $preamble . $entry->asString(true);
+        } else if ($entry instanceof Notice) {
+            return $preamble . $entry->asAtomEntry(true, true);
+        } else {
+            throw new ServerException("Invalid type passed to Ostatus_profile::notify; must be XML string or Activity entry");
+        }
+    }
+
     function getBestName()
     {
         if ($this->isGroup()) {
@@ -543,7 +574,8 @@ class Ostatus_profile extends Memcached_DataObject
             // through PuSH setup or Salmon signature checks.
             $actorUri = self::getActorProfileURI($activity);
             if ($actorUri == $this->uri) {
-                // @fixme check if profile info has changed and update it
+                // Check if profile info has changed and update it
+                $this->updateFromActivityObject($activity->actor);
             } else {
                 common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri");
                 return false;
@@ -636,9 +668,27 @@ class Ostatus_profile extends Memcached_DataObject
      */
     protected function purify($html)
     {
-        // @fixme disable caching or set a sane temp dir
         require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
-        $purifier = new HTMLPurifier();
+
+        // By default Purifier wants to cache data to its own code directories,
+        // and spews error messages if they're not writable.
+        $config = HTMLPurifier_Config::createDefault();
+        if (common_config('ostatus', 'purify_cache')) {
+            $config->set('Cache.SerializerPath', common_config('ostatus', 'purify_cache'));
+        } else {
+            // Although recommended in the documentation, this produces a notice:
+            //    "Core.DefinitionCache is an alias, preferred directive name is Cache.DefinitionImpl"
+            // If I then follow *those* directions, I get a warning and it doesn't work:
+            //    "Cannot set undefined directive Core.DefinitionImpl"
+            // So... lesser of two evils. Suppressing the notice from output,
+            // though it'll still be seen and logged by StatusNet's error handler.
+            $old = error_reporting();
+            error_reporting($old & ~E_NOTICE);
+            $config->set('Core.DefinitionCache', null);
+            error_reporting($old);
+        }
+
+        $purifier = new HTMLPurifier($config);
         return $purifier->purify($html);
     }
 
@@ -785,6 +835,11 @@ class Ostatus_profile extends Memcached_DataObject
      */
     protected function updateAvatar($url)
     {
+        if ($url == $this->avatar) {
+            // We've already got this one.
+            return;
+        }
+
         if ($this->isGroup()) {
             $self = $this->localGroup();
         } else {
@@ -816,12 +871,28 @@ class Ostatus_profile extends Memcached_DataObject
                                      common_timestamp());
         rename($temp_filename, Avatar::path($filename));
         $self->setOriginal($filename);
+
+        $orig = clone($this);
+        $this->avatar = $url;
+        $this->update($orig);
     }
 
-    protected static function getActivityObjectAvatar($object)
+    /**
+     * Pull avatar URL from ActivityObject or profile hints
+     *
+     * @param ActivityObject $object
+     * @param array $hints
+     * @return mixed URL string or false
+     */
+
+    protected static function getActivityObjectAvatar($object, $hints=array())
     {
-        // XXX: go poke around in the feed
-        return $object->avatar;
+        if ($object->avatar) {
+            return $object->avatar;
+        } else if (array_key_exists('avatar', $hints)) {
+            return $hints['avatar'];
+        }
+        return false;
     }
 
     /**
@@ -888,7 +959,9 @@ class Ostatus_profile extends Memcached_DataObject
     public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
     {
         $profile = self::getActivityObjectProfile($object);
-        if (!$profile) {
+        if ($profile) {
+            $profile->updateFromActivityObject($object, $hints);
+        } else {
             $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints);
         }
         return $profile;
@@ -898,7 +971,7 @@ class Ostatus_profile extends Memcached_DataObject
      * @param Activity $activity
      * @return mixed matching Ostatus_profile or false if none known
      */
-    protected static function getActorProfile($activity)
+    public static function getActorProfile($activity)
     {
         return self::getActivityObjectProfile($activity->actor);
     }
@@ -957,8 +1030,6 @@ class Ostatus_profile extends Memcached_DataObject
     protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
     {
         $homeuri  = $object->id;
-        $nickname = self::getActivityObjectNickname($object, $hints);
-        $avatar   = self::getActivityObjectAvatar($object);
 
         if (!$homeuri) {
             common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
@@ -1002,43 +1073,19 @@ class Ostatus_profile extends Memcached_DataObject
 
         if ($object->type == ActivityObject::PERSON) {
             $profile = new Profile();
-            $profile->nickname   = $nickname;
-            $profile->fullname   = $object->title;
-            if (!empty($object->link)) {
-                $profile->profileurl = $object->link;
-            } else if (array_key_exists('profileurl', $hints)) {
-                $profile->profileurl = $hints['profileurl'];
-            }
-            $profile->created    = common_sql_now();
-    
-            // @fixme bio
-            // @fixme tags/categories
-            // @fixme location?
-            // @todo tags from categories
-            // @todo lat/lon/location?
-    
+            self::updateProfile($profile, $object, $hints);
+            $profile->created  = common_sql_now();
+
             $oprofile->profile_id = $profile->insert();
-    
             if (!$oprofile->profile_id) {
                 throw new ServerException("Can't save local profile");
             }
         } else {
             $group = new User_group();
-            $group->nickname = $nickname;
-            $group->fullname = $object->title;
-            // @fixme no canonical profileurl; using homepage instead for now
-            $group->homepage = $homeuri;
             $group->created = common_sql_now();
-
-            // @fixme homepage
-            // @fixme bio
-            // @fixme tags/categories
-            // @fixme location?
-            // @todo tags from categories
-            // @todo lat/lon/location?
+            self::updateGroup($group, $object, $hints);
 
             $oprofile->group_id = $group->insert();
-
             if (!$oprofile->group_id) {
                 throw new ServerException("Can't save local profile");
             }
@@ -1047,6 +1094,7 @@ class Ostatus_profile extends Memcached_DataObject
         $ok = $oprofile->insert();
 
         if ($ok) {
+            $avatar = self::getActivityObjectAvatar($object, $hints);
             if ($avatar) {
                 $oprofile->updateAvatar($avatar);
             }
@@ -1056,8 +1104,81 @@ class Ostatus_profile extends Memcached_DataObject
         }
     }
 
+    /**
+     * Save any updated profile information to our local copy.
+     * @param ActivityObject $object
+     * @param array $hints
+     */
+    public function updateFromActivityObject($object, $hints=array())
+    {
+        if ($this->isGroup()) {
+            $group = $this->localGroup();
+            self::updateGroup($group, $object, $hints);
+        } else {
+            $profile = $this->localProfile();
+            self::updateProfile($profile, $object, $hints);
+        }
+        $avatar = self::getActivityObjectAvatar($object, $hints);
+        if ($avatar) {
+            $this->updateAvatar($avatar);
+        }
+    }
+
+    protected static function updateProfile($profile, $object, $hints=array())
+    {
+        $orig = clone($profile);
+
+        $profile->nickname = self::getActivityObjectNickname($object, $hints);
+        $profile->fullname = $object->title;
+        if (!empty($object->link)) {
+            $profile->profileurl = $object->link;
+        } else if (array_key_exists('profileurl', $hints)) {
+            $profile->profileurl = $hints['profileurl'];
+        }
+
+        // @fixme bio
+        // @fixme tags/categories
+        // @fixme location?
+        // @todo tags from categories
+        // @todo lat/lon/location?
+
+        if ($profile->id) {
+            common_log(LOG_DEBUG, "Updating OStatus profile $profile->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
+            $profile->update($orig);
+        }
+    }
+
+    protected static function updateGroup($group, $object, $hints=array())
+    {
+        $orig = clone($group);
+
+        // @fixme need to make nick unique etc *hack hack*
+        $group->nickname = self::getActivityObjectNickname($object, $hints);
+        $group->fullname = $object->title;
+
+        // @fixme no canonical profileurl; using homepage instead for now
+        $group->homepage = $object->id;
+
+        // @fixme homepage
+        // @fixme bio
+        // @fixme tags/categories
+        // @fixme location?
+        // @todo tags from categories
+        // @todo lat/lon/location?
+
+        if ($group->id) {
+            common_log(LOG_DEBUG, "Updating OStatus group $group->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
+            $group->update($orig);
+        }
+    }
+
     protected static function getActivityObjectNickname($object, $hints=array())
     {
+        if ($object->poco) {
+            if (!empty($object->poco->preferredUsername)) {
+                return common_nicknamize($object->poco->preferredUsername);
+            }
+        }
         if (!empty($object->nickname)) {
             return common_nicknamize($object->nickname);
         }