]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/feedimporter.php
Misses this file to merge. I like the comments.
[quix0rs-gnu-social.git] / lib / feedimporter.php
index 3f6ac0da4aff5fc8c8624a54c5850c4d23d720b7..b5807ee8b45d82384fe98e355a4f9cba864387dd 100644 (file)
@@ -3,8 +3,8 @@
  * StatusNet - the distributed open-source microblogging tool
  * Copyright (C) 2010, StatusNet, Inc.
  *
- * A class for restoring accounts
- * 
+ * Importer for feeds of activities
+ *
  * PHP version 5
  *
  * This program is free software: you can redistribute it and/or modify
@@ -35,13 +35,11 @@ if (!defined('STATUSNET')) {
 }
 
 /**
- * A class for restoring accounts
+ * Importer for feeds of activities
+ *
+ * Takes an XML file representing a feed of activities and imports each
+ * activity to the user in question.
  *
- * This is a clumsy objectification of the functions in restoreuser.php.
- * 
- * Note that it quite illegally uses the OStatus_profile class which may
- * not even exist on this server.
- * 
  * @category  Account
  * @package   StatusNet
  * @author    Evan Prodromou <evan@status.net>
@@ -49,310 +47,115 @@ if (!defined('STATUSNET')) {
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  * @link      http://status.net/
  */
