예제 #1
0
 /**
  * @return Ostatus_profile
  */
 function ensureProfile()
 {
     $actor = $this->act->actor;
     if (empty($actor->id)) {
         common_log(LOG_ERR, "broken actor: " . var_export($actor, true));
         common_log(LOG_ERR, "activity with no actor: " . var_export($this->act, true));
         throw new Exception("Received a salmon slap from unidentified actor.");
     }
     return Ostatus_profile::ensureActivityObjectProfile($actor);
 }
예제 #2
0
 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);
 }
 /**
  * Handle a posted object from Salmon
  *
  * @param Activity $activity activity to handle
  * @param mixed    $target   user or group targeted
  *
  * @return boolean hook value
  */
 function onStartHandleSalmonTarget(Activity $activity, $target)
 {
     if (!$this->isMyActivity($activity)) {
         return true;
     }
     $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap.");
     if ($target instanceof User_group || $target->isGroup()) {
         $uri = $target->getUri();
         if (!array_key_exists($uri, $activity->context->attention)) {
             // @todo FIXME: please document (i18n).
             // TRANS: Client exception thrown when ...
             throw new ClientException(_('Object not posted to this group.'));
         }
     } elseif ($target instanceof Profile && $target->isLocal()) {
         $original = null;
         // FIXME: Shouldn't favorites show up with a 'target' activityobject?
         if (!ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) {
             // If this is not a post, it's a verb targeted at something (such as a Favorite attached to a note)
             if (!empty($activity->objects[0]->id)) {
                 $activity->context->replyToID = $activity->objects[0]->id;
             }
         }
         if (!empty($activity->context->replyToID)) {
             $original = Notice::getKV('uri', $activity->context->replyToID);
         }
         if ((!$original instanceof Notice || $original->profile_id != $target->id) && !array_key_exists($target->getUri(), $activity->context->attention)) {
             // @todo FIXME: Please document (i18n).
             // TRANS: Client exception when ...
             throw new ClientException(_('Object not posted to this user.'));
         }
     } else {
         // TRANS: Server exception thrown when a micro app plugin uses a target that cannot be handled.
         throw new ServerException(_('Do not know how to handle this kind of target.'));
     }
     $oactor = Ostatus_profile::ensureActivityObjectProfile($activity->actor);
     $actor = $oactor->localProfile();
     // FIXME: will this work in all cases? I made it work for Favorite...
     if (ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST))) {
         $object = $activity->objects[0];
     } else {
         $object = $activity;
     }
     $options = array('uri' => $object->id, 'url' => $object->link, 'is_local' => Notice::REMOTE, 'source' => 'ostatus');
     if (!isset($this->oldSaveNew)) {
         $notice = Notice::saveActivity($activity, $actor, $options);
     } else {
         $notice = $this->saveNoticeFromActivity($activity, $actor, $options);
     }
     return false;
 }
