123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- <?php
- /**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * class to import activities as part of a user's timeline
- *
- * 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 Cache
- * @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('GNUSOCIAL')) { exit(1); }
- /**
- * Class comment
- *
- * @category General
- * @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 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) {
- 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) {
- // 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 purify($content)
- {
- require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
- $config = array('safe' => 1,
- 'deny_attribute' => 'id,style,on*');
- return htmLawed($content, $config);
- }
- }
|