From 982426a79075b83998136ed0ab95890bb17b2e14 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 19 Jul 2011 15:20:06 -0400 Subject: [PATCH] handle incoming SHARE verbs --- plugins/OStatus/classes/Ostatus_profile.php | 220 ++++++++++++++++++-- 1 file changed, 207 insertions(+), 13 deletions(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 479a590b3f..cbaae65c3f 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -467,26 +467,38 @@ class Ostatus_profile extends Managed_DataObject public function processEntry($entry, $feed, $source) { $activity = new Activity($entry, $feed); + $this->processActivity($activity, $source); + } + + public function processActivity($activity, $source) + { + // The "WithProfile" events were added later. if (Event::handle('StartHandleFeedEntryWithProfile', array($activity, $this)) && Event::handle('StartHandleFeedEntry', array($activity))) { - // @todo process all activity objects - switch ($activity->objects[0]->type) { - case ActivityObject::ARTICLE: - case ActivityObject::BLOGENTRY: - case ActivityObject::NOTE: - case ActivityObject::STATUS: - case ActivityObject::COMMENT: - case null: - if ($activity->verb == ActivityVerb::POST) { + + switch ($activity->verb) { + case ActivityVerb::POST: + // @todo process all activity objects + switch ($activity->objects[0]->type) { + case ActivityObject::ARTICLE: + case ActivityObject::BLOGENTRY: + case ActivityObject::NOTE: + case ActivityObject::STATUS: + case ActivityObject::COMMENT: + case null: $this->processPost($activity, $source); - } else { - common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb"); + break; + default: + // TRANS: Client exception. + throw new ClientException(_m('Cannot handle that kind of post.')); } break; + case ActivityVerb::SHARE: + $result = $this->processShare($activity, $source); + break; default: - // TRANS: Client exception. - throw new ClientException(_m('Cannot handle that kind of post.')); + common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb"); } Event::handle('EndHandleFeedEntry', array($activity)); @@ -494,6 +506,188 @@ class Ostatus_profile extends Managed_DataObject } } + public function processShare($activity, $method) + { + $oprofile = $this->checkAuthorship($activity); + + if (empty($oprofile)) { + common_log(LOG_INFO, "No author matched share activity"); + return false; + } + + if (count($activity->objects) != 1) { + throw new ClientException(_m("Can only handle share activities with exactly one object.")); + } + + $shared = $activity->objects[0]; + + if (!($shared instanceof Activity)) { + throw new ClientException(_m("Can only handle shared activities.")); + } + + // Save the item (or check for a dupe) + + $this->processActivity($shared, $method); + + // XXX: process*() should return the new or existing notice. They don't, so we have to + // go fishing for it now. + + $sharedNotice = Notice::staticGet('uri', $shared->id); + + if (empty($sharedNotice)) { + throw new ClientException(sprintf(_m("Failed to save activity %d"), + $shared->id)); + } + + // 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 false; + } + + // 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 { + // @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) . + '' . + '…' . + ''; + } + } + + $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 + // @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) { + // @fixme save these locally or....? + $options['urls'][] = $href; + } + + return Notice::saveNew($oprofile->profile_id, + $content, + 'ostatus', + $content); + } + /** * Process an incoming post activity from this remote feed. * @param Activity $activity -- 2.39.5