]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Move account restoration code to a shared library
authorEvan Prodromou <evan@status.net>
Wed, 15 Dec 2010 22:53:38 +0000 (17:53 -0500)
committerEvan Prodromou <evan@status.net>
Wed, 15 Dec 2010 22:53:38 +0000 (17:53 -0500)
Moved most of the heavy-lifting for account restoration out of
restoreuser.php and into its own class, with the hope that we'll do
the work from the Web eventually.

lib/accountrestorer.php [new file with mode: 0644]
scripts/restoreuser.php

diff --git a/lib/accountrestorer.php b/lib/accountrestorer.php
new file mode 100644 (file)
index 0000000..98f12cc
--- /dev/null
@@ -0,0 +1,360 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * A class for restoring accounts
+ * 
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Account
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * A class for restoring accounts
+ *
+ * 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>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+class AccountRestorer
+{
+    function loadXML($xml)
+    {
+        $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;
+    }
+
+    function importActivityStream($user, $doc)
+    {
+        $feed = $doc->documentElement;
+
+        $subjectEl = ActivityUtils::child($feed, Activity::SUBJECT, Activity::SPEC);
+
+        if (!empty($subjectEl)) {
+            $subject = new ActivityObject($subjectEl);
+            // TRANS: Commandline script output. %1$s is the subject ID, %2$s is the subject nickname.
+            printfv(_("Backup file for user %1$s (%2$s)")."\n", $subject->id, Ostatus_profile::getActivityObjectNickname($subject));
+        } else {
+            throw new Exception("Feed doesn't have an <activity:subject> element.");
+        }
+
+        if (is_null($user)) {
+            // TRANS: Commandline script output.
+            printfv(_("No user specified; using backup user.")."\n");
+            $user = $this->userFromSubject($subject);
+        }
+
+        $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
+
+        // TRANS: Commandline script output. %d is the number of entries in the activity stream in backup; used for plural.
+        printfv(_m("%d entry in backup.","%d entries in backup.",$entries->length)."\n", $entries->length);
+
+        for ($i = $entries->length - 1; $i >= 0; $i--) {
+            try {
+                $entry = $entries->item($i);
+
+                $activity = new Activity($entry, $feed);
+
+                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) {
+                print $e->getMessage()."\n";
+                continue;
+            }
+        }
+    }
+
+    function subscribeProfile($user, $subject, $activity)
+    {
+        $profile = $user->getProfile();
+
+        if ($activity->objects[0]->id == $subject->id) {
+
+            $other = $activity->actor;
+            $otherUser = User::staticGet('uri', $other->id);
+
+            if (!empty($otherUser)) {
+                $otherProfile = $otherUser->getProfile();
+            } else {
+                throw new Exception("Can't force remote user to subscribe.");
+            }
+            // 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);
+
+            if (!empty($otherUser)) {
+                $otherProfile = $otherUser->getProfile();
+            } else {
+                $oprofile = Ostatus_profile::ensureActivityObjectProfile($other);
+                $otherProfile = $oprofile->localProfile();
+            }
+
+            Subscription::start($profile, $otherProfile);
+        } else {
+            throw new Exception("This activity seems unrelated to our user.");
+        }
+    }
+
+    function joinGroup($user, $activity)
+    {
+        // XXX: check that actor == subject
+
+        $uri = $activity->objects[0]->id;
+
+        $group = User_group::staticGet('uri', $uri);
+
+        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();
+        }
+
+        assert(!empty($group));
+
+        if (Event::handle('StartJoinGroup', array($group, $user))) {
+            Group_member::join($group->id, $user->id);
+            Event::handle('EndJoinGroup', array($group, $user));
+        }
+    }
+
+    // XXX: largely cadged from Ostatus_profile::processNote()
+
+    function postNote($user, $activity)
+    {
+        $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;
+        } 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);
+        }
+
+        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)
+    {
+        $user = User::staticGet('uri', $subject->id);
+
+        if (empty($user)) {
+            $attrs =
+                array('nickname' => Ostatus_profile::getActivityObjectNickname($subject),
+                      'uri' => $subject->id);
+
+            $user = User::register($attrs);
+        }
+
+        $profile = $user->getProfile();
+        Ostatus_profile::updateProfile($profile, $subject);
+
+        // FIXME: Update avatar
+        return $user;
+    }
+
+    function purify($content)
+    {
+        $config = array('safe' => 1,
+                        'deny_attribute' => 'id,style,on*');
+        return htmLawed($content, $config);
+    }
+}
index b37e9db74108752f4b00072e212079b948b18b5d..eac7e5cf2902cef005f47b4015cabfe33cc240ad 100644 (file)
@@ -36,6 +36,7 @@ END_OF_RESTOREUSER_HELP;
 require_once INSTALLDIR.'/scripts/commandline.inc';
 require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
 
