* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* @package OStatusPlugin
- * @maintainer Brion Vibber <brion@status.net>
+ * @author Brion Vibber <brion@status.net>
+ * @maintainer Mikael Nordfeldth <mmn@hethane.se>
*/
class Ostatus_profile extends Managed_DataObject
{
}
}
+ public function isPerson()
+ {
+ return $this->localProfile()->isPerson();
+ }
+
/**
* @return boolean true if this is a remote peopletag
*/
$xml = $entry->getString();
common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml");
- Salmon::post($this->salmonuri, $xml, $actor->getUser());
+ Salmon::post($this->salmonuri, $xml, $actor);
}
/**
public function notifyActivity($entry, Profile $actor)
{
if ($this->salmonuri) {
- return Salmon::post($this->salmonuri, $this->notifyPrepXml($entry), $actor->getUser());
+ return Salmon::post($this->salmonuri, $this->notifyPrepXml($entry), $actor, $this->localProfile());
}
common_debug(__CLASS__.' error: No salmonuri for Ostatus_profile uri: '.$this->uri);
public function notifyDeferred($entry, $actor)
{
if ($this->salmonuri) {
- $data = array('salmonuri' => $this->salmonuri,
- 'entry' => $this->notifyPrepXml($entry),
- 'actor' => $actor->id);
-
- $qm = QueueManager::get();
- return $qm->enqueue($data, 'salmon');
+ try {
+ common_debug("OSTATUS: user {$actor->getNickname()} ({$actor->getID()}) wants to ping {$this->localProfile()->getNickname()} on {$this->salmonuri}");
+ $data = array('salmonuri' => $this->salmonuri,
+ 'entry' => $this->notifyPrepXml($entry),
+ 'actor' => $actor->getID(),
+ 'target' => $this->localProfile()->getID());
+
+ $qm = QueueManager::get();
+ return $qm->enqueue($data, 'salmon');
+ } catch (Exception $e) {
+ common_log(LOG_ERR, 'OSTATUS: Something went wrong when creating a Salmon slap: '._ve($e->getMessage()));
+ return false;
+ }
}
return false;
return;
}
- for ($i = 0; $i < $entries->length; $i++) {
- $entry = $entries->item($i);
- $this->processEntry($entry, $feed, $source);
- }
+ $this->processEntries($entries, $feed, $source);
}
public function processRssFeed(DOMElement $rss, $source)
$items = $channel->getElementsByTagName('item');
- for ($i = 0; $i < $items->length; $i++) {
- $item = $items->item($i);
- $this->processEntry($item, $channel, $source);
+ $this->processEntries($items, $channel, $source);
+ }
+
+ public function processEntries(DOMNodeList $entries, DOMElement $feed, $source)
+ {
+ for ($i = 0; $i < $entries->length; $i++) {
+ $entry = $entries->item($i);
+ try {
+ $this->processEntry($entry, $feed, $source);
+ } catch (AlreadyFulfilledException $e) {
+ common_debug('We already had this entry: '.$e->getMessage());
+ } catch (ServerException $e) {
+ // FIXME: This should be UnknownUriException and the ActivityUtils:: findLocalObject should only test one URI
+ common_log(LOG_ERR, 'Entry threw exception while processing a feed from '.$source.': '.$e->getMessage());
+ }
}
}
*
* @return Notice Notice representing the new (or existing) activity
*/
- public function processEntry($entry, $feed, $source)
+ public function processEntry(DOMElement $entry, DOMElement $feed, $source)
{
$activity = new Activity($entry, $feed);
return $this->processActivity($activity, $source);
}
// TODO: Make this throw an exception
- public function processActivity($activity, $source)
+ public function processActivity(Activity $activity, $source)
{
$notice = null;
if (Event::handle('StartHandleFeedEntryWithProfile', array($activity, $this->localProfile(), &$notice)) &&
Event::handle('StartHandleFeedEntry', array($activity))) {
- 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:
- $notice = $this->processPost($activity, $source);
- break;
- default:
- // TRANS: Client exception.
- throw new ClientException(_m('Cannot handle that kind of post.'));
- }
- break;
- default:
- common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
- }
+ common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
Event::handle('EndHandleFeedEntry', array($activity));
Event::handle('EndHandleFeedEntryWithProfile', array($activity, $this, $notice));
* @param Activity $activity
* @param string $method 'push' or 'salmon'
* @return mixed saved Notice or false
- * @todo FIXME: Break up this function, it's getting nasty long
*/
public function processPost($activity, $method)
{
- $notice = null;
-
- $profile = ActivityUtils::checkAuthorship($activity, $this->localProfile());
-
- // It's not always an ActivityObject::NOTE, but... let's just say it is.
-
- $note = $activity->objects[0];
-
- // The id URI will be used as a unique identifier 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 = $note->id;
- $dupe = Notice::getKV('uri', $sourceUri);
- if ($dupe instanceof Notice) {
- 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 ($note->link) {
- $sourceUrl = $note->link;
- } else if ($activity->link) {
- $sourceUrl = $activity->link;
- } else if (preg_match('!^https?://!', $note->id)) {
- $sourceUrl = $note->id;
- }
-
- // 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 {
- // @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 = common_purify($sourceContent);
- $content = common_strip_html($rendered);
-
- $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($note->title, $rendered);
- $summary = common_strip_html($note->summary);
- 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) . '">' .
- '…' .
- '</a>';
- }
- }
-
- $options = array('is_local' => Notice::REMOTE,
- 'url' => $sourceUrl,
- 'uri' => $sourceUri,
- 'rendered' => $rendered,
- 'replies' => array(),
- 'groups' => array(),
- 'peopletags' => array(),
- 'tags' => array(),
- 'urls' => array());
+ $actor = ActivityUtils::checkAuthorship($activity, $this->localProfile());
- // Check for optional attributes...
-
- if (!empty($activity->time)) {
- $options['created'] = common_sql_date($activity->time);
- }
-
- if ($activity->context) {
- // TODO: context->attention
- list($options['groups'], $options['replies'])
- = self::filterAttention($profile, $activity->context->attention);
-
- // Maintain direct reply associations
- // @todo 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;
- }
- }
- if (!empty($activity->context->conversation)) {
- // we store the URI here, Notice class can look it up later
- $options['conversation'] = $activity->context->conversation;
- }
-
- $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;
- }
+ $options = array('is_local' => Notice::REMOTE);
try {
- $saved = Notice::saveNew($profile->id,
- $content,
- 'ostatus',
- $options);
- if ($saved instanceof Notice) {
- Ostatus_source::saveNew($saved, $this, $method);
- if (!empty($attachment)) {
- File_to_post::processNew($attachment->id, $saved->id);
- }
- }
+ $stored = Notice::saveActivity($activity, $actor, $options);
+ Ostatus_source::saveNew($stored, $this, $method);
} catch (Exception $e) {
common_log(LOG_ERR, "OStatus save of remote message $sourceUri failed: " . $e->getMessage());
throw $e;
}
- common_log(LOG_INFO, "OStatus saved remote message $sourceUri as notice id $saved->id");
- return $saved;
+ return $stored;
}
/**
// Check if we have a non-canonical URL
- $finalUrl = $response->getUrl();
+ $finalUrl = $response->getEffectiveUrl();
if ($finalUrl != $profile_url) {
}
}
- // Try to get some hCard data
+ if (in_array(
+ preg_replace('/\s*;.*$/', '', $response->getHeader('Content-Type')),
+ array('application/rss+xml', 'application/atom+xml', 'application/xml', 'text/xml'))
+ ) {
+ $hints['feedurl'] = $response->getEffectiveUrl();
+ } else {
+ // Try to get some hCard data
- $body = $response->getBody();
+ $body = $response->getBody();
- $hcardHints = DiscoveryHints::hcardHints($body, $finalUrl);
+ $hcardHints = DiscoveryHints::hcardHints($body, $finalUrl);
- if (!empty($hcardHints)) {
- $hints = array_merge($hints, $hcardHints);
+ if (!empty($hcardHints)) {
+ $hints = array_merge($hints, $hcardHints);
+ }
}
// Check if they've got an LRDD header
}
}
+ $obj = ActivityUtils::getFeedAuthor($feedEl);
+
// @todo FIXME: We should check whether this feed has elements
// with different <author> or <dc:creator> elements, and... I dunno.
// Do something about that.
- $obj = ActivityObject::fromRssChannel($feedEl);
+ if(empty($obj)) { $obj = ActivityObject::fromRssChannel($feedEl); }
return self::ensureActivityObjectProfile($obj, $hints);
}
try {
$this->updateAvatar($avatar);
} catch (Exception $ex) {
- common_log(LOG_WARNING, "Exception saving OStatus profile avatar: " . $ex->getMessage());
+ common_log(LOG_WARNING, "Exception updating OStatus profile avatar: " . $ex->getMessage());
}
}
}
throw new Exception(_m('Not a valid webfinger address.'));
}
- $hints = array('webfinger' => $addr);
-
- $dhints = DiscoveryHints::fromXRD($xrd);
-
- $hints = array_merge($hints, $dhints);
+ $hints = array_merge(array('webfinger' => $addr),
+ DiscoveryHints::fromXRD($xrd));
// If there's an Hcard, let's grab its info
if (array_key_exists('hcard', $hints)) {
return $oprofile->localProfile();
}
-}
-/**
- * Exception indicating we've got a remote reference to a local user,
- * not a remote user!
- *
- * If we can ue a local profile after all, it's available as $e->profile.
- */
-class OStatusShadowException extends Exception
-{
- public $profile;
+ public function updateUriKeys($profile_uri, array $hints=array())
+ {
+ $orig = clone($this);
- /**
- * @param Profile $profile
- * @param string $message
- */
- function __construct($profile, $message) {
- $this->profile = $profile;
- parent::__construct($message);
+ common_debug('URIFIX These identities both say they are each other: "'.$orig->uri.'" and "'.$profile_uri.'"');
+ $this->uri = $profile_uri;
+
+ if (array_key_exists('feedurl', $hints)) {
+ if (!empty($this->feeduri)) {
+ common_debug('URIFIX Changing FeedSub ['.$feedsub->id.'] feeduri "'.$feedsub->uri.'" to "'.$hints['feedurl']);
+ $feedsub = FeedSub::getKV('uri', $this->feeduri);
+ $feedorig = clone($feedsub);
+ $feedsub->uri = $hints['feedurl'];
+ $feedsub->updateWithKeys($feedorig);
+ } else {
+ common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring feed: '.$hints['feedurl']);
+ FeedSub::ensureFeed($hints['feedurl']);
+ }
+ $this->feeduri = $hints['feedurl'];
+ }
+ if (array_key_exists('salmon', $hints)) {
+ common_debug('URIFIX Changing Ostatus_profile salmonuri from "'.$this->salmonuri.'" to "'.$hints['salmon'].'"');
+ $this->salmonuri = $hints['salmon'];
+ }
+
+ common_debug('URIFIX Updating Ostatus_profile URI for '.$orig->uri.' to '.$this->uri);
+ $this->updateWithKeys($orig); // Will use the PID column(s) in the 'UPDATE ... WHERE [unique selector]'
+
+ common_debug('URIFIX Subscribing/renewing feedsub for Ostatus_profile '.$this->uri);
+ $this->subscribe();
}
}