-
-class AccountRestorer
+class FeedImporter extends QueueHandler
 {
-    private $_trusted = false;
-
-    function loadXML($xml)
+    /**
+     * Transport identifier
+     *
+     * @return string identifier for this queue handler
+     */
+    public function transport()
     {
-        $dom = DOMDocument::loadXML($xml);
-
-        if ($dom->documentElement->namespaceURI != Activity::ATOM ||
-            $dom->documentElement->localName != 'feed') {
-            throw new Exception("'$filename' is not an Atom feed.");
-        }
-
-        return $dom;
+        return 'feedimp';
     }
 
-    function importActivityStream($user, $doc)
+    function handle($data)
     {
-        $feed = $doc->documentElement;
+        list($user, $xml, $trusted) = $data;
 
-        $subjectEl = ActivityUtils::child($feed, Activity::SUBJECT, Activity::SPEC);
+        try {
+            $doc = DOMDocument::loadXML($xml);
 
-        if (!empty($subjectEl)) {
-            $subject = new ActivityObject($subjectEl);
-        } else {
-            throw new Exception("Feed doesn't have an <activity:subject> element.");
-        }
-
-        if (is_null($user)) {
-            $user = $this->userFromSubject($subject);
-        }
+            $feed = $doc->documentElement;
 
-        $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
+            if ($feed->namespaceURI != Activity::ATOM ||
+                $feed->localName != 'feed') {
+                // TRANS: Client exception thrown when an imported feed is not an Atom feed.
+                throw new ClientException(_("Not an Atom feed."));
+            }
 
-        $activities = $this->entriesToActivities($entries, $feed);
 
-        // XXX: sort entries here
+            $author = ActivityUtils::getFeedAuthor($feed);
 
-        foreach ($activities as $activity) {
-            try {
-                switch ($activity->verb) {
-                case ActivityVerb::FOLLOW:
-                    $this->subscribeProfile($user, $subject, $activity);
-                    break;
-                case ActivityVerb::JOIN:
-                    $this->joinGroup($user, $activity);
-                    break;
-                case ActivityVerb::POST:
-                    $this->postNote($user, $activity);
-                    break;
-                default:
-                    throw new Exception("Unknown verb: {$activity->verb}");
-                }
-            } catch (Exception $e) {
-                common_log(LOG_WARNING, $e->getMessage());
-                continue;
+            if (empty($author)) {
+                // TRANS: Client exception thrown when an imported feed does not have an author.
+                throw new ClientException(_("No author in the feed."));
             }
-        }
-    }
 
-    function subscribeProfile($user, $subject, $activity)
-    {
-        $profile = $user->getProfile();
-
-        if ($activity->objects[0]->id == $subject->id) {
-            if (!$this->_trusted) {
-                    throw new Exception("Skipping a pushed subscription.");
-            } else {
-                $other = $activity->actor;
-                $otherUser = User::staticGet('uri', $other->id);
-
-                if (!empty($otherUser)) {
-                    $otherProfile = $otherUser->getProfile();
+            if (empty($user)) {
+                if ($trusted) {
+                    $user = $this->userFromAuthor($author);
                 } else {
-                    throw new Exception("Can't force remote user to subscribe.");
+                    // TRANS: Client exception thrown when an imported feed does not have an author that
+                    // TRANS: can be associated with a user.
+                    throw new ClientException(_("Cannot import without a user."));
                 }
-                // XXX: don't do this for untrusted input!
-                Subscription::start($otherProfile, $profile);
             }
-        } else if (empty($activity->actor) 
-                   || $activity->actor->id == $subject->id) {
 
-            $other = $activity->objects[0];
-            $otherUser = User::staticGet('uri', $other->id);
+            $activities = $this->getActivities($feed);
 
-            if (!empty($otherUser)) {
-                $otherProfile = $otherUser->getProfile();
-            } else {
-                $oprofile = Ostatus_profile::ensureActivityObjectProfile($other);
-                $otherProfile = $oprofile->localProfile();
-            }
+            $qm = QueueManager::get();
 
-            Subscription::start($profile, $otherProfile);
-        } else {
-            throw new Exception("This activity seems unrelated to our user.");
+            foreach ($activities as $activity) {
+                $qm->enqueue(array($user, $author, $activity, $trusted), 'actimp');
+            }
+        } catch (ClientException $ce) {
+            common_log(LOG_WARNING, $ce->getMessage());
+            return true;
+        } catch (ServerException $se) {
+            common_log(LOG_ERR, $ce->getMessage());
+            return false;
+        } catch (Exception $e) {
+            common_log(LOG_ERR, $ce->getMessage());
+            return false;
         }
     }
 
-    function joinGroup($user, $activity)
+    function getActivities($feed)
     {
-        // XXX: check that actor == subject
-
-        $uri = $activity->objects[0]->id;
+        $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
 
-        $group = User_group::staticGet('uri', $uri);
+        $activities = array();
 
-        if (empty($group)) {
-            $oprofile = Ostatus_profile::ensureActivityObjectProfile($activity->objects[0]);
-            if (!$oprofile->isGroup()) {
-                throw new Exception("Remote profile is not a group!");
-            }
-            $group = $oprofile->localGroup();
+        for ($i = 0; $i < $entries->length; $i++) {
+            $activities[] = new Activity($entries->item($i));
         }
 
-        assert(!empty($group));
+        usort($activities, array("FeedImporter", "activitySort"));
 
-        if (Event::handle('StartJoinGroup', array($group, $user))) {
-            Group_member::join($group->id, $user->id);
-            Event::handle('EndJoinGroup', array($group, $user));
-        }
+        return $activities;
     }
 
-    // XXX: largely cadged from Ostatus_profile::processNote()
-
-    function postNote($user, $activity)
+    /**
+     * Sort activities oldest-first
+     */
+    static function activitySort($a, $b)
     {
-        $note = $activity->objects[0];
-
-        $sourceUri = $note->id;
-
-        $notice = Notice::staticGet('uri', $sourceUri);
-
-        if (!empty($notice)) {
-            // This is weird.
-            $orig = clone($notice);
-            $notice->profile_id = $user->id;
-            $notice->update($orig);
-            return;
-        }
-
-        // Use summary as fallback for content
-
-        if (!empty($note->content)) {
-            $sourceContent = $note->content;
-        } else if (!empty($note->summary)) {
-            $sourceContent = $note->summary;
-        } else if (!empty($note->title)) {
-            $sourceContent = $note->title;
+        if ($a->time == $b->time) {
+            return 0;
+        } else if ($a->time < $b->time) {
+            return -1;
         } else {
-            // @fixme fetch from $sourceUrl?
-            // @todo i18n FIXME: use sprintf and add i18n.
-            throw new ClientException("No content for notice {$sourceUri}.");
-        }
-
-        // Get (safe!) HTML and text versions of the content
-
-        $rendered = $this->purify($sourceContent);
-        $content = html_entity_decode(strip_tags($rendered), ENT_QUOTES, 'UTF-8');
-
-        $shortened = $user->shortenLinks($content);
-
-        $options = array('is_local' => Notice::LOCAL_PUBLIC,
-                         'uri' => $sourceUri,
-                         'rendered' => $rendered,
-                         'replies' => array(),
-                         'groups' => array(),
-                         'tags' => array(),
-                         'urls' => array());
-
-        // Check for optional attributes...
-
-        if (!empty($activity->time)) {
-            $options['created'] = common_sql_date($activity->time);
+            return 1;
         }
-
-        if ($activity->context) {
-            // Any individual or group attn: targets?
-
-            list($options['groups'], $options['replies']) = $this->filterAttention($activity->context->attention);
-
-            // Maintain direct reply associations
-            // @fixme what about conversation ID?
-            if (!empty($activity->context->replyToID)) {
-                $orig = Notice::staticGet('uri',
-                                          $activity->context->replyToID);
-                if (!empty($orig)) {
-                    $options['reply_to'] = $orig->id;
-                }
-            }
-
-            $location = $activity->context->location;
-
-            if ($location) {
-                $options['lat'] = $location->lat;
-                $options['lon'] = $location->lon;
-                if ($location->location_id) {
-                    $options['location_ns'] = $location->location_ns;
-                    $options['location_id'] = $location->location_id;
-                }
-            }
-        }
-
-        // Atom categories <-> hashtags
-
-        foreach ($activity->categories as $cat) {
-            if ($cat->term) {
-                $term = common_canonical_tag($cat->term);
-                if ($term) {
-                    $options['tags'][] = $term;
-                }
-            }
-        }
-
-        // Atom enclosures -> attachment URLs
-        foreach ($activity->enclosures as $href) {
-            // @fixme save these locally or....?
-            $options['urls'][] = $href;
-        }
-
-        $saved = Notice::saveNew($user->id,
-                                 $content,
-                                 'restore', // TODO: restore the actual source
-                                 $options);
-
-        return $saved;
     }
 
-    function filterAttention($attn)
-    {
-        $groups = array();
-        $replies = array();
-
-        foreach (array_unique($attn) as $recipient) {
-
-            // Is the recipient a local user?
-
-            $user = User::staticGet('uri', $recipient);
-
-            if ($user) {
-                // @fixme sender verification, spam etc?
-                $replies[] = $recipient;
-                continue;
-            }
-
-            // Is the recipient a remote group?
-            $oprofile = Ostatus_profile::ensureProfileURI($recipient);
-
-            if ($oprofile) {
-                if (!$oprofile->isGroup()) {
-                    // may be canonicalized or something
-                    $replies[] = $oprofile->uri;
-                }
-                continue;
-            }
-
-            // Is the recipient a local group?
-            // @fixme uri on user_group isn't reliable yet
-            // $group = User_group::staticGet('uri', $recipient);
-            $id = OStatusPlugin::localGroupFromUrl($recipient);
-
-            if ($id) {
-                $group = User_group::staticGet('id', $id);
-                if ($group) {
-                    // Deliver to all members of this local group if allowed.
-                    $profile = $sender->localProfile();
-                    if ($profile->isMember($group)) {
-                        $groups[] = $group->id;
-                    } else {
-                        common_log(LOG_INFO, "Skipping reply to local group {$group->nickname} as sender {$profile->id} is not a member");
-                    }
-                    continue;
-                } else {
-                    common_log(LOG_INFO, "Skipping reply to bogus group $recipient");
-                }
-            }
-        }
-
-        return array($groups, $replies);
-    }
-   function userFromSubject($subject)
+    function userFromAuthor($author)
     {
-        $user = User::staticGet('uri', $subject->id);
+        $user = User::getKV('uri', $author->id);
 
         if (empty($user)) {
             $attrs =
-                array('nickname' => Ostatus_profile::getActivityObjectNickname($subject),
-                      'uri' => $subject->id);
+                array('nickname' => Ostatus_profile::getActivityObjectNickname($author),
+                      'uri' => $author->id);
 
             $user = User::register($attrs);
         }
 
         $profile = $user->getProfile();
-        Ostatus_profile::updateProfile($profile, $subject);
+        Ostatus_profile::updateProfile($profile, $author);
 
-        // FIXME: Update avatar
+        // @todo FIXME: Update avatar
         return $user;
     }
-
-    function purify($content)
-    {
-        $config = array('safe' => 1,
-                        'deny_attribute' => 'id,style,on*');
-        return htmLawed($content, $config);
-    }
 }