예제 #4
0
 public function processShare($activity, $method)
 {
     $notice = null;
     $oprofile = $this->checkAuthorship($activity);
     if (empty($oprofile)) {
         common_log(LOG_INFO, "No author matched share activity");
         return null;
     }
     if (count($activity->objects) != 1) {
         // TRANS: Client exception thrown when trying to share multiple activities at once.
         throw new ClientException(_m('Can only handle share activities with exactly one object.'));
     }
     $shared = $activity->objects[0];
     if (!$shared instanceof Activity) {
         // TRANS: Client exception thrown when trying to share a non-activity object.
         throw new ClientException(_m('Can only handle shared activities.'));
     }
     $other = Ostatus_profile::ensureActivityObjectProfile($shared->actor);
     // Save the item (or check for a dupe)
     $sharedNotice = $other->processActivity($shared, $method);
     if (empty($sharedNotice)) {
         $sharedId = $shared->id ? $shared->id : $shared->objects[0]->id;
         // TRANS: Client exception thrown when saving an activity share fails.
         // TRANS: %s is a share ID.
         throw new ClientException(sprintf(_m('Failed to save activity %s.'), $sharedId));
     }
     // The id URI will be used as a unique identifier for for the notice,
     // protecting against duplicate saves. It isn't required to be a URL;
     // tag: URIs for instance are found in Google Buzz feeds.
     $sourceUri = $activity->id;
     $dupe = Notice::staticGet('uri', $sourceUri);
     if ($dupe) {
         common_log(LOG_INFO, "OStatus: ignoring duplicate post: {$sourceUri}");
         return $dupe;
     }
     // We'll also want to save a web link to the original notice, if provided.
     $sourceUrl = null;
     if ($activity->link) {
         $sourceUrl = $activity->link;
     } else {
         if ($activity->link) {
             $sourceUrl = $activity->link;
         } else {
             if (preg_match('!^https?://!', $activity->id)) {
                 $sourceUrl = $activity->id;
             }
         }
     }
     // Use summary as fallback for content
     if (!empty($activity->content)) {
         $sourceContent = $activity->content;
     } else {
         if (!empty($activity->summary)) {
             $sourceContent = $activity->summary;
         } else {
             if (!empty($activity->title)) {
                 $sourceContent = $activity->title;
             } else {
                 // @todo FIXME: Fetch from $sourceUrl?
                 // TRANS: Client exception. %s is a source URI.
                 throw new ClientException(sprintf(_m('No content for notice %s.'), $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 = common_shorten_links($content);
     // If it's too long, try using the summary, and make the
     // HTML an attachment.
     $attachment = null;
     if (Notice::contentTooLong($shortened)) {
         $attachment = $this->saveHTMLFile($activity->title, $rendered);
         $summary = html_entity_decode(strip_tags($activity->summary), ENT_QUOTES, 'UTF-8');
         if (empty($summary)) {
             $summary = $content;
         }
         $shortSummary = common_shorten_links($summary);
         if (Notice::contentTooLong($shortSummary)) {
             $url = common_shorten_url($sourceUrl);
             $shortSummary = substr($shortSummary, 0, Notice::maxContent() - (mb_strlen($url) + 2));
             $content = $shortSummary . ' ' . $url;
             // We mark up the attachment link specially for the HTML output
             // so we can fold-out the full version inline.
             // @todo FIXME i18n: This tooltip will be saved with the site's default language
             // TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
             // TRANS: this will usually be replaced with localised text from StatusNet core messages.
             $showMoreText = _m('Show more');
             $attachUrl = common_local_url('attachment', array('attachment' => $attachment->id));
             $rendered = common_render_text($shortSummary) . '<a href="' . htmlspecialchars($attachUrl) . '"' . ' class="attachment more"' . ' title="' . htmlspecialchars($showMoreText) . '">' . '&#8230;' . '</a>';
         }
     }
     $options = array('is_local' => Notice::REMOTE, 'url' => $sourceUrl, 'uri' => $sourceUri, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'peopletags' => array(), 'tags' => array(), 'urls' => array(), 'repeat_of' => $sharedNotice->id, 'scope' => $sharedNotice->scope);
     // Check for optional attributes...
     if (!empty($activity->time)) {
         $options['created'] = common_sql_date($activity->time);
     }
     if ($activity->context) {
         // Any individual or group attn: targets?
         $replies = $activity->context->attention;
         $options['groups'] = $this->filterReplies($oprofile, $replies);
         $options['replies'] = $replies;
         // Maintain direct reply associations
         // @todo 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;
             }
         }
     }
     if ($this->isPeopletag()) {
         $options['peopletags'][] = $this->localPeopletag();
     }
     // 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) {
         // @todo FIXME: Save these locally or....?
         $options['urls'][] = $href;
     }
     $notice = Notice::saveNew($oprofile->profile_id, $content, 'ostatus', $options);
     return $notice;
 }
예제 #5
0
 protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options = array())
 {
     assert($this->isMyActivity($act));
     // The below algorithm is mainly copied from the previous Ostatus_profile->processShare()
     if (count($act->objects) !== 1) {
         // TRANS: Client exception thrown when trying to share multiple activities at once.
         throw new ClientException(_m('Can only handle share activities with exactly one object.'));
     }
     $shared = $act->objects[0];
     if (!$shared instanceof Activity) {
         // TRANS: Client exception thrown when trying to share a non-activity object.
         throw new ClientException(_m('Can only handle shared activities.'));
     }
     $sharedUri = $shared->id;
     if (!empty($shared->objects[0]->id)) {
         // Because StatusNet since commit 8cc4660 sets $shared->id to a TagURI which
         // f***s up federation, because the URI is no longer recognised by the origin.
         // So we set it to the object ID if it exists, otherwise we trust $shared->id
         $sharedUri = $shared->objects[0]->id;
     }
     if (empty($sharedUri)) {
         throw new ClientException(_m('Shared activity does not have an id'));
     }
     try {
         // First check if we have the shared activity. This has to be done first, because
         // we can't use these functions to "ensureActivityObjectProfile" of a local user,
         // who might be the creator of the shared activity in question.
         $sharedNotice = Notice::getByUri($sharedUri);
     } catch (NoResultException $e) {
         // If no locally stored notice is found, process it!
         // TODO: Remember to check Deleted_notice!
         // TODO: If a post is shared that we can't retrieve - what to do?
         $other = Ostatus_profile::ensureActivityObjectProfile($shared->actor);
         $sharedNotice = $other->processActivity($shared, 'push');
         // FIXME: push/salmon/what?
         if (!$sharedNotice instanceof Notice) {
             // And if we apparently can't get the shared notice, we'll abort the whole thing.
             // TRANS: Client exception thrown when saving an activity share fails.
             // TRANS: %s is a share ID.
             throw new ClientException(sprintf(_m('Failed to save activity %s.'), $sharedUri));
         }
     } catch (FeedSubException $e) {
         // Remote feed could not be found or verified, should we
         // transform this into an "RT @user Blah, blah, blah..."?
         common_log(LOG_INFO, __METHOD__ . ' got a ' . get_class($e) . ': ' . $e->getMessage());
         return false;
     }
     // Setting this here because when the algorithm gets back to
     // Notice::saveActivity it will update the Notice object.
     $stored->repeat_of = $sharedNotice->getID();
     $stored->conversation = $sharedNotice->conversation;
     $stored->object_type = ActivityUtils::resolveUri(ActivityObject::ACTIVITY, true);
     // We don't have to save a repeat in a separate table, we can
     // find repeats by just looking at the notice.repeat_of field.
     // By returning true here instead of something that evaluates
     // to false, we show that we have processed everything properly.
     return true;
 }
예제 #6
0
 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()) {
             // 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."));
     }
     if (Event::handle('StartJoinGroup', array($group, $user))) {
         Group_member::join($group->id, $user->id);
         Event::handle('EndJoinGroup', array($group, $user));
     }
 }
