. * * @category Cache * @package StatusNet * @author Evan Prodromou * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ if (!defined('GNUSOCIAL')) { exit(1); } /** * Class comment * * @category General * @package StatusNet * @author Evan Prodromou * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ class ActivityImporter extends QueueHandler { private $trusted = false; /** * Function comment * * @param * * @return */ function handle($data) { list($user, $author, $activity, $trusted) = $data; $this->trusted = $trusted; $done = null; try { if (Event::handle('StartImportActivity', array($user, $author, $activity, $trusted, &$done))) { switch ($activity->verb) { case ActivityVerb::FOLLOW: $this->subscribeProfile($user, $author, $activity); break; case ActivityVerb::JOIN: $this->joinGroup($user, $activity); break; case ActivityVerb::POST: $this->postNote($user, $author, $activity); break; default: // TRANS: Client exception thrown when using an unknown verb for the activity importer. throw new ClientException(sprintf(_("Unknown verb: \"%s\"."),$activity->verb)); } Event::handle('EndImportActivity', array($user, $author, $activity, $trusted)); $done = true; } } catch (Exception $e) { common_log(LOG_ERR, $e->getMessage()); $done = true; } return $done; } function subscribeProfile($user, $author, $activity) { $profile = $user->getProfile(); if ($activity->objects[0]->id == $author->id) { if (!$this->trusted) { // TRANS: Client exception thrown when trying to force a subscription for an untrusted user. throw new ClientException(_('Cannot force subscription for untrusted user.')); } $other = $activity->actor; $otherUser = User::getKV('uri', $other->id); if (!$otherUser instanceof User) { // TRANS: Client exception thrown when trying to force a remote user to subscribe. throw new Exception(_('Cannot force remote user to subscribe.')); } $otherProfile = $otherUser->getProfile(); // XXX: don't do this for untrusted input! Subscription::start($otherProfile, $profile); } else if (empty($activity->actor) || $activity->actor->id == $author->id) { $other = $activity->objects[0]; try { $otherProfile = Profile::fromUri($other->id); // TRANS: Client exception thrown when trying to subscribe to an unknown profile. } catch (UnknownUriException $e) { // Let's convert it to a client exception instead of server. throw new ClientException(_('Unknown profile.')); } Subscription::start($profile, $otherProfile); } else { // TRANS: Client exception thrown when trying to import an event not related to the importing user. 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::getKV('uri', $uri); if (!$group instanceof User_group) { $oprofile = Ostatus_profile::ensureActivityObjectProfile($activity->objects[0]); if (!$oprofile->isGroup()) { // TRANS: Client exception thrown when trying to join a remote group that is not a group. throw new ClientException(_('Remote profile is not a group!')); } $group = $oprofile->localGroup(); } assert(!empty($group)); if ($user->isMember($group)) { // TRANS: Client exception thrown when trying to join a group the importing user is already a member of. throw new ClientException(_("User is already a member of this group.")); } $user->joinGroup($group); } // XXX: largely cadged from Ostatus_profile::processNote() function postNote($user, $author, $activity) { $note = $activity->objects[0]; $sourceUri = $note->id; $notice = Notice::getKV('uri', $sourceUri); if ($notice instanceof Notice) { common_log(LOG_INFO, "Notice {$sourceUri} already exists."); if ($this->trusted) { $profile = $notice->getProfile(); $uri = $profile->getUri(); if ($uri === $author->id) { common_log(LOG_INFO, sprintf('Updating notice author from %s to %s', $author->id, $user->getUri())); $orig = clone($notice); $notice->profile_id = $user->id; $notice->update($orig); return; } else { // TRANS: Client exception thrown when trying to import a notice by another user. // TRANS: %1$s is the source URI of the notice, %2$s is the URI of the author. throw new ClientException(sprintf(_('Already know about notice %1$s and '. ' it has a different author %2$s.'), $sourceUri, $uri)); } } else { // TRANS: Client exception thrown when trying to overwrite the author information for a non-trusted user during import. throw new ClientException(_('Not overwriting author info for non-trusted user.')); } } // 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? // TRANS: Client exception thrown when trying to import a notice without content. // TRANS: %s is the notice URI. throw new ClientException(sprintf(_('No content for notice %s.'),$sourceUri)); } // Get (safe!) HTML and text versions of the content $rendered = $this->purify($sourceContent); $content = common_strip_html($rendered); $shortened = $user->shortenLinks($content); $options = array('is_local' => Notice::LOCAL_PUBLIC, 'uri' => $sourceUri, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'tags' => array(), 'urls' => array(), 'distribute' => false); // 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::getKV('uri', $activity->context->replyToID); if ($orig instanceof Notice) { $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; } common_log(LOG_INFO, "Saving notice {$options['uri']}"); $saved = Notice::saveNew($user->id, $content, 'restore', // TODO: restore the actual source $options); return $saved; } protected function filterAttention(array $attn) { $groups = array(); // TODO: context->attention $replies = array(); // TODO: context->attention foreach ($attn as $recipient=>$type) { // Is the recipient a local user? $user = User::getKV('uri', $recipient); if ($user instanceof User) { // TODO: @fixme sender verification, spam etc? $replies[] = $recipient; continue; } // Is the recipient a remote group? $oprofile = Ostatus_profile::ensureProfileURI($recipient); if ($oprofile instanceof Ostatus_profile) { if (!$oprofile->isGroup()) { // may be canonicalized or something $replies[] = $oprofile->uri; } continue; } // Is the recipient a local group? // TODO: @fixme uri on user_group isn't reliable yet // $group = User_group::getKV('uri', $recipient); $id = OStatusPlugin::localGroupFromUrl($recipient); if ($id) { $group = User_group::getKV('id', $id); if ($group instanceof User_group) { // Deliver to all members of this local group if allowed. $profile = Profile::getKV('id', $recipient); if (($profile instanceof Profile) && ($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 purify($content) { require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; $config = array('safe' => 1, 'deny_attribute' => 'id,style,on*'); return htmLawed($content, $config); } }