+
 function getActivityStreamDocument()
 {
     $filename = get_option_value('f', 'file');
@@ -60,311 +61,12 @@ function getActivityStreamDocument()
     // TRANS: Commandline script output. %s is the filename that contains a backup for a user.
     printfv(_("Getting backup from file '%s'.")."\n",$filename);
 
-    $xml = file_get_contents($filename);
-
-    $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;
-}
-
-function importActivityStream($user, $doc)
-{
-    $feed = $doc->documentElement;
-
-    $subjectEl = ActivityUtils::child($feed, Activity::SUBJECT, Activity::SPEC);
-
-    if (!empty($subjectEl)) {
-        $subject = new ActivityObject($subjectEl);
-        // TRANS: Commandline script output. %1$s is the subject ID, %2$s is the subject nickname.
-        printfv(_("Backup file for user %1$s (%2$s)")."\n", $subject->id, Ostatus_profile::getActivityObjectNickname($subject));
-    } else {
-        throw new Exception("Feed doesn't have an <activity:subject> element.");
-    }
-
-    if (is_null($user)) {
-        // TRANS: Commandline script output.
-        printfv(_("No user specified; using backup user.")."\n");
-        $user = userFromSubject($subject);
-    }
-
-    $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
-
-    // TRANS: Commandline script output. %d is the number of entries in the activity stream in backup; used for plural.
-    printfv(_m("%d entry in backup.","%d entries in backup.",$entries->length)."\n", $entries->length);
-
-    for ($i = $entries->length - 1; $i >= 0; $i--) {
-        try {
-            $entry = $entries->item($i);
-
-            $activity = new Activity($entry, $feed);
-
-            switch ($activity->verb) {
-            case ActivityVerb::FOLLOW:
-                subscribeProfile($user, $subject, $activity);
-                break;
-            case ActivityVerb::JOIN:
-                joinGroup($user, $activity);
-                break;
-            case ActivityVerb::POST:
-                postNote($user, $activity);
-                break;
-            default:
-                throw new Exception("Unknown verb: {$activity->verb}");
-            }
-        } catch (Exception $e) {
-            print $e->getMessage()."\n";
-            continue;
-        }
-    }
-}
-
-function subscribeProfile($user, $subject, $activity)
-{
-    $profile = $user->getProfile();
-
-    if ($activity->objects[0]->id == $subject->id) {
-
-        $other = $activity->actor;
-        $otherUser = User::staticGet('uri', $other->id);
-
-        if (!empty($otherUser)) {
-            $otherProfile = $otherUser->getProfile();
-        } else {
-            throw new Exception("Can't force remote user to subscribe.");
-        }
-        // 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);
-
-        if (!empty($otherUser)) {
-            $otherProfile = $otherUser->getProfile();
-        } else {
-            $oprofile = Ostatus_profile::ensureActivityObjectProfile($other);
-            $otherProfile = $oprofile->localProfile();
-        }
-
-        Subscription::start($profile, $otherProfile);
-    } else {
-        throw new Exception("This activity seems unrelated to our user.");
-    }
-}
-
-function joinGroup($user, $activity)
-{
-    // XXX: check that actor == subject
-
-    $uri = $activity->objects[0]->id;
-
-    $group = User_group::staticGet('uri', $uri);
-
-    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();
-    }
-
-    assert(!empty($group));
-
-    if (Event::handle('StartJoinGroup', array($group, $user))) {
-        Group_member::join($group->id, $user->id);
-        Event::handle('EndJoinGroup', array($group, $user));
-    }
-}
-
-// XXX: largely cadged from Ostatus_profile::processNote()
-
-function postNote($user, $activity)
-{
-    $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;
-    } 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 = 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);
-    }
-
-    if ($activity->context) {
-        // Any individual or group attn: targets?
-
-        list($options['groups'], $options['replies']) = 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)
-{
-    $user = User::staticGet('uri', $subject->id);
-
-    if (empty($user)) {
-        $attrs =
-          array('nickname' => Ostatus_profile::getActivityObjectNickname($subject),
-                'uri' => $subject->id);
-
-        $user = User::register($attrs);
-    }
-
-    $profile = $user->getProfile();
-    Ostatus_profile::updateProfile($profile, $subject);
+    $xml = file_get_contents($filename);
 
-    // FIXME: Update avatar
-    return $user;
+    return $xml;
 }
 
-function purify($content)
-{
-    $config = array('safe' => 1,
-                    'deny_attribute' => 'id,style,on*');
-    return htmLawed($content, $config);
-}
 
 try {
     try {
@@ -372,8 +74,10 @@ try {
     } catch (NoUserArgumentException $noae) {
         $user = null;
     }
-    $doc  = getActivityStreamDocument();
-    importActivityStream($user, $doc);
+    $xml = getActivityStreamDocument();
+    $restorer = new AccountRestorer();
+    $doc = $restorer->loadXML($xml);
+    $restorer->importActivityStream($user, $doc);
 } catch (Exception $e) {
     print $e->getMessage()."\n";
     exit(1);