X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=lib%2Ffeedimporter.php;h=466093534e2898aa5864e07b807aaa989f9eb315;hb=8cfd8450d5bbaeeda4a344018ee0e0b27e775196;hp=3f6ac0da4aff5fc8c8624a54c5850c4d23d720b7;hpb=6469d75fb0c2d148c8947e905545da89293b3236;p=quix0rs-gnu-social.git diff --git a/lib/feedimporter.php b/lib/feedimporter.php index 3f6ac0da4a..466093534e 100644 --- a/lib/feedimporter.php +++ b/lib/feedimporter.php @@ -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 @@ -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 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::staticGet('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); - } }