예제 #7
0
 function ensureProfiles()
 {
     try {
         $this->oprofile = Ostatus_profile::getActorProfile($this->activity);
         if (!$this->oprofile instanceof Ostatus_profile) {
             throw new UnknownUriException($this->activity->actor->id);
         }
     } catch (UnknownUriException $e) {
         // Apparently we didn't find the Profile object based on our URI,
         // so OStatus doesn't have it with this URI in ostatus_profile.
         // Try to look it up again, remote side may have changed from http to https
         // or maybe publish an acct: URI now instead of an http: URL.
         //
         // Steps:
         // 1. Check the newly received URI. Who does it say it is?
         // 2. Compare these alleged identities to our local database.
         // 3. If we found any locally stored identities, ask it about its aliases.
         // 4. Do any of the aliases from our known identity match the recently introduced one?
         //
         // Example: We have stored http://example.com/user/1 but this URI says https://example.com/user/1
         common_debug('No local Profile object found for a magicsigned activity author URI: ' . $e->object_uri);
         $disco = new Discovery();
         $xrd = $disco->lookup($e->object_uri);
         // Step 1: We got a bunch of discovery data for https://example.com/user/1 which includes
         //         aliases https://example.com/user and hopefully our original http://example.com/user/1 too
         $all_ids = array_merge(array($xrd->subject), $xrd->aliases);
         if (!in_array($e->object_uri, $all_ids)) {
             common_debug('The activity author URI we got was not listed itself when doing discovery on it.');
             throw $e;
         }
         // Go through each reported alias from lookup to see if we know this already
         foreach ($all_ids as $aliased_uri) {
             $oprofile = Ostatus_profile::getKV('uri', $aliased_uri);
             if (!$oprofile instanceof Ostatus_profile) {
                 continue;
                 // unknown locally, check the next alias
             }
             // Step 2: We found the alleged http://example.com/user/1 URI in our local database,
             //         but this can't be trusted yet because anyone can publish any alias.
             common_debug('Found a local Ostatus_profile for "' . $e->object_uri . '" with this URI: ' . $aliased_uri);
             // We found an existing OStatus profile, but is it really the same? Do a callback to the URI's origin
             // Step 3: lookup our previously known http://example.com/user/1 webfinger etc.
             $xrd = $disco->lookup($oprofile->getUri());
             // getUri returns ->uri, which we filtered on earlier
             $doublecheck_aliases = array_merge(array($xrd->subject), $xrd->aliases);
             common_debug('Trying to match known "' . $aliased_uri . '" against its returned aliases: ' . implode(' ', $doublecheck_aliases));
             // if we find our original URI here, it is a legitimate alias
             // Step 4: Is the newly introduced https://example.com/user/1 URI in the list of aliases
             //         presented by http://example.com/user/1 (i.e. do they both say they are the same identity?)
             if (in_array($e->object_uri, $doublecheck_aliases)) {
                 $oprofile->updateUriKeys($e->object_uri, DiscoveryHints::fromXRD($xrd));
                 $this->oprofile = $oprofile;
                 break;
                 // don't iterate through aliases anymore
             }
         }
         // We might end up here after $all_ids is iterated through without a $this->oprofile value,
         if (!$this->oprofile instanceof Ostatus_profile) {
             common_debug("We do not have a local profile to connect to this activity's author. Let's create one.");
             // ensureActivityObjectProfile throws exception on failure
             $this->oprofile = Ostatus_profile::ensureActivityObjectProfile($this->activity->actor);
         }
     }
     assert($this->oprofile instanceof Ostatus_profile);
     $this->actor = $this->oprofile->localProfile();
 }
