summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvan Prodromou <evan@status.net>2010-02-24 15:00:52 -0500
committerEvan Prodromou <evan@status.net>2010-02-24 15:00:52 -0500
commitdaea98878692cadc9406be16fe2e15a419add3c4 (patch)
treed7b5e0ec34b04c7f91c43a4e5761dc764c64330a
parentf5ec7c27070dac4ac28ba860f4cc9a808b5f7c30 (diff)
parent07214f1370547fcc64db34ce8c8a84ec70e0d5bd (diff)
Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing
-rw-r--r--classes/Memcached_DataObject.php6
-rw-r--r--js/util.js49
-rw-r--r--lib/activity.php184
-rw-r--r--lib/noticelist.php9
-rw-r--r--plugins/OStatus/OStatusPlugin.php56
-rw-r--r--plugins/OStatus/actions/groupsalmon.php9
-rw-r--r--plugins/OStatus/actions/ostatussub.php11
-rw-r--r--plugins/OStatus/actions/webfinger.php15
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php373
-rw-r--r--plugins/OStatus/js/ostatus.js34
-rw-r--r--plugins/OStatus/lib/ostatusqueuehandler.php (renamed from plugins/OStatus/lib/hubdistribqueuehandler.php)95
-rw-r--r--plugins/OStatus/lib/salmonaction.php50
-rw-r--r--plugins/OStatus/lib/salmonoutqueuehandler.php44
-rw-r--r--tests/ActivityParseTests.php (renamed from plugins/OStatus/tests/ActivityParseTests.php)161
-rw-r--r--theme/base/css/display.css1
15 files changed, 774 insertions, 323 deletions
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php
index 40576dc71..bc4c3a000 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)
diff --git a/js/util.js b/js/util.js
index 3623337b9..4b6c39a1d 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);
+ }
}
},
@@ -707,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;
+ });
}
}
};
@@ -721,5 +765,8 @@ $(document).ready(function(){
if ($('#content .entity_actions').length > 0) {
SN.Init.EntityActions();
}
+ if ($('#form_login').length > 0) {
+ SN.Init.Login();
+ }
});
diff --git a/lib/activity.php b/lib/activity.php
index 04c57c561..fa4ae0274 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,25 +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)
+ {
+ if (empty($element)) {
+ return;
+ }
+
+ $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)
{
- $this->preferredUsername = $profile->nickname;
+ $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();
- $this->note = $profile->bio;
- $this->address = new PoCoAddress($profile->location);
+ $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,
@@ -118,6 +209,8 @@ class PoCo
)
);
}
+
+ return $poco;
}
function asString()
@@ -129,6 +222,12 @@ class PoCo
$this->preferredUsername
);
+ $xs->element(
+ 'poco:displayName',
+ null,
+ $this->displayName
+ );
+
if (!empty($this->note)) {
$xs->element('poco:note', null, $this->note);
}
@@ -374,6 +473,8 @@ class ActivityObject
public $source;
public $avatar;
public $geopoint;
+ public $poco;
+ public $displayName;
/**
* Constructor
@@ -426,7 +527,6 @@ class ActivityObject
$this->link = ActivityUtils::getPermalink($element);
- // XXX: grab PoCo stuff
}
// Some per-type attributes...
@@ -434,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);
}
}
@@ -490,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;
}
@@ -519,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,
@@ -532,7 +641,7 @@ class ActivityObject
? Avatar::defaultImage(AVATAR_PROFILE_SIZE)
: $this->avatar->displayUrl()
),
- ''
+ null
);
}
@@ -848,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');
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 837cb90fa..dcf17be08 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');
}
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php
index 934c858ac..8c8d909a8 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 version="1.0" encoding="UTF-8" ?' . '>';
- $xml .= $notice->asAtomEntry(true, true);
-
- $salmon = new Salmon();
- $salmon->post($oprofile->salmonuri, $xml);
- }
- }
}
/**
@@ -295,13 +281,19 @@ class OStatusPlugin extends Plugin
function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
{
if ($notice->source == 'ostatus') {
- $bits = parse_url($notice->uri);
- $domain = $bits['host'];
-
- $name = $domain;
- $url = $notice->uri;
- $title = sprintf(_m("Sent from %s via OStatus"), $domain);
- return false;
+ if ($notice->url) {
+ $bits = parse_url($notice->url);
+ $domain = $bits['host'];
+ if (substr($domain, 0, 4) == 'www.') {
+ $name = substr($domain, 4);
+ } else {
+ $name = $domain;
+ }
+
+ $url = $notice->url;
+ $title = sprintf(_m("Sent from %s via OStatus"), $domain);
+ return false;
+ }
}
}
@@ -316,7 +308,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");
}
@@ -517,12 +509,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/actions/groupsalmon.php b/plugins/OStatus/actions/groupsalmon.php
index 2e4fe9443..29377b5fa 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/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php
index b3569e695..12832cdcf 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.');
diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/webfinger.php
index fbbd8d039..34336a903 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));
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 91b957fa2..9f9efb96e 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',
@@ -488,7 +492,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 +502,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 +512,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,130 +526,190 @@ 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
+ // Check if profile info has changed and update it
+ $this->updateFromActivityObject($activity->actor);
} 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;
}
- $sourceUri = $activity->object->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->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) {
+ $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
+ $rendered = $this->purify($activity->object->content);
+ $content = html_entity_decode(strip_tags($rendered));
- $content = $activity->object->title;
-
- $params = array('is_local' => Notice::REMOTE_OMB,
+ $options = array('is_local' => Notice::REMOTE_OMB,
'url' => $sourceUrl,
- 'uri' => $sourceUri);
+ 'uri' => $sourceUri,
+ '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;
- }
- }
-
- $user = User::staticGet('uri', $recipient);
- if ($user) {
- // An @-reply directed to a local user.
- // @fixme sender verification, spam etc?
- $params['replies'][] = $recipient;
- 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;
}
-
- // @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;
+ }
+
+ /**
+ * 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)
+ {
+ common_log(LOG_DEBUG, "Original reply recipients: " . implode(', ', $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;
+ } else {
+ common_log(LOG_DEBUG, "Skipping reply to remote profile $recipient");
+ }
+ 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.
+ $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");
- // 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());
}
+ $attention_uris = $replies;
+ common_log(LOG_DEBUG, "Local reply recipients: " . implode(', ', $replies));
+ common_log(LOG_DEBUG, "Local group recipients: " . implode(', ', $groups));
+ return $groups;
}
/**
@@ -729,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 {
@@ -760,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;
}
/**
@@ -832,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;
@@ -901,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));
@@ -946,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");
}
@@ -991,6 +1049,7 @@ class Ostatus_profile extends Memcached_DataObject
$ok = $oprofile->insert();
if ($ok) {
+ $avatar = self::getActivityObjectAvatar($object, $hints);
if ($avatar) {
$oprofile->updateAvatar($avatar);
}
@@ -1000,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);
}
diff --git a/plugins/OStatus/js/ostatus.js b/plugins/OStatus/js/ostatus.js
index 1fc44b21b..bd29b5c0c 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;
});
}
@@ -123,4 +97,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; });
});
diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/ostatusqueuehandler.php
index c2bd630f9..c1e50bffa 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 <brion@status.net>
*/
-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 version="1.0" encoding="UTF-8" ?' . '>';
+ $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/salmonaction.php b/plugins/OStatus/lib/salmonaction.php
index 83cf0b8f8..9aac2ed52 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');
}
}
diff --git a/plugins/OStatus/lib/salmonoutqueuehandler.php b/plugins/OStatus/lib/salmonoutqueuehandler.php
new file mode 100644
index 000000000..536ff94af
--- /dev/null
+++ b/plugins/OStatus/lib/salmonoutqueuehandler.php
@@ -0,0 +1,44 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Send a Salmon notification in the background.
+ * @package OStatusPlugin
+ * @author Brion Vibber <brion@status.net>
+ */
+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;
+ }
+}
diff --git a/plugins/OStatus/tests/ActivityParseTests.php b/tests/ActivityParseTests.php
index d7305dede..5de97d2e2 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 = <<<EXAMPLE1
@@ -156,26 +194,26 @@ $_example3 = <<<EXAMPLE3
<feed xmlns="http://www.w3.org/2005/Atom">
- <title>Example Feed</title>
- <subtitle>A subtitle.</subtitle>
- <link href="http://example.org/feed/" rel="self" />
- <link href="http://example.org/" />
- <id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
- <updated>2003-12-13T18:30:02Z</updated>
- <author>
- <name>John Doe</name>
- <email>johndoe@example.com</email>
- </author>
-
- <entry>
- <title>Atom-Powered Robots Run Amok</title>
- <link href="http://example.org/2003/12/13/atom03" />
- <link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>
- <link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>
- <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
- <updated>2003-12-13T18:30:02Z</updated>
- <summary>Some text.</summary>
- </entry>
+ <title>Example Feed</title>
+ <subtitle>A subtitle.</subtitle>
+ <link href="http://example.org/feed/" rel="self" />
+ <link href="http://example.org/" />
+ <id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
+ <updated>2003-12-13T18:30:02Z</updated>
+ <author>
+ <name>John Doe</name>
+ <email>johndoe@example.com</email>
+ </author>
+
+ <entry>
+ <title>Atom-Powered Robots Run Amok</title>
+ <link href="http://example.org/2003/12/13/atom03" />
+ <link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>
+ <link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
+ <updated>2003-12-13T18:30:02Z</updated>
+ <summary>Some text.</summary>
+ </entry>
</feed>
EXAMPLE3;
@@ -207,3 +245,82 @@ $_example4 = <<<EXAMPLE4
<category term="thetime"></category>
</entry>
EXAMPLE4;
+
+$_example5 = <<<EXAMPLE5
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0">
+ <id>3</id>
+ <title>testuser timeline</title>
+ <subtitle>Updates from testuser on Zach Dev!</subtitle>
+ <logo>http://example.net/mysite/avatar/3-96-20100224004207.jpeg</logo>
+ <updated>2010-02-24T06:38:49+00:00</updated>
+<author>
+ <name>testuser</name>
+ <uri>http://example.net/mysite/user/3</uri>
+
+</author>
+ <link href="http://example.net/mysite/testuser" rel="alternate" type="text/html"/>
+ <link href="http://example.net/mysite/api/statuses/user_timeline/3.atom" rel="self" type="application/atom+xml"/>
+ <link href="http://example.net/mysite/main/sup#3" rel="http://api.friendfeed.com/2008/03#sup" type="application/json"/>
+ <link href="http://example.net/mysite/main/push/hub" rel="hub"/>
+ <link href="http://example.net/mysite/main/salmon/user/3" rel="salmon"/>
+<activity:subject>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
+ <id>http://example.net/mysite/user/3</id>
+ <title>Test User</title>
+ <link rel="alternate" type="text/html" href="http://example.net/mysite/testuser"/>
+ <link type="image/jpeg" rel="avatar" href="http://example.net/mysite/avatar/3-96-20100224004207.jpeg"/>
+ <georss:point>37.7749295 -122.4194155</georss:point>
+
+<poco:preferredUsername>testuser</poco:preferredUsername>
+<poco:displayName>Test User</poco:displayName>
+<poco:note>Just another test user.</poco:note>
+<poco:address>
+ <poco:formatted>San Francisco, CA</poco:formatted>
+</poco:address>
+<poco:urls>
+ <poco:type>homepage</poco:type>
+ <poco:value>http://example.com/blog.html</poco:value>
+ <poco:primary>true</poco:primary>
+
+</poco:urls>
+</activity:subject>
+<entry>
+ <title>Hey man, is that Freedom Code?! #freedom #hippy</title>
+ <summary>Hey man, is that Freedom Code?! #freedom #hippy</summary>
+<author>
+ <name>testuser</name>
+ <uri>http://example.net/mysite/user/3</uri>
+</author>
+<activity:actor>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
+ <id>http://example.net/mysite/user/3</id>
+ <title>Test User</title>
+ <link rel="alternate" type="text/html" href="http://example.net/mysite/testuser"/>
+ <link type="image/jpeg" rel="avatar" href="http://example.net/mysite/avatar/3-96-20100224004207.jpeg"/>
+ <georss:point>37.7749295 -122.4194155</georss:point>
+
+<poco:preferredUsername>testuser</poco:preferredUsername>
+<poco:displayName>Test User</poco:displayName>
+<poco:note>Just another test user.</poco:note>
+<poco:address>
+ <poco:formatted>San Francisco, CA</poco:formatted>
+</poco:address>
+<poco:urls>
+ <poco:type>homepage</poco:type>
+ <poco:value>http://example.com/blog.html</poco:value>
+ <poco:primary>true</poco:primary>
+
+</poco:urls>
+</activity:actor>
+ <link rel="alternate" type="text/html" href="http://example.net/mysite/notice/7"/>
+ <id>http://example.net/mysite/notice/7</id>
+ <published>2010-02-24T00:53:06+00:00</published>
+ <updated>2010-02-24T00:53:06+00:00</updated>
+ <link rel="ostatus:conversation" href="http://example.net/mysite/conversation/7"/>
+ <content type="html">Hey man, is that Freedom Code?! #&lt;span class=&quot;tag&quot;&gt;&lt;a href=&quot;http://example.net/mysite/tag/freedom&quot; rel=&quot;tag&quot;&gt;freedom&lt;/a&gt;&lt;/span&gt; #&lt;span class=&quot;tag&quot;&gt;&lt;a href=&quot;http://example.net/mysite/tag/hippy&quot; rel=&quot;tag&quot;&gt;hippy&lt;/a&gt;&lt;/span&gt;</content>
+ <georss:point>37.8313160 -122.2852473</georss:point>
+
+</entry>
+</feed>
+EXAMPLE5;
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index 380975e32..52f97f6b1 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;