From 5f9a8ca64bee5247358238c26b4f54003337d11b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 23 Feb 2010 21:11:44 +0000 Subject: [PATCH 01/16] OStatus: accept webfinger addresses as well as profile URLs in the explicit remote subscribe form. --- plugins/OStatus/actions/ostatussub.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index b3569e6951..12832cdcfb 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -288,10 +288,15 @@ class OStatusSubAction extends Action } $this->profile_uri = $profile_uri; - // @fixme validate, normalize bla bla try { - $oprofile = Ostatus_profile::ensureProfile($this->profile_uri); - $this->oprofile = $oprofile; + if (Validate::email($this->profile_uri)) { + $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri); + } else if (Validate::uri($this->profile_uri)) { + $this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri); + } else { + $this->error = _m("Invalid address format."); + return false; + } return true; } catch (FeedSubBadURLException $e) { $this->error = _m('Invalid URL or could not reach server.'); From d6ad7332475f1cc4ab45d55fc04ef491d5f3999d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 23 Feb 2010 21:47:14 +0000 Subject: [PATCH 02/16] OStatus: fixes for link/id and text extraction gets import of Buzz feeds working. --- plugins/OStatus/OStatusPlugin.php | 24 +++++++++++---------- plugins/OStatus/classes/Ostatus_profile.php | 17 ++++++++++++--- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 3a9e77c2a6..724634924d 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -287,13 +287,19 @@ class OStatusPlugin extends Plugin function onStartNoticeSourceLink($notice, &$name, &$url, &$title) { if ($notice->source == 'ostatus') { - $bits = parse_url($notice->uri); - $domain = $bits['host']; + if ($notice->url) { + $bits = parse_url($notice->url); + $domain = $bits['host']; + if (substr($domain, 0, 4) == 'www.') { + $name = substr($domain, 4); + } else { + $name = $domain; + } - $name = $domain; - $url = $notice->uri; - $title = sprintf(_m("Sent from %s via OStatus"), $domain); - return false; + $url = $notice->url; + $title = sprintf(_m("Sent from %s via OStatus"), $domain); + return false; + } } } @@ -509,12 +515,8 @@ class OStatusPlugin extends Plugin $oprofile = Ostatus_profile::staticGet('group_id', $group->id); if ($oprofile) { // Drop the PuSH subscription if there are no other subscribers. + $oprofile->garbageCollect(); - $members = $group->getMembers(0, 1); - if ($members->N == 0) { - common_log(LOG_INFO, "Unsubscribing from now-unused group feed $oprofile->feeduri"); - $oprofile->unsubscribe(); - } $member = Profile::staticGet($user->id); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 91b957fa23..4998809bc8 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -556,17 +556,28 @@ class Ostatus_profile extends Memcached_DataObject if ($activity->object->link) { $sourceUrl = $activity->object->link; + } else if ($activity->link) { + $sourceUrl = $activity->link; } else if (preg_match('!^https?://!', $activity->object->id)) { $sourceUrl = $activity->object->id; } - // @fixme sanitize and save HTML content if available + // Get (safe!) HTML and text versions of the content - $content = $activity->object->title; + require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php'); + + $html = $activity->object->content; + + $purifier = new HTMLPurifier(); + + $rendered = $purifier->purify($html); + + $content = html_entity_decode(strip_tags($rendered)); $params = array('is_local' => Notice::REMOTE_OMB, 'url' => $sourceUrl, - 'uri' => $sourceUri); + 'uri' => $sourceUri, + 'rendered' => $rendered); $location = $activity->context->location; From fa178a8aa7d6e5ade76eef12ac0ca49aa10f5cdc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 23 Feb 2010 14:26:34 -0800 Subject: [PATCH 03/16] Add poco:displayName to Atom output for person object --- lib/activity.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/activity.php b/lib/activity.php index 04c57c561c..853741a9a1 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -104,6 +104,7 @@ class PoCo function __construct($profile) { $this->preferredUsername = $profile->nickname; + $this->displayName = $profile->getBestName(); $this->note = $profile->bio; $this->address = new PoCoAddress($profile->location); @@ -129,6 +130,12 @@ class PoCo $this->preferredUsername ); + $xs->element( + 'poco:displayName', + null, + $this->displayName + ); + if (!empty($this->note)) { $xs->element('poco:note', null, $this->note); } From 391b45949f6fabef0427aa99d4123fe6ef5ef49d Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 23 Feb 2010 18:25:31 -0500 Subject: [PATCH 04/16] adding xfn, foaf and hcard rel's to our webfinger output --- plugins/OStatus/actions/webfinger.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/webfinger.php index fbbd8d0397..34336a9039 100644 --- a/plugins/OStatus/actions/webfinger.php +++ b/plugins/OStatus/actions/webfinger.php @@ -65,6 +65,21 @@ class WebfingerAction extends Action 'format' => 'atom')), 'type' => 'application/atom+xml'); + // hCard + $xrd->links[] = array('rel' => 'http://microformats.org/profile/hcard', + 'type' => 'text/html', + 'href' => common_profile_url($nick)); + + // XFN + $xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11', + 'type' => 'text/html', + 'href' => common_profile_url($nick)); + // FOAF + $xrd->links[] = array('rel' => 'describedby', + 'type' => 'application/rdf+xml', + 'href' => common_local_url('foaf', + array('nickname' => $nick))); + $salmon_url = common_local_url('salmon', array('id' => $this->user->id)); From 25864aea9dd81ebcdb8c7386af3662eda800ccc3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 24 Feb 2010 00:59:00 +0100 Subject: [PATCH 05/16] Using the default abbr class pattern for geo microformats instead of the shorthand that I've proposed at http://microformats.org/wiki/geo-brainstorming#latitude_longitude_shorthand_and_geo_link If anyone wants to pick up on where the discussion was left off or get more implementation support by other sites and software, and be recognized by parsers, I'd be happy to go back to the shorthand. Because you know, it actually makes a lot of sense. --- lib/noticelist.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/noticelist.php b/lib/noticelist.php index 837cb90faa..dcf17be08c 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -438,14 +438,15 @@ class NoticeListItem extends Widget $this->out->text(_('at')); $this->out->text(' '); if (empty($url)) { - $this->out->element('span', array('class' => 'geo', + $this->out->element('abbr', array('class' => 'geo', 'title' => $latlon), $name); } else { - $this->out->element('a', array('class' => 'geo', - 'title' => $latlon, - 'href' => $url), + $this->out->elementStart('a', array('href' => $url)); + $this->out->element('abbr', array('class' => 'geo', + 'title' => $latlon), $name); + $this->out->elementEnd('a'); } $this->out->elementEnd('span'); } From 2aaf8d4e308d49d072a6c43e43cb99c373deca2e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Feb 2010 01:09:12 +0000 Subject: [PATCH 06/16] Add class and (if present) id to DB_DataObject error exceptions; often they're VERRRRRY vague, and it helps to know what type of item is failing! --- classes/Memcached_DataObject.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 40576dc717..bc4c3a000c 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -501,7 +501,11 @@ class Memcached_DataObject extends Safe_DataObject function raiseError($message, $type = null, $behaviour = null) { - throw new ServerException("DB_DataObject error [$type]: $message"); + $id = get_class($this); + if ($this->id) { + $id .= ':' . $this->id; + } + throw new ServerException("[$id] DB_DataObject error [$type]: $message"); } static function cacheGet($keyPart) From 584b87cfe57bd2d683101194040e3563f0706536 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Feb 2010 01:09:52 +0000 Subject: [PATCH 07/16] OStatus: consolidate the low-level notice save code between Salmon and PuSH input paths. Validation etc remains at higher levels. --- plugins/OStatus/OStatusPlugin.php | 2 +- plugins/OStatus/classes/Ostatus_profile.php | 214 +++++++++++--------- plugins/OStatus/lib/salmonaction.php | 50 +---- 3 files changed, 125 insertions(+), 141 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 724634924d..35f9529355 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -314,7 +314,7 @@ class OStatusPlugin extends Plugin { $oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri); if ($oprofile) { - $oprofile->processFeed($feed); + $oprofile->processFeed($feed, 'push'); } else { common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri"); } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 4998809bc8..6beaf0f5d0 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -488,7 +488,7 @@ class Ostatus_profile extends Memcached_DataObject * * @param DOMDocument $feed */ - public function processFeed($feed) + public function processFeed($feed, $source) { $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry'); if ($entries->length == 0) { @@ -498,7 +498,7 @@ class Ostatus_profile extends Memcached_DataObject for ($i = 0; $i < $entries->length; $i++) { $entry = $entries->item($i); - $this->processEntry($entry, $feed); + $this->processEntry($entry, $feed, $source); } } @@ -508,15 +508,12 @@ class Ostatus_profile extends Memcached_DataObject * @param DOMElement $entry * @param DOMElement $feed for context */ - protected function processEntry($entry, $feed) + public function processEntry($entry, $feed, $source) { $activity = new Activity($entry, $feed); - $debug = var_export($activity, true); - common_log(LOG_DEBUG, $debug); - if ($activity->verb == ActivityVerb::POST) { - $this->processPost($activity); + $this->processPost($activity, $source); } else { common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb"); } @@ -525,35 +522,47 @@ class Ostatus_profile extends Memcached_DataObject /** * Process an incoming post activity from this remote feed. * @param Activity $activity + * @param string $method 'push' or 'salmon' + * @return mixed saved Notice or false * @fixme break up this function, it's getting nasty long */ - protected function processPost($activity) + public function processPost($activity, $method) { if ($this->isGroup()) { + // A group feed will contain posts from multiple authors. // @fixme validate these profiles in some way! $oprofile = self::ensureActorProfile($activity); + if ($oprofile->isGroup()) { + // Groups can't post notices in StatusNet. + common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: $oprofile->uri in feed from $this->uri"); + return false; + } } else { + // Individual user feeds may contain only posts from themselves. + // Authorship is validated against the profile URI on upper layers, + // through PuSH setup or Salmon signature checks. $actorUri = self::getActorProfileURI($activity); if ($actorUri == $this->uri) { // @fixme check if profile info has changed and update it } else { - // @fixme drop or reject the messages once we've got the canonical profile URI recorded sanely - common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->uri"); - //return; + common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri"); + return false; } $oprofile = $this; } + + // 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->object->id; - $dupe = Notice::staticGet('uri', $sourceUri); - if ($dupe) { common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri"); - return; + return false; } + // We'll also want to save a web link to the original notice, if provided. $sourceUrl = null; - if ($activity->object->link) { $sourceUrl = $activity->object->link; } else if ($activity->link) { @@ -563,103 +572,126 @@ class Ostatus_profile extends Memcached_DataObject } // Get (safe!) HTML and text versions of the content - - require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php'); - - $html = $activity->object->content; - - $purifier = new HTMLPurifier(); - - $rendered = $purifier->purify($html); - + $rendered = $this->purify($activity->object->content); $content = html_entity_decode(strip_tags($rendered)); - $params = array('is_local' => Notice::REMOTE_OMB, + $options = array('is_local' => Notice::REMOTE_OMB, 'url' => $sourceUrl, 'uri' => $sourceUri, - 'rendered' => $rendered); + 'rendered' => $rendered, + 'replies' => array(), + 'groups' => array()); - $location = $activity->context->location; + // Check for optional attributes... - if ($location) { - $params['lat'] = $location->lat; - $params['lon'] = $location->lon; - if ($location->location_id) { - $params['location_ns'] = $location->location_ns; - $params['location_id'] = $location->location_id; - } + if (!empty($activity->time)) { + $options['created'] = common_sql_date($activity->time); } - $profile = $oprofile->localProfile(); - $params['groups'] = array(); - $params['replies'] = array(); if ($activity->context) { - foreach ($activity->context->attention as $recipient) { - $roprofile = Ostatus_profile::staticGet('uri', $recipient); - if ($roprofile) { - if ($roprofile->isGroup()) { - // Deliver to local recipients of this remote group. - // @fixme sender verification? - $params['groups'][] = $roprofile->group_id; - continue; - } else { - // Delivery to remote users is the source service's job. - continue; - } + // 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; } - - $user = User::staticGet('uri', $recipient); - if ($user) { - // An @-reply directed to a local user. - // @fixme sender verification, spam etc? - $params['replies'][] = $recipient; - continue; - } - - // @fixme we need a uri on user_group - // $group = User_group::staticGet('uri', $recipient); - $template = common_local_url('groupbyid', array('id' => '31337')); - $template = preg_quote($template, '/'); - $template = str_replace('31337', '(\d+)', $template); - common_log(LOG_DEBUG, $template); - if (preg_match("/$template/", $recipient, $matches)) { - $id = $matches[1]; - $group = User_group::staticGet('id', $id); - if ($group) { - // Deliver to all members of this local group. - // @fixme sender verification? - if ($profile->isMember($group)) { - common_log(LOG_DEBUG, "delivering to group $id $group->nickname"); - $params['groups'][] = $group->id; - } else { - common_log(LOG_DEBUG, "not delivering to group $id $group->nickname because sender $profile->nickname is not a member"); - } - continue; - } else { - common_log(LOG_DEBUG, "not delivering to missing group $id"); - } - } else { - common_log(LOG_DEBUG, "not delivering to groups for $recipient"); + } + + $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; } } } try { - $saved = Notice::saveNew($profile->id, + $saved = Notice::saveNew($oprofile->profile_id, $content, 'ostatus', - $params); + $options); + if ($saved) { + Ostatus_source::saveNew($saved, $this, $method); + } } catch (Exception $e) { - common_log(LOG_ERR, "Failed saving notice entry for $sourceUri: " . $e->getMessage()); - return; + 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; + } - // Record which feed this came through... - try { - Ostatus_source::saveNew($saved, $this, 'push'); - } catch (Exception $e) { - common_log(LOG_ERR, "Failed saving ostatus_source entry for $saved->notice_id: " . $e->getMessage()); + /** + * Clean up HTML + */ + protected function purify($html) + { + // @fixme disable caching or set a sane temp dir + require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php'); + $purifier = new HTMLPurifier(); + return $purifier->purify($html); + } + + /** + * Filters a list of recipient ID URIs to just those for local delivery. + * @param Ostatus_profile local profile of sender + * @param array in/out &$attention_uris set of URIs, will be pruned on output + * @return array of group IDs + */ + protected function filterReplies($sender, &$attention_uris) + { + $groups = array(); + $replies = array(); + foreach ($attention_uris as $recipient) { + // Is the recipient a local user? + $user = User::staticGet('uri', $recipient); + if ($user) { + // @fixme sender verification, spam etc? + $replies[] = $recipient; + continue; + } + + // Is the recipient a remote group? + $oprofile = Ostatus_profile::staticGet('uri', $recipient); + if ($oprofile) { + if ($oprofile->isGroup()) { + // Deliver to local members of this remote group. + // @fixme sender verification? + $groups[] = $oprofile->group_id; + } + continue; + } + + // Is the recipient a local group? + // @fixme we need a uri on user_group + // $group = User_group::staticGet('uri', $recipient); + $template = common_local_url('groupbyid', array('id' => '31337')); + $template = preg_quote($template, '/'); + $template = str_replace('31337', '(\d+)', $template); + if (preg_match("/$template/", $recipient, $matches)) { + $id = $matches[1]; + $group = User_group::staticGet('id', $id); + if ($group) { + // Deliver to all members of this local group if allowed. + if ($sender->localProfile()->isMember($group)) { + $groups[] = $group->id; + } + continue; + } + } } + $attention_uris = $replies; + return $groups; } /** diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 83cf0b8f8a..9aac2ed52f 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -185,54 +185,6 @@ class SalmonAction extends Action function saveNotice() { $oprofile = $this->ensureProfile(); - - // Get (safe!) HTML and text versions of the content - - require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php'); - - $html = $this->act->object->content; - - $purifier = new HTMLPurifier(); - - $rendered = $purifier->purify($html); - - $content = html_entity_decode(strip_tags($rendered)); - - $options = array('is_local' => Notice::REMOTE_OMB, - 'uri' => $this->act->object->id, - 'url' => $this->act->object->link, - 'rendered' => $rendered, - 'replies' => $this->act->context->attention); - - if (!empty($this->act->context->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 (!empty($this->act->context->replyToID)) { - $orig = Notice::staticGet('uri', - $this->act->context->replyToID); - if (!empty($orig)) { - $options['reply_to'] = $orig->id; - } - } - - if (!empty($this->act->time)) { - $options['created'] = common_sql_date($this->act->time); - } - - $saved = Notice::saveNew($oprofile->profile_id, - $content, - 'ostatus+salmon', - $options); - - // Record that this was saved through a validated Salmon source - // @fixme actually do the signature validation! - Ostatus_source::saveNew($saved, $oprofile, 'salmon'); - return $saved; + return $oprofile->processPost($this->act, 'salmon'); } } From 2e58802cc9959763f28e2f43c8e0cd0dbe7bcd8e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Feb 2010 02:19:13 +0000 Subject: [PATCH 08/16] OStatus: fix group delivery, send reply/group Salmon pings from background. --- plugins/OStatus/OStatusPlugin.php | 28 ++---- plugins/OStatus/actions/groupsalmon.php | 9 +- plugins/OStatus/classes/Ostatus_profile.php | 15 ++- ...euehandler.php => ostatusqueuehandler.php} | 95 +++++++++++++------ plugins/OStatus/lib/salmonoutqueuehandler.php | 44 +++++++++ 5 files changed, 140 insertions(+), 51 deletions(-) rename plugins/OStatus/lib/{hubdistribqueuehandler.php => ostatusqueuehandler.php} (65%) create mode 100644 plugins/OStatus/lib/salmonoutqueuehandler.php diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 35f9529355..9376c048de 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -78,11 +78,16 @@ class OStatusPlugin extends Plugin */ function onEndInitializeQueueManager(QueueManager $qm) { + // Prepare outgoing distributions after notice save. + $qm->connect('ostatus', 'OStatusQueueHandler'); + // Outgoing from our internal PuSH hub $qm->connect('hubverify', 'HubVerifyQueueHandler'); - $qm->connect('hubdistrib', 'HubDistribQueueHandler'); $qm->connect('hubout', 'HubOutQueueHandler'); + // Outgoing Salmon replies (when we don't need a return value) + $qm->connect('salmonout', 'SalmonOutQueueHandler'); + // Incoming from a foreign PuSH hub $qm->connect('pushinput', 'PushInputQueueHandler'); return true; @@ -93,7 +98,7 @@ class OStatusPlugin extends Plugin */ function onStartEnqueueNotice($notice, &$transports) { - $transports[] = 'hubdistrib'; + $transports[] = 'ostatus'; return true; } @@ -199,25 +204,6 @@ class OStatusPlugin extends Plugin function onEndNoticeSave($notice) { - $mentioned = $notice->getReplies(); - - foreach ($mentioned as $profile_id) { - - $oprofile = Ostatus_profile::staticGet('profile_id', $profile_id); - - if (!empty($oprofile) && !empty($oprofile->salmonuri)) { - - common_log(LOG_INFO, "Sending notice '{$notice->uri}' to remote profile '{$oprofile->uri}'."); - - // FIXME: this needs to go out in a queue handler - - $xml = ''; - $xml .= $notice->asAtomEntry(true, true); - - $salmon = new Salmon(); - $salmon->post($oprofile->salmonuri, $xml); - } - } } /** diff --git a/plugins/OStatus/actions/groupsalmon.php b/plugins/OStatus/actions/groupsalmon.php index 2e4fe94436..29377b5fa0 100644 --- a/plugins/OStatus/actions/groupsalmon.php +++ b/plugins/OStatus/actions/groupsalmon.php @@ -46,6 +46,11 @@ class GroupsalmonAction extends SalmonAction $this->clientError(_('No such group.')); } + $oprofile = Ostatus_profile::staticGet('group_id', $id); + if ($oprofile) { + $this->clientError(_m("Can't accept remote posts for a remote group.")); + } + return true; } @@ -74,13 +79,13 @@ class GroupsalmonAction extends SalmonAction throw new ClientException("Not to the attention of anyone."); } else { $uri = common_local_url('groupbyid', array('id' => $this->group->id)); - if (!in_array($context->attention, $uri)) { + if (!in_array($uri, $context->attention)) { throw new ClientException("Not to the attention of this group."); } } $profile = $this->ensureProfile(); - // @fixme save the post + $this->saveNotice(); } /** diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 6beaf0f5d0..82dbf773dd 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -650,6 +650,7 @@ class Ostatus_profile extends Memcached_DataObject */ protected function filterReplies($sender, &$attention_uris) { + common_log(LOG_DEBUG, "Original reply recipients: " . implode(', ', $attention_uris)); $groups = array(); $replies = array(); foreach ($attention_uris as $recipient) { @@ -668,6 +669,8 @@ class Ostatus_profile extends Memcached_DataObject // Deliver to local members of this remote group. // @fixme sender verification? $groups[] = $oprofile->group_id; + } else { + common_log(LOG_DEBUG, "Skipping reply to remote profile $recipient"); } continue; } @@ -683,14 +686,24 @@ class Ostatus_profile extends Memcached_DataObject $group = User_group::staticGet('id', $id); if ($group) { // Deliver to all members of this local group if allowed. - if ($sender->localProfile()->isMember($group)) { + $profile = $sender->localProfile(); + if ($profile->isMember($group)) { $groups[] = $group->id; + } else { + common_log(LOG_DEBUG, "Skipping reply to local group $group->nickname as sender $profile->id is not a member"); } continue; + } else { + common_log(LOG_DEBUG, "Skipping reply to bogus group $recipient"); } } + + common_log(LOG_DEBUG, "Skipping reply to unrecognized profile $recipient"); + } $attention_uris = $replies; + common_log(LOG_DEBUG, "Local reply recipients: " . implode(', ', $replies)); + common_log(LOG_DEBUG, "Local group recipients: " . implode(', ', $groups)); return $groups; } diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/ostatusqueuehandler.php similarity index 65% rename from plugins/OStatus/lib/hubdistribqueuehandler.php rename to plugins/OStatus/lib/ostatusqueuehandler.php index c2bd630f9c..c1e50bffa1 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/ostatusqueuehandler.php @@ -18,46 +18,89 @@ */ /** - * Send a PuSH subscription verification from our internal hub. - * Queue up final distribution for - * @package Hub + * Prepare PuSH and Salmon distributions for an outgoing message. + * + * @package OStatusPlugin * @author Brion Vibber */ -class HubDistribQueueHandler extends QueueHandler +class OStatusQueueHandler extends QueueHandler { function transport() { - return 'hubdistrib'; + return 'ostatus'; } function handle($notice) { assert($notice instanceof Notice); - $this->pushUser($notice); + $this->notice = $notice; + $this->user = User::staticGet($notice->profile_id); + + $this->pushUser(); + foreach ($notice->getGroups() as $group) { - $this->pushGroup($notice, $group->id); + $oprofile = Ostatus_profile::staticGet('group_id', $group->id); + if ($oprofile) { + $this->pingReply($oprofile); + } else { + $this->pushGroup($group->id); + } } + + foreach ($notice->getReplies() as $profile_id) { + $oprofile = Ostatus_profile::staticGet('profile_id', $profile_id); + if ($oprofile) { + $this->pingReply($oprofile); + } + } + return true; } - - function pushUser($notice) + + function pushUser() { - // See if there's any PuSH subscriptions, including OStatus clients. - // @fixme handle group subscriptions as well - // http://identi.ca/api/statuses/user_timeline/1.atom - $feed = common_local_url('ApiTimelineUser', - array('id' => $notice->profile_id, - 'format' => 'atom')); - $this->pushFeed($feed, array($this, 'userFeedForNotice'), $notice); + if ($this->user) { + // For local posts, ping the PuSH hub to update their feed. + // http://identi.ca/api/statuses/user_timeline/1.atom + $feed = common_local_url('ApiTimelineUser', + array('id' => $this->user->id, + 'format' => 'atom')); + $this->pushFeed($feed, array($this, 'userFeedForNotice')); + } } - function pushGroup($notice, $group_id) + function pushGroup($group_id) { + // For a local group, ping the PuSH hub to update its feed. + // Updates may come from either a local or a remote user. $feed = common_local_url('ApiTimelineGroup', array('id' => $group_id, 'format' => 'atom')); - $this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id, $notice); + $this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id); + } + + function pingReply($oprofile) + { + if ($this->user) { + if (!empty($oprofile->salmonuri)) { + // For local posts, send a Salmon ping to the mentioned + // remote user or group. + // @fixme as an optimization we can skip this if the + // remote profile is subscribed to the author. + + common_log(LOG_INFO, "Prepping to send notice '{$this->notice->uri}' to remote profile '{$oprofile->uri}'."); + + $xml = ''; + $xml .= $this->notice->asAtomEntry(true, true); + + $data = array('salmonuri' => $oprofile->salmonuri, + 'entry' => $xml); + + $qm = QueueManager::get(); + $qm->enqueue($data, 'salmonout'); + } + } } /** @@ -122,7 +165,6 @@ class HubDistribQueueHandler extends QueueHandler function pushFeedInternal($atom, $sub) { common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic"); - $qm = QueueManager::get(); while ($sub->fetch()) { $sub->distribute($atom); } @@ -130,20 +172,19 @@ class HubDistribQueueHandler extends QueueHandler /** * Build a single-item version of the sending user's Atom feed. - * @param Notice $notice * @return string */ - function userFeedForNotice($notice) + function userFeedForNotice() { // @fixme this feels VERY hacky... // should probably be a cleaner way to do it ob_start(); $api = new ApiTimelineUserAction(); - $api->prepare(array('id' => $notice->profile_id, + $api->prepare(array('id' => $this->notice->profile_id, 'format' => 'atom', - 'max_id' => $notice->id, - 'since_id' => $notice->id - 1)); + 'max_id' => $this->notice->id, + 'since_id' => $this->notice->id - 1)); $api->showTimeline(); $feed = ob_get_clean(); @@ -155,7 +196,7 @@ class HubDistribQueueHandler extends QueueHandler return $feed; } - function groupFeedForNotice($group_id, $notice) + function groupFeedForNotice($group_id) { // @fixme this feels VERY hacky... // should probably be a cleaner way to do it @@ -164,8 +205,8 @@ class HubDistribQueueHandler extends QueueHandler $api = new ApiTimelineGroupAction(); $args = array('id' => $group_id, 'format' => 'atom', - 'max_id' => $notice->id, - 'since_id' => $notice->id - 1); + 'max_id' => $this->notice->id, + 'since_id' => $this->notice->id - 1); $api->prepare($args); $api->handle($args); $feed = ob_get_clean(); diff --git a/plugins/OStatus/lib/salmonoutqueuehandler.php b/plugins/OStatus/lib/salmonoutqueuehandler.php new file mode 100644 index 0000000000..536ff94af6 --- /dev/null +++ b/plugins/OStatus/lib/salmonoutqueuehandler.php @@ -0,0 +1,44 @@ +. + */ + +/** + * Send a Salmon notification in the background. + * @package OStatusPlugin + * @author Brion Vibber + */ +class SalmonOutQueueHandler extends QueueHandler +{ + function transport() + { + return 'salmonout'; + } + + function handle($data) + { + assert(is_array($data)); + assert(is_string($data['salmonuri'])); + assert(is_string($data['entry'])); + + $salmon = new Salmon(); + $salmon->post($data['salmonuri'], $data['entry']); + + // @fixme detect failure and attempt to resend + return true; + } +} From 3a3af6782a82ca3512680a276b76d1d10de47d94 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 23 Feb 2010 22:35:48 -0800 Subject: [PATCH 09/16] Add PoCo parsing and some other fixes. --- lib/activity.php | 162 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 132 insertions(+), 30 deletions(-) diff --git a/lib/activity.php b/lib/activity.php index 853741a9a1..5cbab8d5f3 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -34,6 +34,7 @@ if (!defined('STATUSNET')) { class PoCoURL { + const URLS = 'urls'; const TYPE = 'type'; const VALUE = 'value'; const PRIMARY = 'primary'; @@ -55,7 +56,7 @@ class PoCoURL $xs->elementStart('poco:urls'); $xs->element('poco:type', null, $this->type); $xs->element('poco:value', null, $this->value); - if ($this->primary) { + if (!empty($this->primary)) { $xs->element('poco:primary', null, 'true'); } $xs->elementEnd('poco:urls'); @@ -70,21 +71,19 @@ class PoCoAddress public $formatted; - function __construct($formatted) - { - if (empty($formatted)) { - return null; - } - $this->formatted = $formatted; - } + // @todo Other address fields function asString() { - $xs = new XMLStringer(true); - $xs->elementStart('poco:address'); - $xs->element('poco:formatted', null, $this->formatted); - $xs->elementEnd('poco:address'); - return $xs->getString(); + if (!empty($this->formatted)) { + $xs = new XMLStringer(true); + $xs->elementStart('poco:address'); + $xs->element('poco:formatted', null, $this->formatted); + $xs->elementEnd('poco:address'); + return $xs->getString(); + } + + return null; } } @@ -92,26 +91,117 @@ class PoCo { const NS = 'http://portablecontacts.net/spec/1.0'; - const USERNAME = 'preferredUsername'; - const NOTE = 'note'; - const URLS = 'urls'; + const USERNAME = 'preferredUsername'; + const DISPLAYNAME = 'displayName'; + const NOTE = 'note'; public $preferredUsername; + public $displayName; public $note; public $address; public $urls = array(); - function __construct($profile) + function __construct($element = null) { - $this->preferredUsername = $profile->nickname; - $this->displayName = $profile->getBestName(); + if (empty($element)) { + return; + } - $this->note = $profile->bio; - $this->address = new PoCoAddress($profile->location); + $this->preferredUsername = ActivityUtils::childContent( + $element, + self::USERNAME, + self::NS + ); + + $this->displayName = ActivityUtils::childContent( + $element, + self::DISPLAYNAME, + self::NS + ); + + $this->note = ActivityUtils::childContent( + $element, + self::NOTE, + self::NS + ); + + $this->address = $this->_getAddress($element); + $this->urls = $this->_getURLs($element); + } + + private function _getURLs($element) + { + $urlEls = $element->getElementsByTagnameNS(self::NS, PoCoURL::URLS); + $urls = array(); + + foreach ($urlEls as $urlEl) { + + $type = ActivityUtils::childContent( + $urlEl, + PoCoURL::TYPE, + PoCo::NS + ); + + $value = ActivityUtils::childContent( + $urlEl, + PoCoURL::VALUE, + PoCo::NS + ); + + $primary = ActivityUtils::childContent( + $urlEl, + PoCoURL::PRIMARY, + PoCo::NS + ); + + array_push($urls, new PoCoURL($type, $value, $primary)); + } + return $urls; + } + + private function _getAddress($element) + { + $addressEl = ActivityUtils::child( + $element, + PoCoAddress::ADDRESS, + PoCo::NS + ); + + $formatted = ActivityUtils::childContent( + $addressEl, + PoCoAddress::FORMATTED, + self::NS + ); + + if (!empty($formatted)) { + $address = new PoCoAddress(); + $address->formatted = $formatted; + return $address; + } + + return null; + } + + function fromProfile($profile) + { + if (empty($profile)) { + return null; + } + + $poco = new PoCo(); + + $poco->preferredUsername = $profile->nickname; + $poco->displayName = $profile->getBestName(); + + $poco->note = $profile->bio; + + $paddy = new PoCoAddress(); + $paddy->formatted = $profile->location; + $poco->address = $paddy; if (!empty($profile->homepage)) { array_push( - $this->urls, + $poco->urls, new PoCoURL( 'homepage', $profile->homepage, @@ -119,6 +209,8 @@ class PoCo ) ); } + + return $poco; } function asString() @@ -381,6 +473,8 @@ class ActivityObject public $source; public $avatar; public $geopoint; + public $poco; + public $displayName; /** * Constructor @@ -433,7 +527,6 @@ class ActivityObject $this->link = ActivityUtils::getPermalink($element); - // XXX: grab PoCo stuff } // Some per-type attributes... @@ -441,8 +534,9 @@ class ActivityObject $this->displayName = $this->title; // @fixme we may have multiple avatars with different resolutions specified - $this->avatar = ActivityUtils::getLink($element, 'avatar'); - $this->nickname = ActivityUtils::childContent($element, PoCo::USERNAME, PoCo::NS); + $this->avatar = ActivityUtils::getLink($element, 'avatar'); + + $this->poco = new PoCo($element); } } @@ -497,7 +591,7 @@ class ActivityObject $object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon; } - $object->poco = new PoCo($profile); + $object->poco = PoCo::fromProfile($profile); return $object; } @@ -526,11 +620,19 @@ class ActivityObject } if (!empty($this->link)) { - $xs->element('link', array('rel' => 'alternate', 'type' => 'text/html'), - $this->link); + $xs->element( + 'link', + array( + 'rel' => 'alternate', + 'type' => 'text/html', + 'href' => $this->link + ), + null + ); } - if ($this->type == ActivityObject::PERSON) { + if ($this->type == ActivityObject::PERSON + || $this->type == ActivityObject::GROUP) { $xs->element( 'link', array( 'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype, @@ -539,7 +641,7 @@ class ActivityObject ? Avatar::defaultImage(AVATAR_PROFILE_SIZE) : $this->avatar->displayUrl() ), - '' + null ); } From 618ce6a855330f424d54c3dedf10acb60f7e3001 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 23 Feb 2010 23:58:21 -0800 Subject: [PATCH 10/16] - Move ActivityParseTests to core - Add test for Portable Contacts stuff --- .../tests => tests}/ActivityParseTests.php | 159 +++++++++++++++--- 1 file changed, 138 insertions(+), 21 deletions(-) rename {plugins/OStatus/tests => tests}/ActivityParseTests.php (57%) diff --git a/plugins/OStatus/tests/ActivityParseTests.php b/tests/ActivityParseTests.php similarity index 57% rename from plugins/OStatus/tests/ActivityParseTests.php rename to tests/ActivityParseTests.php index d7305dedea..5de97d2e2e 100644 --- a/plugins/OStatus/tests/ActivityParseTests.php +++ b/tests/ActivityParseTests.php @@ -7,11 +7,10 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { // XXX: we should probably have some common source for this stuff -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); define('STATUSNET', true); require_once INSTALLDIR . '/lib/common.php'; -require_once INSTALLDIR . '/plugins/OStatus/lib/activity.php'; class ActivityParseTests extends PHPUnit_Framework_TestCase { @@ -97,6 +96,45 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase $this->assertFalse(empty($act->actor)); } + + public function testExample5() + { + global $_example5; + $dom = DOMDocument::loadXML($_example5); + + $feed = $dom->documentElement; + + // @todo Test feed elements + + $entries = $feed->getElementsByTagName('entry'); + $entry = $entries->item(0); + + $act = new Activity($entry, $feed); + + // Post + $this->assertEquals($act->verb, ActivityVerb::POST); + $this->assertFalse(empty($act->context)); + + // Actor w/Portable Contacts stuff + $this->assertFalse(empty($act->actor)); + $this->assertEquals($act->actor->type, ActivityObject::PERSON); + $this->assertEquals($act->actor->title, 'Test User'); + $this->assertEquals($act->actor->id, 'http://example.net/mysite/user/3'); + $this->assertEquals($act->actor->link, 'http://example.net/mysite/testuser'); + $this->assertEquals( + $act->actor->avatar, + 'http://example.net/mysite/avatar/3-96-20100224004207.jpeg' + ); + $this->assertEquals($act->actor->displayName, 'Test User'); + + $poco = $act->actor->poco; + $this->assertEquals($poco->preferredUsername, 'testuser'); + $this->assertEquals($poco->address->formatted, 'San Francisco, CA'); + $this->assertEquals($poco->urls[0]->type, 'homepage'); + $this->assertEquals($poco->urls[0]->value, 'http://example.com/blog.html'); + $this->assertEquals($poco->urls[0]->primary, 'true'); + } + } $_example1 = << - Example Feed - A subtitle. - - - urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6 - 2003-12-13T18:30:02Z - - John Doe - johndoe@example.com - + Example Feed + A subtitle. + + + urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6 + 2003-12-13T18:30:02Z + + John Doe + johndoe@example.com + - - Atom-Powered Robots Run Amok - - - - urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a - 2003-12-13T18:30:02Z - Some text. - + + Atom-Powered Robots Run Amok + + + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2003-12-13T18:30:02Z + Some text. + EXAMPLE3; @@ -207,3 +245,82 @@ $_example4 = << EXAMPLE4; + +$_example5 = << + + 3 + testuser timeline + Updates from testuser on Zach Dev! + http://example.net/mysite/avatar/3-96-20100224004207.jpeg + 2010-02-24T06:38:49+00:00 + + testuser + http://example.net/mysite/user/3 + + + + + + + + + http://activitystrea.ms/schema/1.0/person + http://example.net/mysite/user/3 + Test User + + + 37.7749295 -122.4194155 + +testuser +Test User +Just another test user. + + San Francisco, CA + + + homepage + http://example.com/blog.html + true + + + + + Hey man, is that Freedom Code?! #freedom #hippy + Hey man, is that Freedom Code?! #freedom #hippy + + testuser + http://example.net/mysite/user/3 + + + http://activitystrea.ms/schema/1.0/person + http://example.net/mysite/user/3 + Test User + + + 37.7749295 -122.4194155 + +testuser +Test User +Just another test user. + + San Francisco, CA + + + homepage + http://example.com/blog.html + true + + + + + http://example.net/mysite/notice/7 + 2010-02-24T00:53:06+00:00 + 2010-02-24T00:53:06+00:00 + + Hey man, is that Freedom Code?! #<span class="tag"><a href="http://example.net/mysite/tag/freedom" rel="tag">freedom</a></span> #<span class="tag"><a href="http://example.net/mysite/tag/hippy" rel="tag">hippy</a></span> + 37.8313160 -122.2852473 + + + +EXAMPLE5; From 2466dbfc97e81144d005fa546b387c9747ce00ad Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 24 Feb 2010 14:55:35 +0100 Subject: [PATCH 11/16] Break long strings in tagcloud --- theme/base/css/display.css | 1 + 1 file changed, 1 insertion(+) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 380975e324..52f97f6b12 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1490,6 +1490,7 @@ text-align:center; } .aside .tag-cloud { font-size:0.8em; +word-wrap:break-word; } .tag-cloud li { display:inline; From 8e7606cc8d9003cb5d58410678c2be0f812d0ed1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 24 Feb 2010 15:20:44 +0100 Subject: [PATCH 12/16] Added processing indicator for .form_remote_authorize on ostatussub page --- plugins/OStatus/js/ostatus.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/OStatus/js/ostatus.js b/plugins/OStatus/js/ostatus.js index 1fc44b21b6..3637b8725b 100644 --- a/plugins/OStatus/js/ostatus.js +++ b/plugins/OStatus/js/ostatus.js @@ -123,4 +123,6 @@ SN.Init.Subscribe = function() { $(document).ready(function() { SN.Init.Subscribe(); + + $('.form_remote_authorize').bind('submit', function() { $(this).addClass(SN.C.S.Processing); return true; }); }); From 1f45273d5303e98430a02d4480278c24733a5be9 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 24 Feb 2010 16:35:20 +0100 Subject: [PATCH 13/16] Moved StatusNetInstance into SN in util.js --- js/util.js | 32 +++++++++++++++++++++++++++++++- plugins/OStatus/js/ostatus.js | 32 +++----------------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/js/util.js b/js/util.js index 3623337b9f..78533ab731 100644 --- a/js/util.js +++ b/js/util.js @@ -54,7 +54,8 @@ var SN = { // StatusNet NoticeGeoName: 'notice_data-geo_name', NoticeDataGeo: 'notice_data-geo', NoticeDataGeoCookie: 'notice_data-geo_cookie', - NoticeDataGeoSelected: 'notice_data-geo_selected' + NoticeDataGeoSelected: 'notice_data-geo_selected', + StatusNetInstance:'StatusNetInstance' } }, @@ -670,6 +671,35 @@ var SN = { // StatusNet date.setFullYear(year, month, day); return date; + }, + + StatusNetInstance: { + Set: function(value) { + var SNI = SN.U.StatusNetInstance.Get(); + if (SNI !== null) { + value = $.extend(SNI, value); + } + + $.cookie( + SN.C.S.StatusNetInstance, + JSON.stringify(value), + { + path: '/', + expires: SN.U.GetFullYear(2029, 0, 1) + }); + }, + + Get: function() { + var cookieValue = $.cookie(SN.C.S.StatusNetInstance); + if (cookieValue !== null) { + return JSON.parse(cookieValue); + } + return null; + }, + + Delete: function() { + $.cookie(SN.C.S.StatusNetInstance, null); + } } }, diff --git a/plugins/OStatus/js/ostatus.js b/plugins/OStatus/js/ostatus.js index 3637b8725b..bd29b5c0cf 100644 --- a/plugins/OStatus/js/ostatus.js +++ b/plugins/OStatus/js/ostatus.js @@ -24,35 +24,9 @@ * @note Everything in here should eventually migrate over to /js/util.js's SN. */ -SN.C.S.StatusNetInstance = 'StatusNetInstance'; - -SN.U.StatusNetInstance = { - Set: function(value) { - $.cookie( - SN.C.S.StatusNetInstance, - JSON.stringify(value), - { - path: '/', - expires: SN.U.GetFullYear(2029, 0, 1) - }); - }, - - Get: function() { - var cookieValue = $.cookie(SN.C.S.StatusNetInstance); - if (cookieValue !== null) { - return JSON.parse(cookieValue); - } - return null; - }, - - Delete: function() { - $.cookie(SN.C.S.StatusNetInstance, null); - } -}; - SN.Init.OStatusCookie = function() { if (SN.U.StatusNetInstance.Get() === null) { - SN.U.StatusNetInstance.Set({profile: null}); + SN.U.StatusNetInstance.Set({RemoteProfile: null}); } }; @@ -101,10 +75,10 @@ SN.U.DialogBox = { if (form.attr('id') == 'form_ostatus_connect') { SN.Init.OStatusCookie(); - form.find('#profile').val(SN.U.StatusNetInstance.Get().profile); + form.find('#profile').val(SN.U.StatusNetInstance.Get().RemoteProfile); form.find("[type=submit]").bind('click', function() { - SN.U.StatusNetInstance.Set({profile: form.find('#profile').val()}); + SN.U.StatusNetInstance.Set({RemoteProfile: form.find('#profile').val()}); return true; }); } From 959171acac5abc3716119f7d5a7918e7497fdbfd Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 24 Feb 2010 16:39:16 +0100 Subject: [PATCH 14/16] Added a cookie for the nickname cookie for the login page and prefill the input field. --- js/util.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/js/util.js b/js/util.js index 78533ab731..4b6c39a1dc 100644 --- a/js/util.js +++ b/js/util.js @@ -737,6 +737,20 @@ var SN = { // StatusNet SN.U.NewDirectMessage(); } + }, + + Login: function() { + if (SN.U.StatusNetInstance.Get() !== null) { + var nickname = SN.U.StatusNetInstance.Get().Nickname; + if (nickname !== null) { + $('#form_login #nickname').val(nickname); + } + } + + $('#form_login').bind('submit', function() { + SN.U.StatusNetInstance.Set({Nickname: $('#form_login #nickname').val()}); + return true; + }); } } }; @@ -751,5 +765,8 @@ $(document).ready(function(){ if ($('#content .entity_actions').length > 0) { SN.Init.EntityActions(); } + if ($('#form_login').length > 0) { + SN.Init.Login(); + } }); From 5cabb63e4eaf7cd3642bf7a0c4beb3fef2e1ba07 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Feb 2010 17:36:31 +0000 Subject: [PATCH 15/16] Include with actor ID and name in Activity::asString(); fixes Salmon signature on OStatus unsub pings --- lib/activity.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/activity.php b/lib/activity.php index 5cbab8d5f3..fa4ae02748 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -957,11 +957,24 @@ class Activity } // XXX: add context - // XXX: add target + $xs->elementStart('author'); + $xs->element('uri', array(), $this->actor->id); + if ($this->actor->title) { + $xs->element('name', array(), $this->actor->title); + } + $xs->elementEnd('author'); $xs->raw($this->actor->asString('activity:actor')); + $xs->element('activity:verb', null, $this->verb); - $xs->raw($this->object->asString()); + + if ($this->object) { + $xs->raw($this->object->asString()); + } + + if ($this->target) { + $xs->raw($this->target->asString('activity:target')); + } $xs->elementEnd('entry'); From 07214f1370547fcc64db34ce8c8a84ec70e0d5bd Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Feb 2010 19:06:10 +0000 Subject: [PATCH 16/16] OStatus: save updated profile bits when they come in over the wire; fix up nicknames --- plugins/OStatus/classes/Ostatus_profile.php | 147 +++++++++++++++----- 1 file changed, 112 insertions(+), 35 deletions(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 82dbf773dd..9f9efb96ee 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -33,6 +33,7 @@ class Ostatus_profile extends Memcached_DataObject public $feeduri; public $salmonuri; + public $avatar; // remote URL of the last avatar we saved public $created; public $modified; @@ -58,6 +59,7 @@ class Ostatus_profile extends Memcached_DataObject 'group_id' => DB_DATAOBJECT_INT, 'feeduri' => DB_DATAOBJECT_STR, 'salmonuri' => DB_DATAOBJECT_STR, + 'avatar' => DB_DATAOBJECT_STR, 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); } @@ -74,6 +76,8 @@ class Ostatus_profile extends Memcached_DataObject 255, true, 'UNI'), new ColumnDef('salmonuri', 'text', null, true), + new ColumnDef('avatar', 'text', + null, true), new ColumnDef('created', 'datetime', null, false), new ColumnDef('modified', 'datetime', @@ -543,7 +547,8 @@ class Ostatus_profile extends Memcached_DataObject // through PuSH setup or Salmon signature checks. $actorUri = self::getActorProfileURI($activity); if ($actorUri == $this->uri) { - // @fixme check if profile info has changed and update it + // Check if profile info has changed and update it + $this->updateFromActivityObject($activity->actor); } else { common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri"); return false; @@ -785,6 +790,11 @@ class Ostatus_profile extends Memcached_DataObject */ protected function updateAvatar($url) { + if ($url == $this->avatar) { + // We've already got this one. + return; + } + if ($this->isGroup()) { $self = $this->localGroup(); } else { @@ -816,12 +826,28 @@ class Ostatus_profile extends Memcached_DataObject common_timestamp()); rename($temp_filename, Avatar::path($filename)); $self->setOriginal($filename); + + $orig = clone($this); + $this->avatar = $url; + $this->update($orig); } - protected static function getActivityObjectAvatar($object) + /** + * Pull avatar URL from ActivityObject or profile hints + * + * @param ActivityObject $object + * @param array $hints + * @return mixed URL string or false + */ + + protected static function getActivityObjectAvatar($object, $hints=array()) { - // XXX: go poke around in the feed - return $object->avatar; + if ($object->avatar) { + return $object->avatar; + } else if (array_key_exists('avatar', $hints)) { + return $hints['avatar']; + } + return false; } /** @@ -888,7 +914,9 @@ class Ostatus_profile extends Memcached_DataObject public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) { $profile = self::getActivityObjectProfile($object); - if (!$profile) { + if ($profile) { + $profile->updateFromActivityObject($object, $hints); + } else { $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints); } return $profile; @@ -957,8 +985,6 @@ class Ostatus_profile extends Memcached_DataObject protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) { $homeuri = $object->id; - $nickname = self::getActivityObjectNickname($object, $hints); - $avatar = self::getActivityObjectAvatar($object); if (!$homeuri) { common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true)); @@ -1002,43 +1028,19 @@ class Ostatus_profile extends Memcached_DataObject if ($object->type == ActivityObject::PERSON) { $profile = new Profile(); - $profile->nickname = $nickname; - $profile->fullname = $object->title; - if (!empty($object->link)) { - $profile->profileurl = $object->link; - } else if (array_key_exists('profileurl', $hints)) { - $profile->profileurl = $hints['profileurl']; - } - $profile->created = common_sql_now(); - - // @fixme bio - // @fixme tags/categories - // @fixme location? - // @todo tags from categories - // @todo lat/lon/location? - + self::updateProfile($profile, $object, $hints); + $profile->created = common_sql_now(); + $oprofile->profile_id = $profile->insert(); - if (!$oprofile->profile_id) { throw new ServerException("Can't save local profile"); } } else { $group = new User_group(); - $group->nickname = $nickname; - $group->fullname = $object->title; - // @fixme no canonical profileurl; using homepage instead for now - $group->homepage = $homeuri; $group->created = common_sql_now(); - - // @fixme homepage - // @fixme bio - // @fixme tags/categories - // @fixme location? - // @todo tags from categories - // @todo lat/lon/location? + self::updateGroup($group, $object, $hints); $oprofile->group_id = $group->insert(); - if (!$oprofile->group_id) { throw new ServerException("Can't save local profile"); } @@ -1047,6 +1049,7 @@ class Ostatus_profile extends Memcached_DataObject $ok = $oprofile->insert(); if ($ok) { + $avatar = self::getActivityObjectAvatar($object, $hints); if ($avatar) { $oprofile->updateAvatar($avatar); } @@ -1056,8 +1059,82 @@ class Ostatus_profile extends Memcached_DataObject } } + /** + * Save any updated profile information to our local copy. + * @param ActivityObject $object + * @param array $hints + */ + protected function updateFromActivityObject($object, $hints=array()) + { + if ($this->isGroup()) { + $group = $this->localGroup(); + self::updateGroup($group, $object, $hints); + } else { + $profile = $this->localProfile(); + self::updateProfile($profile, $object, $hints); + } + $avatar = self::getActivityObjectAvatar($object, $hints); + if ($avatar) { + $this->updateAvatar($avatar); + } + } + + protected static function updateProfile($profile, $object, $hints=array()) + { + $orig = clone($profile); + + $profile->nickname = self::getActivityObjectNickname($object, $hints); + $profile->fullname = $object->title; + if (!empty($object->link)) { + $profile->profileurl = $object->link; + } else if (array_key_exists('profileurl', $hints)) { + $profile->profileurl = $hints['profileurl']; + } + + // @fixme bio + // @fixme tags/categories + // @fixme location? + // @todo tags from categories + // @todo lat/lon/location? + + if ($profile->id) { + common_log(LOG_DEBUG, "Updating OStatus profile $profile->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true)); + $profile->update($orig); + } + } + + protected static function updateGroup($group, $object, $hints=array()) + { + $orig = clone($group); + + // @fixme need to make nick unique etc *hack hack* + $group->nickname = self::getActivityObjectNickname($object, $hints); + $group->fullname = $object->title; + + // @fixme no canonical profileurl; using homepage instead for now + $group->homepage = $object->id; + + // @fixme homepage + // @fixme bio + // @fixme tags/categories + // @fixme location? + // @todo tags from categories + // @todo lat/lon/location? + + if ($group->id) { + common_log(LOG_DEBUG, "Updating OStatus group $group->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true)); + $group->update($orig); + } + } + + protected static function getActivityObjectNickname($object, $hints=array()) { + if ($object->poco) { + if (!empty($object->poco->preferredUsername)) { + return common_nicknamize($object->poco->preferredUsername); + } + } if (!empty($object->nickname)) { return common_nicknamize($object->nickname); }