예제 #8
0
 /**
  * Handle a posted object from Salmon
  *
  * @param Activity $activity activity to handle
  * @param mixed    $target   user or group targeted
  *
  * @return boolean hook value
  */
 function onStartHandleSalmonTarget($activity, $target)
 {
     if ($this->isMyActivity($activity)) {
         $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap.");
         if ($target instanceof User_group) {
             $uri = $target->getUri();
             if (!in_array($uri, $activity->context->attention)) {
                 // @todo FIXME: please document (i18n).
                 // TRANS: Client exception thrown when ...
                 throw new ClientException(_('Bookmark not posted to this group.'));
             }
         } else {
             if ($target instanceof User) {
                 $uri = $target->uri;
                 $original = null;
                 if (!empty($activity->context->replyToID)) {
                     $original = Notice::staticGet('uri', $activity->context->replyToID);
                 }
                 if (!in_array($uri, $activity->context->attention) && (empty($original) || $original->profile_id != $target->id)) {
                     // @todo FIXME: Please document (i18n).
                     // TRANS: Client exception when ...
                     throw new ClientException(_('Object not posted to this user.'));
                 }
             } else {
                 // TRANS: Server exception thrown when a micro app plugin uses a target that cannot be handled.
                 throw new ServerException(_('Do not know how to handle this kind of target.'));
             }
         }
         $actor = Ostatus_profile::ensureActivityObjectProfile($activity->actor);
         $object = $activity->objects[0];
         $options = array('uri' => $object->id, 'url' => $object->link, 'is_local' => Notice::REMOTE, 'source' => 'ostatus');
         // $actor is an ostatus_profile
         $this->saveNoticeFromActivity($activity, $actor->localProfile(), $options);
         return false;
     }
     return true;
 }
예제 #9
0
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));
    }
}
예제 #10
0
 function handleUntag()
 {
     if ($this->activity->target->type == ActivityObject::_LIST) {
         if ($this->activity->objects[0]->type != ActivityObject::PERSON) {
             // TRANS: Client exception.
             throw new ClientException(_m('Not a person object.'));
             return false;
         }
         // this is a peopletag
         $tagged = User::staticGet('uri', $this->activity->objects[0]->id);
         if (empty($tagged)) {
             // TRANS: Client exception.
             throw new ClientException(_m('Unidentified profile being untagged.'));
         }
         if ($tagged->id !== $this->user->id) {
             // TRANS: Client exception.
             throw new ClientException(_m('This user is not the one being untagged.'));
         }
         // save the list
         $tagger = $this->ensureProfile();
         $list = Ostatus_profile::ensureActivityObjectProfile($this->activity->target);
         $ptag = $list->localPeopletag();
         $result = Profile_tag::unTag($ptag->tagger, $tagged->id, $ptag->tag);
         if (!$result) {
             // TRANS: Client exception.
             throw new ClientException(_m('The tag could not be deleted.'));
         }
     }
 }
예제 #11
0
 /**
  * Handle a posted bookmark from Salmon
  *
  * @param Activity $activity activity to handle
  * @param mixed    $target   user or group targeted
  *
  * @return boolean hook value
  */
 function onStartHandleSalmonTarget($activity, $target)
 {
     if (self::_isPostBookmark($activity)) {
         $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap.");
         if ($target instanceof User_group) {
             $uri = $target->getUri();
             if (!in_array($uri, $activity->context->attention)) {
                 throw new ClientException(_("Bookmark not posted " . "to this group."));
             }
         } else {
             if ($target instanceof User) {
                 $uri = $target->uri;
                 $original = null;
                 if (!empty($activity->context->replyToID)) {
                     $original = Notice::staticGet('uri', $activity->context->replyToID);
                 }
                 if (!in_array($uri, $activity->context->attention) && (empty($original) || $original->profile_id != $target->id)) {
                     throw new ClientException(_("Bookmark not posted " . "to this user."));
                 }
             } else {
                 throw new ServerException(_("Don't know how to handle " . "this kind of target."));
             }
         }
         $author = Ostatus_profile::ensureActivityObjectProfile($activity->actor);
         self::_postRemoteBookmark($author, $activity);
         return false;
     }
     return true;
 }