summaryrefslogtreecommitdiff
path: root/plugins/OStatus
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/OStatus')
-rw-r--r--plugins/OStatus/OStatusPlugin.php259
-rw-r--r--plugins/OStatus/actions/feedsubsettings.php107
-rw-r--r--plugins/OStatus/actions/groupsalmon.php108
-rw-r--r--plugins/OStatus/actions/ostatusinit.php78
-rw-r--r--plugins/OStatus/actions/ostatussub.php232
-rw-r--r--plugins/OStatus/actions/pushcallback.php24
-rw-r--r--plugins/OStatus/actions/pushhub.php9
-rw-r--r--plugins/OStatus/actions/salmon.php81
-rw-r--r--plugins/OStatus/actions/usersalmon.php202
-rw-r--r--plugins/OStatus/actions/webfinger.php2
-rw-r--r--plugins/OStatus/classes/FeedSub.php443
-rw-r--r--plugins/OStatus/classes/HubSub.php11
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php822
-rw-r--r--plugins/OStatus/extlib/README9
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser.php351
-rw-r--r--plugins/OStatus/extlib/XML/Feed/Parser/Atom.php365
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/AtomElement.php261
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/Exception.php42
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS09.php214
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS09Element.php62
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS1.php277
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS11.php276
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS11Element.php151
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS1Element.php116
-rw-r--r--plugins/OStatus/extlib/XML/Feed/Parser/RSS2.php335
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/Parser/RSS2Element.php171
-rw-r--r--plugins/OStatus/extlib/XML/Feed/Parser/Type.php467
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/atom10-entryonly.xml28
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/atom10-example1.xml20
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/atom10-example2.xml45
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/delicious.feed177
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/flickr.feed184
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/grwifi-atom.xml7
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/hoder.xml102
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/illformed_atom10.xml13
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss091-complete.xml47
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss091-international.xml30
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss091-simple.xml15
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss092-sample.xml103
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss10-example1.xml62
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss10-example2.xml67
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/rss2sample.xml42
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/sixapart-jp.xml226
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/samples/technorati.feed54
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/schemas/atom.rnc338
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/schemas/rss10.rnc113
-rwxr-xr-xplugins/OStatus/extlib/XML/Feed/schemas/rss11.rnc218
-rw-r--r--plugins/OStatus/extlib/xml-feed-parser-bug-16416.patch14
-rw-r--r--plugins/OStatus/lib/activity.php278
-rw-r--r--plugins/OStatus/lib/feeddiscovery.php50
-rw-r--r--plugins/OStatus/lib/feedmunger.php350
-rw-r--r--plugins/OStatus/lib/hubverifyqueuehandler.php3
-rw-r--r--plugins/OStatus/lib/salmon.php3
-rw-r--r--plugins/OStatus/lib/salmonaction.php209
-rw-r--r--plugins/OStatus/tests/ActivityParseTests.php64
-rw-r--r--plugins/OStatus/tests/FeedMungerTest.php147
56 files changed, 2180 insertions, 6304 deletions
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php
index 3686c0226..756bff976 100644
--- a/plugins/OStatus/OStatusPlugin.php
+++ b/plugins/OStatus/OStatusPlugin.php
@@ -1,17 +1,7 @@
<?php
/*
-StatusNet Plugin: 0.9
-Plugin Name: FeedSub
-Plugin URI: http://status.net/wiki/Feed_subscription
-Description: FeedSub allows subscribing to real-time updates from external feeds supporting PubHubSubbub protocol.
-Version: 0.1
-Author: Brion Vibber <brion@status.net>
-Author URI: http://status.net/
-*/
-
-/*
* StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, StatusNet, Inc.
+ * Copyright (C) 2009-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
@@ -28,17 +18,12 @@ Author URI: http://status.net/
*/
/**
- * @package FeedSubPlugin
+ * @package OStatusPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-define('FEEDSUB_SERVICE', 100); // fixme -- avoid hardcoding these?
-
-// We bundle the XML_Parse_Feed library...
-set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib');
-
class FeedSubException extends Exception
{
}
@@ -78,10 +63,10 @@ class OStatusPlugin extends Plugin
// Salmon endpoint
$m->connect('main/salmon/user/:id',
- array('action' => 'salmon'),
+ array('action' => 'usersalmon'),
array('id' => '[0-9]+'));
$m->connect('main/salmon/group/:id',
- array('action' => 'salmongroup'),
+ array('action' => 'groupsalmon'),
array('id' => '[0-9]+'));
return true;
}
@@ -112,21 +97,26 @@ class OStatusPlugin extends Plugin
* Set up a PuSH hub link to our internal link for canonical timeline
* Atom feeds for users and groups.
*/
- function onStartApiAtom(AtomNoticeFeed $feed)
+ function onStartApiAtom($feed)
{
$id = null;
if ($feed instanceof AtomUserNoticeFeed) {
- $salmonAction = 'salmon';
- $id = $feed->getUser()->id;
+ $salmonAction = 'usersalmon';
+ $user = $feed->getUser();
+ $id = $user->id;
+ $profile = $user->getProfile();
+ $feed->setActivitySubject($profile->asActivityNoun('subject'));
} else if ($feed instanceof AtomGroupNoticeFeed) {
- $salmonAction = 'salmongroup';
- $id = $feed->getGroup()->id;
+ $salmonAction = 'groupsalmon';
+ $group = $feed->getGroup();
+ $id = $group->id;
+ $feed->setActivitySubject($group->asActivitySubject());
} else {
- return;
+ return true;
}
- if (!empty($id)) {
+ if (!empty($id)) {
$hub = common_config('ostatus', 'hub');
if (empty($hub)) {
// Updates will be handled through our internal PuSH hub.
@@ -138,6 +128,8 @@ class OStatusPlugin extends Plugin
$salmon = common_local_url($salmonAction, array('id' => $id));
$feed->addLink($salmon, array('rel' => 'salmon'));
}
+
+ return true;
}
/**
@@ -171,6 +163,12 @@ class OStatusPlugin extends Plugin
{
$base = dirname(__FILE__);
$lower = strtolower($cls);
+ $map = array('activityverb' => 'activity',
+ 'activityobject' => 'activity',
+ 'activityutils' => 'activity');
+ if (isset($map[$lower])) {
+ $lower = $map[$lower];
+ }
$files = array("$base/classes/$cls.php",
"$base/lib/$lower.php");
if (substr($lower, -6) == 'action') {
@@ -253,20 +251,54 @@ class OStatusPlugin extends Plugin
}
/**
- * Garbage collect unused feeds on unsubscribe
+ * Notify remote server and garbage collect unused feeds on unsubscribe.
+ * @fixme send these operations to background queues
+ *
+ * @param User $user
+ * @param Profile $other
+ * @return hook return value
*/
- function onEndUnsubscribe($user, $other)
+ function onEndUnsubscribe($profile, $other)
{
- $profile = Ostatus_profile::staticGet('profile_id', $other->id);
- if ($feed) {
- $sub = new Subscription();
- $sub->subscribed = $other->id;
- $sub->limit(1);
- if (!$sub->find(true)) {
- common_log(LOG_INFO, "Unsubscribing from now-unused feed $feed->feeduri on hub $feed->huburi");
- $profile->unsubscribe();
- }
+ $user = User::staticGet('id', $profile->id);
+
+ if (empty($user)) {
+ return true;
+ }
+
+ $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
+
+ if (empty($oprofile)) {
+ return true;
+ }
+
+ // Drop the PuSH subscription if there are no other subscribers.
+
+ if ($other->subscriberCount() == 0) {
+ common_log(LOG_INFO, "Unsubscribing from now-unused feed $oprofile->feeduri");
+ $oprofile->unsubscribe();
}
+
+ $act = new Activity();
+
+ $act->verb = ActivityVerb::UNFOLLOW;
+
+ $act->id = TagURI::mint('unfollow:%d:%d:%s',
+ $profile->id,
+ $other->id,
+ common_date_iso8601(time()));
+
+ $act->time = time();
+ $act->title = _("Unfollow");
+ $act->content = sprintf(_("%s stopped following %s."),
+ $profile->getBestName(),
+ $other->getBestName());
+
+ $act->actor = ActivityObject::fromProfile($profile);
+ $act->object = ActivityObject::fromProfile($other);
+
+ $oprofile->notifyActivity($act);
+
return true;
}
@@ -276,6 +308,7 @@ class OStatusPlugin extends Plugin
function onCheckSchema() {
$schema = Schema::get();
$schema->ensureTable('ostatus_profile', Ostatus_profile::schemaDef());
+ $schema->ensureTable('feedsub', FeedSub::schemaDef());
$schema->ensureTable('hubsub', HubSub::schemaDef());
return true;
}
@@ -290,6 +323,16 @@ class OStatusPlugin extends Plugin
return true;
}
+ /**
+ * Override the "from ostatus" bit in notice lists to link to the
+ * original post and show the domain it came from.
+ *
+ * @param Notice in $notice
+ * @param string out &$name
+ * @param string out &$url
+ * @param string out &$title
+ * @return mixed hook return code
+ */
function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
{
if ($notice->source == 'ostatus') {
@@ -302,4 +345,146 @@ class OStatusPlugin extends Plugin
return false;
}
}
+
+ /**
+ * Send incoming PuSH feeds for OStatus endpoints in for processing.
+ *
+ * @param FeedSub $feedsub
+ * @param DOMDocument $feed
+ * @return mixed hook return code
+ */
+ function onStartFeedSubReceive($feedsub, $feed)
+ {
+ $oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
+ if ($oprofile) {
+ $oprofile->processFeed($feed);
+ } else {
+ common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri");
+ }
+ }
+
+ function onEndSubscribe($subscriber, $other)
+ {
+ $user = User::staticGet('id', $subscriber->id);
+
+ if (empty($user)) {
+ return true;
+ }
+
+ $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
+
+ if (empty($oprofile)) {
+ return true;
+ }
+
+ $act = new Activity();
+
+ $act->verb = ActivityVerb::FOLLOW;
+
+ $act->id = TagURI::mint('follow:%d:%d:%s',
+ $subscriber->id,
+ $other->id,
+ common_date_iso8601(time()));
+
+ $act->time = time();
+ $act->title = _("Follow");
+ $act->content = sprintf(_("%s is now following %s."),
+ $subscriber->getBestName(),
+ $other->getBestName());
+
+ $act->actor = ActivityObject::fromProfile($subscriber);
+ $act->object = ActivityObject::fromProfile($other);
+
+ $oprofile->notifyActivity($act);
+
+ return true;
+ }
+
+ /**
+ * Notify remote users when their notices get favorited.
+ *
+ * @param Profile or User $profile of local user doing the faving
+ * @param Notice $notice being favored
+ * @return hook return value
+ */
+
+ function onEndFavorNotice(Profile $profile, Notice $notice)
+ {
+ $user = User::staticGet('id', $profile->id);
+
+ if (empty($user)) {
+ return true;
+ }
+
+ $oprofile = Ostatus_profile::staticGet('profile_id', $notice->profile_id);
+
+ if (empty($oprofile)) {
+ return true;
+ }
+
+ $act = new Activity();
+
+ $act->verb = ActivityVerb::FAVORITE;
+ $act->id = TagURI::mint('favor:%d:%d:%s',
+ $profile->id,
+ $notice->id,
+ common_date_iso8601(time()));
+
+ $act->time = time();
+ $act->title = _("Favor");
+ $act->content = sprintf(_("%s marked notice %s as a favorite."),
+ $profile->getBestName(),
+ $notice->uri);
+
+ $act->actor = ActivityObject::fromProfile($profile);
+ $act->object = ActivityObject::fromNotice($notice);
+
+ $oprofile->notifyActivity($act);
+
+ return true;
+ }
+
+ /**
+ * Notify remote users when their notices get de-favorited.
+ *
+ * @param Profile $profile Profile person doing the de-faving
+ * @param Notice $notice Notice being favored
+ *
+ * @return hook return value
+ */
+
+ function onEndDisfavorNotice(Profile $profile, Notice $notice)
+ {
+ $user = User::staticGet('id', $profile->id);
+
+ if (empty($user)) {
+ return true;
+ }
+
+ $oprofile = Ostatus_profile::staticGet('profile_id', $notice->profile_id);
+
+ if (empty($oprofile)) {
+ return true;
+ }
+
+ $act = new Activity();
+
+ $act->verb = ActivityVerb::UNFAVORITE;
+ $act->id = TagURI::mint('disfavor:%d:%d:%s',
+ $profile->id,
+ $notice->id,
+ common_date_iso8601(time()));
+ $act->time = time();
+ $act->title = _("Disfavor");
+ $act->content = sprintf(_("%s marked notice %s as no longer a favorite."),
+ $profile->getBestName(),
+ $notice->uri);
+
+ $act->actor = ActivityObject::fromProfile($profile);
+ $act->object = ActivityObject::fromNotice($notice);
+
+ $oprofile->notifyActivity($act);
+
+ return true;
+ }
}
diff --git a/plugins/OStatus/actions/feedsubsettings.php b/plugins/OStatus/actions/feedsubsettings.php
index 6933c9bf2..aee4cee9a 100644
--- a/plugins/OStatus/actions/feedsubsettings.php
+++ b/plugins/OStatus/actions/feedsubsettings.php
@@ -26,7 +26,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class FeedSubSettingsAction extends ConnectSettingsAction
{
- protected $feedurl;
+ protected $profile_uri;
protected $preview;
protected $munger;
@@ -68,14 +68,6 @@ class FeedSubSettingsAction extends ConnectSettingsAction
$profile = $user->getProfile();
- $fuser = null;
-
- $flink = Foreign_link::getByUserID($user->id, FEEDSUB_SERVICE);
-
- if (!empty($flink)) {
- $fuser = $flink->getForeignUser();
- }
-
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_feedsub',
'class' => 'form_settings',
@@ -88,7 +80,10 @@ class FeedSubSettingsAction extends ConnectSettingsAction
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'settings_twitter_login_button'));
- $this->input('feedurl', _('Feed URL'), $this->feedurl, _('Enter the URL of a PubSubHubbub-enabled feed'));
+ $this->input('profile_uri',
+ _m('Feed URL'),
+ $this->profile_uri,
+ _m('Enter the profile URL of a PubSubHubbub-enabled feed'));
$this->elementEnd('li');
$this->elementEnd('ul');
@@ -145,79 +140,55 @@ class FeedSubSettingsAction extends ConnectSettingsAction
*/
function validateFeed()
{
- $feedurl = trim($this->arg('feedurl'));
+ $profile_uri = trim($this->arg('profile_uri'));
- if ($feedurl == '') {
- $this->showForm(_m('Empty feed URL!'));
+ if ($profile_uri == '') {
+ $this->showForm(_m('Empty remote profile URL!'));
return;
}
- $this->feedurl = $feedurl;
+ $this->profile_uri = $profile_uri;
- // Get the canonical feed URI and check it
+ // @fixme validate, normalize bla bla
try {
- $discover = new FeedDiscovery();
- $uri = $discover->discoverFromURL($feedurl);
+ $oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
+ $this->oprofile = $oprofile;
+ return true;
} catch (FeedSubBadURLException $e) {
- $this->showForm(_m('Invalid URL or could not reach server.'));
- return false;
+ $err = _m('Invalid URL or could not reach server.');
} catch (FeedSubBadResponseException $e) {
- $this->showForm(_m('Cannot read feed; server returned error.'));
- return false;
+ $err = _m('Cannot read feed; server returned error.');
} catch (FeedSubEmptyException $e) {
- $this->showForm(_m('Cannot read feed; server returned an empty page.'));
- return false;
+ $err = _m('Cannot read feed; server returned an empty page.');
} catch (FeedSubBadHTMLException $e) {
- $this->showForm(_m('Bad HTML, could not find feed link.'));
- return false;
+ $err = _m('Bad HTML, could not find feed link.');
} catch (FeedSubNoFeedException $e) {
- $this->showForm(_m('Could not find a feed linked from this URL.'));
- return false;
+ $err = _m('Could not find a feed linked from this URL.');
} catch (FeedSubUnrecognizedTypeException $e) {
- $this->showForm(_m('Not a recognized feed type.'));
- return false;
+ $err = _m('Not a recognized feed type.');
} catch (FeedSubException $e) {
// Any new ones we forgot about
- $this->showForm(_m('Bad feed URL.'));
- return false;
+ $err = sprintf(_m('Bad feed URL: %s %s'), get_class($e), $e->getMessage());
}
-
- $this->munger = $discover->feedMunger();
- $this->profile = $this->munger->ostatusProfile();
- if ($this->profile->huburi == '' && !common_config('feedsub', 'nohub')) {
- $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.'));
- return false;
- }
-
- return true;
+ $this->showForm($err);
+ return false;
}
function saveFeed()
{
if ($this->validateFeed()) {
$this->preview = true;
- $this->profile = Ostatus_profile::ensureProfile($this->munger);
- if (!$this->profile) {
- throw new ServerException("Feed profile was not saved properly.");
- }
-
- // If not already in use, subscribe to updates via the hub
- if ($this->profile->sub_start) {
- common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->profile->feeduri} last subbed {$this->profile->sub_start}");
- } else {
- $ok = $this->profile->subscribe();
- common_log(LOG_INFO, __METHOD__ . ": sub was $ok");
- if (!$ok) {
- $this->showForm(_m('Feed subscription failed! Bad response from hub.'));
- return;
- }
- }
// And subscribe the current user to the local profile
$user = common_current_user();
- if ($this->profile->isGroup()) {
- $group = $this->profile->localGroup();
+ if (!$this->oprofile->subscribe()) {
+ $this->showForm(_m("Failed to set up server-to-server subscription."));
+ return;
+ }
+
+ if ($this->oprofile->isGroup()) {
+ $group = $this->oprofile->localGroup();
if ($user->isMember($group)) {
$this->showForm(_m('Already a member!'));
} elseif (Group_member::join($this->profile->group_id, $user->id)) {
@@ -226,13 +197,13 @@ class FeedSubSettingsAction extends ConnectSettingsAction
$this->showForm(_m('Remote group join failed!'));
}
} else {
- $local = $this->profile->localProfile();
+ $local = $this->oprofile->localProfile();
if ($user->isSubscribed($local)) {
$this->showForm(_m('Already subscribed!'));
- } elseif ($user->subscribeTo($local)) {
- $this->showForm(_m('Feed subscribed!'));
+ } elseif ($this->oprofile->subscribeLocalToRemote($user)) {
+ $this->showForm(_m('Remote user subscribed!'));
} else {
- $this->showForm(_m('Feed subscription failed!'));
+ $this->showForm(_m('Remote subscription failed!'));
}
}
}
@@ -248,17 +219,7 @@ class FeedSubSettingsAction extends ConnectSettingsAction
function previewFeed()
{
- $profile = $this->munger->ostatusProfile();
- $notice = $this->munger->notice(0, true); // preview
-
- if ($notice) {
- $this->element('b', null, 'Preview of latest post from this feed:');
-
- $item = new NoticeList($notice, $this);
- $item->show();
- } else {
- $this->element('b', null, 'No posts in this feed yet.');
- }
+ $this->text('Profile preview should go here');
}
function showScripts()
diff --git a/plugins/OStatus/actions/groupsalmon.php b/plugins/OStatus/actions/groupsalmon.php
new file mode 100644
index 000000000..64ae9f3cc
--- /dev/null
+++ b/plugins/OStatus/actions/groupsalmon.php
@@ -0,0 +1,108 @@
+<?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/>.
+ */
+
+/**
+ * @package OStatusPlugin
+ * @author James Walker <james@status.net>
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class GroupsalmonAction extends SalmonAction
+{
+ var $group = null;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $id = $this->trimmed('id');
+
+ if (!$id) {
+ $this->clientError(_('No ID.'));
+ }
+
+ $this->group = User_group::staticGet('id', $id);
+
+ if (empty($this->group)) {
+ $this->clientError(_('No such group.'));
+ }
+
+ return true;
+ }
+
+ /**
+ * We've gotten a post event on the Salmon backchannel, probably a reply.
+ */
+
+ function handlePost()
+ {
+ switch ($this->act->object->type) {
+ case ActivityObject::ARTICLE:
+ case ActivityObject::BLOGENTRY:
+ case ActivityObject::NOTE:
+ case ActivityObject::STATUS:
+ case ActivityObject::COMMENT:
+ break;
+ default:
+ throw new ClientException("Can't handle that kind of post.");
+ }
+
+ // Notice must be to the attention of this group
+
+ $context = $this->act->context;
+
+ if (empty($context->attention)) {
+ 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)) {
+ throw new ClientException("Not to the attention of this group.");
+ }
+ }
+
+ $profile = $this->ensureProfile();
+ // @fixme save the post
+ }
+
+ /**
+ * We've gotten a follow/subscribe notification from a remote user.
+ * Save a subscription relationship for them.
+ */
+
+ function handleFollow()
+ {
+ $this->handleJoin(); // ???
+ }
+
+ function handleUnfollow()
+ {
+ }
+
+ /**
+ * A remote user joined our group.
+ */
+
+ function handleJoin()
+ {
+ }
+
+}
diff --git a/plugins/OStatus/actions/ostatusinit.php b/plugins/OStatus/actions/ostatusinit.php
index d21774420..4afde2c36 100644
--- a/plugins/OStatus/actions/ostatusinit.php
+++ b/plugins/OStatus/actions/ostatusinit.php
@@ -37,7 +37,7 @@ class OStatusInitAction extends Action
parent::prepare($args);
if (common_logged_in()) {
- $this->clientError(_('You can use the local subscription!'));
+ $this->clientError(_m('You can use the local subscription!'));
return false;
}
@@ -55,7 +55,7 @@ class OStatusInitAction extends Action
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
- $this->showForm(_('There was a problem with your session token. '.
+ $this->showForm(_m('There was a problem with your session token. '.
'Try again, please.'));
return;
}
@@ -73,7 +73,7 @@ class OStatusInitAction extends Action
$this->xw->startDocument('1.0', 'UTF-8');
$this->elementStart('html');
$this->elementStart('head');
- $this->element('title', null, _('Subscribe to user'));
+ $this->element('title', null, _m('Subscribe to user'));
$this->elementEnd('head');
$this->elementStart('body');
$this->showContent();
@@ -91,50 +91,78 @@ class OStatusInitAction extends Action
'class' => 'form_settings',
'action' => common_local_url('ostatusinit')));
$this->elementStart('fieldset');
- $this->element('legend', null, sprintf(_('Subscribe to %s'), $this->nickname));
+ $this->element('legend', null, sprintf(_m('Subscribe to %s'), $this->nickname));
$this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'ostatus_nickname'));
- $this->input('nickname', _('User nickname'), $this->nickname,
- _('Nickname of the user you want to follow'));
+ $this->input('nickname', _m('User nickname'), $this->nickname,
+ _m('Nickname of the user you want to follow'));
$this->elementEnd('li');
$this->elementStart('li', array('id' => 'ostatus_profile'));
- $this->input('acct', _('Profile Account'), $this->acct,
- _('Your account id (i.e. user@identi.ca)'));
+ $this->input('acct', _m('Profile Account'), $this->acct,
+ _m('Your account id (i.e. user@identi.ca)'));
$this->elementEnd('li');
$this->elementEnd('ul');
- $this->submit('submit', _('Subscribe'));
+ $this->submit('submit', _m('Subscribe'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
function ostatusConnect()
{
- $w = new Webfinger;
+ $opts = array('allowed_schemes' => array('http', 'https', 'acct'));
+ if (Validate::uri($this->acct, $opts)) {
+ $bits = parse_url($this->acct);
+ if ($bits['scheme'] == 'acct') {
+ $this->connectWebfinger($bits['path']);
+ } else {
+ $this->connectProfile($this->acct);
+ }
+ } elseif (strpos('@', $this->acct) !== false) {
+ $this->connectWebfinger($this->acct);
+ }
+ }
+
+ function connectWebfinger($acct)
+ {
+ $w = new Webfinger;
+
+ $result = $w->lookup($acct);
+ if (!$result) {
+ $this->clientError(_m("Couldn't look up OStatus account profile."));
+ }
+ foreach ($result->links as $link) {
+ if ($link['rel'] == 'http://ostatus.org/schema/1.0/subscribe') {
+ // We found a URL - let's redirect!
+
+ $user = User::staticGet('nickname', $this->nickname);
+ $target_profile = common_local_url('userbyid', array('id' => $user->id));
- $result = $w->lookup($this->acct);
- foreach ($result->links as $link) {
- if ($link['rel'] == 'http://ostatus.org/schema/1.0/subscribe') {
- // We found a URL - let's redirect!
+ $url = $w->applyTemplate($link['template'], $feed_url);
- $user = User::staticGet('nickname', $this->nickname);
+ common_redirect($url, 303);
+ }
+
+ }
+
+ }
- $feed_url = common_local_url('ApiTimelineUser',
- array('id' => $user->id,
- 'format' => 'atom'));
- $url = $w->applyTemplate($link['template'], $feed_url);
+ function connectProfile($subscriber_profile)
+ {
+ $user = User::staticGet('nickname', $this->nickname);
+ $target_profile = common_local_url('userbyid', array('id' => $user->id));
- common_redirect($url, 303);
- }
+ // @fixme hack hack! We should look up the remote sub URL from XRDS
+ $suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/ostatussub', $subscriber_profile);
+ $suburl .= '?profile=' . urlencode($target_profile);
- }
-
+ common_redirect($suburl, 303);
}
-
+
function title()
{
- return _('OStatus Connect');
+ return _m('OStatus Connect');
}
}
diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php
index 239122501..bbbd1b7e6 100644
--- a/plugins/OStatus/actions/ostatussub.php
+++ b/plugins/OStatus/actions/ostatussub.php
@@ -1,7 +1,7 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
+ * Copyright (C) 2009, 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
@@ -19,57 +19,70 @@
/**
* @package OStatusPlugin
- * @maintainer James Walker <james@status.net>
+ * @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class OStatusSubAction extends Action
{
+ protected $profile_uri;
+ protected $preview;
+ protected $munger;
+
+ /**
+ * Title of the page
+ *
+ * @return string Title of the page
+ */
- protected $feedurl;
-
function title()
{
- return _m("OStatus Subscribe");
+ return _m('Authorize subscription');
}
- function handle($args)
- {
- if ($this->validateFeed()) {
- $this->showForm();
- }
-
- return true;
+ /**
+ * Instructions for use
+ *
+ * @return instructions for use
+ */
+ function getInstructions()
+ {
+ return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
}
- function showForm($err = null)
+ function showForm($error=null)
{
- $this->err = $err;
+ $this->error = $error;
$this->showPage();
}
+ /**
+ * Content area of the page
+ *
+ * Shows a form for associating a remote OStatus account with this
+ * StatusNet account.
+ *
+ * @return void
+ */
function showContent()
{
+ // @fixme is this right place?
+ if ($this->error) {
+ $this->text($this->error);
+ }
+
$user = common_current_user();
$profile = $user->getProfile();
- $fuser = null;
-
- $flink = Foreign_link::getByUserID($user->id, FEEDSUB_SERVICE);
-
- if (!empty($flink)) {
- $fuser = $flink->getForeignUser();
- }
-
$this->elementStart('form', array('method' => 'post',
- 'id' => 'form_settings_feedsub',
+ 'id' => 'ostatus_sub',
'class' => 'form_settings',
'action' =>
- common_local_url('feedsubsettings')));
+ common_local_url('ostatussub')));
$this->hidden('token', common_session_token());
@@ -77,17 +90,58 @@ class OStatusSubAction extends Action
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
- $this->input('feedurl', _('Feed URL'), $this->feedurl, _('Enter the URL of a PubSubHubbub-enabled feed'));
+ $this->input('profile',
+ _m('Address or profile URL'),
+ $this->profile_uri,
+ _m('Enter the profile URL of a PubSubHubbub-enabled feed'));
$this->elementEnd('li');
$this->elementEnd('ul');
- $this->submit('subscribe', _m('Subscribe'));
+ if ($this->preview) {
+ $this->submit('subscribe', _m('Subscribe'));
+ } else {
+ $this->submit('validate', _m('Continue'));
+ }
$this->elementEnd('fieldset');
$this->elementEnd('form');
- $this->previewFeed();
+ if ($this->preview) {
+ $this->previewFeed();
+ }
+ }
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ if (!common_logged_in()) {
+ // XXX: selfURL() didn't work. :<
+ common_set_returnto($_SERVER['REQUEST_URI']);
+ if (Event::handle('RedirectToLogin', array($this, null))) {
+ common_redirect(common_local_url('login'), 303);
+ }
+ return false;
+ }
+
+ $this->profile_uri = $this->arg('profile');
+
+ return true;
+ }
+
+ function handle($args)
+ {
+ parent::handle($args);
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->handlePost();
+ } else {
+ if ($this->profile_uri) {
+ $this->validateAndPreview();
+ } else {
+ $this->showPage();
+ }
+ }
}
/**
@@ -111,14 +165,15 @@ class OStatusSubAction extends Action
return;
}
- if ($this->arg('subscribe')) {
+ if ($this->arg('validate')) {
+ $this->validateAndPreview();
+ } else if ($this->arg('subscribe')) {
$this->saveFeed();
} else {
$this->showForm(_('Unexpected form submission.'));
}
}
-
/**
* Set up and add a feed
*
@@ -127,100 +182,91 @@ class OStatusSubAction extends Action
*/
function validateFeed()
{
- $feedurl = $this->trimmed('feed');
-
- if ($feedurl == '') {
- $this->showForm(_m('Empty feed URL!'));
+ $profile_uri = trim($this->arg('profile'));
+
+ if ($profile_uri == '') {
+ $this->showForm(_m('Empty remote profile URL!'));
return;
}
- $this->feedurl = $feedurl;
-
- // Get the canonical feed URI and check it
+ $this->profile_uri = $profile_uri;
+
+ // @fixme validate, normalize bla bla
try {
- $discover = new FeedDiscovery();
- $uri = $discover->discoverFromURL($feedurl);
+ $oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
+ $this->oprofile = $oprofile;
+ return true;
} catch (FeedSubBadURLException $e) {
- $this->showForm(_m('Invalid URL or could not reach server.'));
- return false;
+ $err = _m('Invalid URL or could not reach server.');
} catch (FeedSubBadResponseException $e) {
- $this->showForm(_m('Cannot read feed; server returned error.'));
- return false;
+ $err = _m('Cannot read feed; server returned error.');
} catch (FeedSubEmptyException $e) {
- $this->showForm(_m('Cannot read feed; server returned an empty page.'));
- return false;
+ $err = _m('Cannot read feed; server returned an empty page.');
} catch (FeedSubBadHTMLException $e) {
- $this->showForm(_m('Bad HTML, could not find feed link.'));
- return false;
+ $err = _m('Bad HTML, could not find feed link.');
} catch (FeedSubNoFeedException $e) {
- $this->showForm(_m('Could not find a feed linked from this URL.'));
- return false;
+ $err = _m('Could not find a feed linked from this URL.');
} catch (FeedSubUnrecognizedTypeException $e) {
- $this->showForm(_m('Not a recognized feed type.'));
- return false;
+ $err = _m('Not a recognized feed type.');
} catch (FeedSubException $e) {
// Any new ones we forgot about
- $this->showForm(_m('Bad feed URL.'));
- return false;
+ $err = sprintf(_m('Bad feed URL: %s %s'), get_class($e), $e->getMessage());
}
-
- $this->munger = $discover->feedMunger();
- $this->profile = $this->munger->ostatusProfile();
- if ($this->profile->huburi == '') {
- $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.'));
- return false;
- }
-
- return true;
+ $this->showForm($err);
+ return false;
}
function saveFeed()
{
if ($this->validateFeed()) {
$this->preview = true;
- $this->profile = Ostatus_profile::ensureProfile($this->munger);
- // If not already in use, subscribe to updates via the hub
- if ($this->profile->sub_start) {
- common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->profile->feeduri} last subbed {$this->profile->sub_start}");
- } else {
- $ok = $this->profile->subscribe();
- common_log(LOG_INFO, __METHOD__ . ": sub was $ok");
- if (!$ok) {
- $this->showForm(_m('Feed subscription failed! Bad response from hub.'));
- return;
- }
- }
-
// And subscribe the current user to the local profile
$user = common_current_user();
- $profile = $this->profile->getProfile();
-
- if ($user->isSubscribed($profile)) {
- $this->showForm(_m('Already subscribed!'));
- } elseif ($user->subscribeTo($profile)) {
- $this->showForm(_m('Feed subscribed!'));
+
+ if (!$this->oprofile->subscribe()) {
+ $this->showForm(_m("Failed to set up server-to-server subscription."));
+ return;
+ }
+
+ if ($this->oprofile->isGroup()) {
+ $group = $this->oprofile->localGroup();
+ if ($user->isMember($group)) {
+ $this->showForm(_m('Already a member!'));
+ } elseif (Group_member::join($this->profile->group_id, $user->id)) {
+ $this->showForm(_m('Joined remote group!'));
+ } else {
+ $this->showForm(_m('Remote group join failed!'));
+ }
} else {
- $this->showForm(_m('Feed subscription failed!'));
+ $local = $this->oprofile->localProfile();
+ if ($user->isSubscribed($local)) {
+ $this->showForm(_m('Already subscribed!'));
+ } elseif ($this->oprofile->subscribeLocalToRemote($user)) {
+ $this->showForm(_m('Remote user subscribed!'));
+ } else {
+ $this->showForm(_m('Remote subscription failed!'));
+ }
}
}
}
-
- function previewFeed()
+ function validateAndPreview()
{
- $profile = $this->munger->ostatusProfile();
- $notice = $this->munger->notice(0, true); // preview
-
- if ($notice) {
- $this->element('b', null, 'Preview of latest post from this feed:');
-
- $item = new NoticeList($notice, $this);
- $item->show();
- } else {
- $this->element('b', null, 'No posts in this feed yet.');
+ if ($this->validateFeed()) {
+ $this->preview = true;
+ $this->showForm(_m('Previewing feed:'));
}
}
+ function previewFeed()
+ {
+ $this->text('Profile preview should go here');
+ }
+ function showScripts()
+ {
+ parent::showScripts();
+ $this->autofocus('feedurl');
+ }
}
diff --git a/plugins/OStatus/actions/pushcallback.php b/plugins/OStatus/actions/pushcallback.php
index 388c8f9c3..7e1227a66 100644
--- a/plugins/OStatus/actions/pushcallback.php
+++ b/plugins/OStatus/actions/pushcallback.php
@@ -48,9 +48,9 @@ class PushCallbackAction extends Action
throw new ServerException('Empty or invalid feed id', 400);
}
- $profile = Ostatus_profile::staticGet('id', $feedid);
- if (!$profile) {
- throw new ServerException('Unknown OStatus/PuSH feed id ' . $feedid, 400);
+ $feedsub = FeedSub::staticGet('id', $feedid);
+ if (!$feedsub) {
+ throw new ServerException('Unknown PuSH feed id ' . $feedid, 400);
}
$hmac = '';
@@ -62,7 +62,7 @@ class PushCallbackAction extends Action
// @fixme Queue this to a background process; we should return
// as quickly as possible from a distribution POST.
- $profile->postUpdates($post, $hmac);
+ $feedsub->receive($post, $hmac);
}
/**
@@ -81,29 +81,29 @@ class PushCallbackAction extends Action
throw new ServerException("Bogus hub callback: bad mode", 404);
}
- $profile = Ostatus_profile::staticGet('feeduri', $topic);
- if (!$profile) {
+ $feedsub = FeedSub::staticGet('uri', $topic);
+ if (!$feedsub) {
common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback for unknown feed $topic");
throw new ServerException("Bogus hub callback: unknown feed", 404);
}
- if ($profile->verify_token !== $verify_token) {
+ if ($feedsub->verify_token !== $verify_token) {
common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with bad token \"$verify_token\" for feed $topic");
- throw new ServerError("Bogus hub callback: bad token", 404);
+ throw new ServerException("Bogus hub callback: bad token", 404);
}
- if ($mode != $profile->sub_state) {
- common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with bad mode \"$mode\" for feed $topic in state \"{$profile->sub_state}\"");
+ if ($mode != $feedsub->sub_state) {
+ common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with bad mode \"$mode\" for feed $topic in state \"{$feedsub->sub_state}\"");
throw new ServerException("Bogus hub callback: mode doesn't match subscription state.", 404);
}
// OK!
if ($mode == 'subscribe') {
common_log(LOG_INFO, __METHOD__ . ': sub confirmed');
- $profile->confirmSubscribe($lease_seconds);
+ $feedsub->confirmSubscribe($lease_seconds);
} else {
common_log(LOG_INFO, __METHOD__ . ": unsub confirmed; deleting sub record for $topic");
- $profile->confirmUnsubscribe();
+ $feedsub->confirmUnsubscribe();
}
print $challenge;
}
diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php
index 13ec09d52..19599d815 100644
--- a/plugins/OStatus/actions/pushhub.php
+++ b/plugins/OStatus/actions/pushhub.php
@@ -83,6 +83,7 @@ class PushHubAction extends Action
{
$feed = $this->argUrl('hub.topic');
$callback = $this->argUrl('hub.callback');
+ $token = $this->arg('hub.verify_token', null);
common_log(LOG_DEBUG, __METHOD__ . ": checking sub'd to $feed $callback");
if ($this->getSub($feed, $callback)) {
@@ -96,7 +97,6 @@ class PushHubAction extends Action
$sub = new HubSub();
$sub->topic = $feed;
$sub->callback = $callback;
- $sub->verify_token = $this->arg('hub.verify_token', null);
$sub->secret = $this->arg('hub.secret', null);
if (strlen($sub->secret) > 200) {
throw new ClientException("hub.secret must be no longer than 200 chars", 400);
@@ -115,7 +115,7 @@ class PushHubAction extends Action
// @fixme check errors ;)
- $data = array('sub' => $sub, 'mode' => 'subscribe');
+ $data = array('sub' => $sub, 'mode' => 'subscribe', 'token' => $token);
$qm = QueueManager::get();
$qm->enqueue($data, 'hubverify');
@@ -130,6 +130,8 @@ class PushHubAction extends Action
* 202 Accepted - request saved and awaiting verification
* 204 No Content - already subscribed
* 400 Bad Request - invalid params or rejected feed
+ *
+ * @fixme background this
*/
function unsubscribe()
{
@@ -138,7 +140,8 @@ class PushHubAction extends Action
$sub = $this->getSub($feed, $callback);
if ($sub) {
- if ($sub->verify('unsubscribe')) {
+ $token = $this->arg('hub.verify_token', null);
+ if ($sub->verify('unsubscribe', $token)) {
$sub->delete();
common_log(LOG_INFO, "PuSH unsubscribed $feed for $callback");
} else {
diff --git a/plugins/OStatus/actions/salmon.php b/plugins/OStatus/actions/salmon.php
deleted file mode 100644
index c79d09c95..000000000
--- a/plugins/OStatus/actions/salmon.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?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/>.
- */
-
-/**
- * @package OStatusPlugin
- * @author James Walker <james@status.net>
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-class SalmonAction extends Action
-{
- var $user = null;
- var $xml = null;
- var $activity = null;
-
- function prepare($args)
- {
- if ($_SERVER['REQUEST_METHOD'] != 'POST') {
- $this->clientError(_('This method requires a POST.'));
- }
-
- if ($_SERVER['CONTENT_TYPE'] != 'application/atom+xml') {
- $this->clientError(_('Salmon requires application/atom+xml'));
- }
-
- $id = $this->trimmed('id');
-
- if (!$id) {
- $this->clientError(_('No ID.'));
- }
-
- $this->user = User::staticGet($id);
-
- if (empty($this->user)) {
- $this->clientError(_('No such user.'));
- }
-
- $xml = file_get_contents('php://input');
-
- $dom = DOMDocument::loadXML($xml);
-
- // XXX: check that document element is Atom entry
- // XXX: check the signature
-
- $this->act = new Activity($dom->documentElement);
- }
-
- function handle($args)
- {
- common_log(LOG_DEBUG, 'Salmon: incoming post for user: '. $user_id);
-
- // TODO : Insert new $xml -> notice code
-
- switch ($this->act->verb)
- {
- case Activity::POST:
- case Activity::SHARE:
- case Activity::FAVORITE:
- case Activity::FOLLOW:
- }
- }
-}
diff --git a/plugins/OStatus/actions/usersalmon.php b/plugins/OStatus/actions/usersalmon.php
new file mode 100644
index 000000000..12c74798f
--- /dev/null
+++ b/plugins/OStatus/actions/usersalmon.php
@@ -0,0 +1,202 @@
+<?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/>.
+ */
+
+/**
+ * @package OStatusPlugin
+ * @author James Walker <james@status.net>
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class UsersalmonAction extends SalmonAction
+{
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $id = $this->trimmed('id');
+
+ if (!$id) {
+ $this->clientError(_('No ID.'));
+ }
+
+ $this->user = User::staticGet('id', $id);
+
+ if (empty($this->user)) {
+ $this->clientError(_('No such user.'));
+ }
+
+ return true;
+ }
+
+ /**
+ * We've gotten a post event on the Salmon backchannel, probably a reply.
+ *
+ * @todo validate if we need to handle this post, then call into
+ * ostatus_profile's general incoming-post handling.
+ */
+ function handlePost()
+ {
+ switch ($this->act->object->type) {
+ case ActivityObject::ARTICLE:
+ case ActivityObject::BLOGENTRY:
+ case ActivityObject::NOTE:
+ case ActivityObject::STATUS:
+ case ActivityObject::COMMENT:
+ break;
+ default:
+ throw new ClientException("Can't handle that kind of post.");
+ }
+
+ // Notice must either be a) in reply to a notice by this user
+ // or b) to the attention of this user
+
+ $context = $this->act->context;
+
+ if (!empty($context->replyToID)) {
+ $notice = Notice::staticGet('uri', $context->replyToID);
+ if (empty($notice)) {
+ throw new ClientException("In reply to unknown notice");
+ }
+ if ($notice->profile_id != $this->user->id) {
+ throw new ClientException("In reply to a notice not by this user");
+ }
+ } else if (!empty($context->attention)) {
+ if (!in_array($context->attention, $this->user->uri)) {
+ throw new ClientException("To the attention of user(s) not including this one!");
+ }
+ } else {
+ throw new ClientException("Not to anyone in reply to anything!");
+ }
+
+ $this->saveNotice();
+ }
+
+ /**
+ * We've gotten a follow/subscribe notification from a remote user.
+ * Save a subscription relationship for them.
+ */
+
+ function handleFollow()
+ {
+ $oprofile = $this->ensureProfile();
+ if ($oprofile) {
+ common_log(LOG_INFO, "Setting up subscription from remote {$oprofile->uri} to local {$this->user->nickname}");
+ Subscription::start($oprofile->localProfile(),
+ $this->user->getProfile());
+ } else {
+ common_log(LOG_INFO, "Can't set up subscription from remote; missing profile.");
+ }
+ }
+
+ /**
+ * We've gotten an unfollow/unsubscribe notification from a remote user.
+ * Check if we have a subscription relationship for them and kill it.
+ *
+ * @fixme probably catch exceptions on fail?
+ */
+ function handleUnfollow()
+ {
+ $oprofile = $this->ensureProfile();
+ if ($oprofile) {
+ common_log(LOG_INFO, "Canceling subscription from remote {$oprofile->uri} to local {$this->user->nickname}");
+ Subscription::cancel($oprofile->localProfile(), $this->user->getProfile());
+ } else {
+ common_log(LOG_ERR, "Can't cancel subscription from remote, didn't find the profile");
+ }
+ }
+
+ /**
+ * Remote user likes one of our posts.
+ * Confirm the post is ours, and save a local favorite event.
+ */
+
+ function handleFavorite()
+ {
+ $notice = $this->getNotice($this->act->object);
+ $profile = $this->ensureProfile()->localProfile();
+
+ $old = Fave::pkeyGet(array('user_id' => $profile->id,
+ 'notice_id' => $notice->id));
+
+ if (!empty($old)) {
+ throw new ClientException("We already know that's a fave!");
+ }
+
+ if (!Fave::addNew($profile, $notice)) {
+ throw new ClientException("Could not save new favorite.");
+ }
+ }
+
+ /**
+ * Remote user doesn't like one of our posts after all!
+ * Confirm the post is ours, and save a local favorite event.
+ */
+ function handleUnfavorite()
+ {
+ $notice = $this->getNotice($this->act->object);
+ $profile = $this->ensureProfile()->localProfile();
+
+ $fave = Fave::pkeyGet(array('user_id' => $profile->id,
+ 'notice_id' => $notice->id));
+ if (empty($fave)) {
+ throw new ClientException("Notice wasn't favorited!");
+ }
+
+ $fave->delete();
+ }
+
+ /**
+ * @param ActivityObject $object
+ * @return Notice
+ * @throws ClientException on invalid input
+ */
+ function getNotice($object)
+ {
+ if (!$object) {
+ throw new ClientException("Can't favorite/unfavorite without an object.");
+ }
+
+ switch ($object->type) {
+ case ActivityObject::ARTICLE:
+ case ActivityObject::BLOGENTRY:
+ case ActivityObject::NOTE:
+ case ActivityObject::STATUS:
+ case ActivityObject::COMMENT:
+ break;
+ default:
+ throw new ClientException("Can't handle that kind of object for liking/faving.");
+ }
+
+ $notice = Notice::staticGet('uri', $object->id);
+
+ if (empty($notice)) {
+ throw new ClientException("Notice with ID $object->id unknown.");
+ }
+
+ if ($notice->profile_id != $this->user->id) {
+ throw new ClientException("Notice with ID $object->id not posted by $this->user->id.");
+ }
+
+ return $notice;
+ }
+
+}
diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/webfinger.php
index 75ba16638..f4dc61b7d 100644
--- a/plugins/OStatus/actions/webfinger.php
+++ b/plugins/OStatus/actions/webfinger.php
@@ -66,7 +66,7 @@ class WebfingerAction extends Action
'href' => $salmon_url);
// TODO - finalize where the redirect should go on the publisher
- $url = common_local_url('ostatussub') . '?feed={uri}';
+ $url = common_local_url('ostatussub') . '?profile={uri}';
$xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe',
'template' => $url );
diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php
new file mode 100644
index 000000000..bf9d063fa
--- /dev/null
+++ b/plugins/OStatus/classes/FeedSub.php
@@ -0,0 +1,443 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009-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/>.
+ */
+
+/**
+ * @package OStatusPlugin
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+
+/*
+PuSH subscription flow:
+
+ $profile->subscribe()
+ generate random verification token
+ save to verify_token
+ sends a sub request to the hub...
+
+ main/push/callback
+ hub sends confirmation back to us via GET
+ We verify the request, then echo back the challenge.
+ On our end, we save the time we subscribed and the lease expiration
+
+ main/push/callback
+ hub sends us updates via POST
+
+*/
+
+class FeedDBException extends FeedSubException
+{
+ public $obj;
+
+ function __construct($obj)
+ {
+ parent::__construct('Database insert failure');
+ $this->obj = $obj;
+ }
+}
+
+/**
+ * FeedSub handles low-level PubHubSubbub (PuSH) subscriptions.
+ * Higher-level behavior building OStatus stuff on top is handled
+ * under Ostatus_profile.
+ */
+class FeedSub extends Memcached_DataObject
+{
+ public $__table = 'feedsub';
+
+ public $id;
+ public $feeduri;
+
+ // PuSH subscription data
+ public $huburi;
+ public $secret;
+ public $verify_token;
+ public $sub_state; // subscribe, active, unsubscribe, inactive
+ public $sub_start;
+ public $sub_end;
+ public $last_update;
+
+ public $created;
+ public $modified;
+
+ public /*static*/ function staticGet($k, $v=null)
+ {
+ return parent::staticGet(__CLASS__, $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'huburi' => DB_DATAOBJECT_STR,
+ 'secret' => DB_DATAOBJECT_STR,
+ 'verify_token' => DB_DATAOBJECT_STR,
+ 'sub_state' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
+ 'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
+ 'last_update' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
+ '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);
+ }
+
+ static function schemaDef()
+ {
+ return array(new ColumnDef('id', 'integer',
+ /*size*/ null,
+ /*nullable*/ false,
+ /*key*/ 'PRI',
+ /*default*/ '0',
+ /*extra*/ null,
+ /*auto_increment*/ true),
+ new ColumnDef('uri', 'varchar',
+ 255, false, 'UNI'),
+ new ColumnDef('huburi', 'text',
+ null, true),
+ new ColumnDef('verify_token', 'text',
+ null, true),
+ new ColumnDef('secret', 'text',
+ null, true),
+ new ColumnDef('sub_state', "enum('subscribe','active','unsubscribe','inactive')",
+ null, false),
+ new ColumnDef('sub_start', 'datetime',
+ null, true),
+ new ColumnDef('sub_end', 'datetime',
+ null, true),
+ new ColumnDef('last_update', 'datetime',
+ null, false),
+ new ColumnDef('created', 'datetime',
+ null, false),
+ new ColumnDef('modified', 'datetime',
+ null, false));
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has; this function
+ * defines them.
+ *
+ * @return array key definitions
+ */
+
+ function keys()
+ {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them.
+ *
+ * @return array key definitions
+ */
+
+ function keyTypes()
+ {
+ return array('id' => 'K', 'uri' => 'U');
+ }
+
+ function sequenceKey()
+ {
+ return array('id', true, false);
+ }
+
+ /**
+ * Fetch the StatusNet-side profile for this feed
+ * @return Profile
+ */
+ public function localProfile()
+ {
+ if ($this->profile_id) {
+ return Profile::staticGet('id', $this->profile_id);
+ }
+ return null;
+ }
+
+ /**
+ * Fetch the StatusNet-side profile for this feed
+ * @return Profile
+ */
+ public function localGroup()
+ {
+ if ($this->group_id) {
+ return User_group::staticGet('id', $this->group_id);
+ }
+ return null;
+ }
+
+ /**
+ * @param string $feeduri
+ * @return FeedSub
+ * @throws FeedSubException if feed is invalid or lacks PuSH setup
+ */
+ public static function ensureFeed($feeduri)
+ {
+ $current = self::staticGet('uri', $feeduri);
+ if ($current) {
+ return $current;
+ }
+
+ $discover = new FeedDiscovery();
+ $discover->discoverFromFeedURL($feeduri);
+
+ $huburi = $discover->getAtomLink('hub');
+ if (!$huburi) {
+ throw new FeedSubNoHubException();
+ }
+
+ $feedsub = new FeedSub();
+ $feedsub->uri = $feeduri;
+ $feedsub->huburi = $huburi;
+ $feedsub->sub_state = 'inactive';
+
+ $feedsub->created = common_sql_now();
+ $feedsub->modified = common_sql_now();
+
+ $result = $feedsub->insert();
+ if (empty($result)) {
+ throw new FeedDBException($feedsub);
+ }
+
+ return $feedsub;
+ }
+
+ /**
+ * Send a subscription request to the hub for this feed.
+ * The hub will later send us a confirmation POST to /main/push/callback.
+ *
+ * @return bool true on success, false on failure
+ * @throws ServerException if feed state is not valid
+ */
+ public function subscribe($mode='subscribe')
+ {
+ if ($this->sub_state && $this->sub_state != 'inactive') {
+ throw new ServerException("Attempting to start PuSH subscription to feed in state $this->sub_state");
+ }
+ if (empty($this->huburi)) {
+ if (common_config('feedsub', 'nohub')) {
+ // Fake it! We're just testing remote feeds w/o hubs.
+ return true;
+ } else {
+ throw new ServerException("Attempting to start PuSH subscription for feed with no hub");
+ }
+ }
+
+ return $this->doSubscribe('subscribe');
+ }
+
+ /**
+ * Send a PuSH unsubscription request to the hub for this feed.
+ * The hub will later send us a confirmation POST to /main/push/callback.
+ *
+ * @return bool true on success, false on failure
+ * @throws ServerException if feed state is not valid
+ */
+ public function unsubscribe() {
+ if ($this->sub_state != 'active') {
+ throw new ServerException("Attempting to end PuSH subscription to feed in state $this->sub_state");
+ }
+ if (empty($this->huburi)) {
+ if (common_config('feedsub', 'nohub')) {
+ // Fake it! We're just testing remote feeds w/o hubs.
+ return true;
+ } else {
+ throw new ServerException("Attempting to end PuSH subscription for feed with no hub");
+ }
+ }
+
+ return $this->doSubscribe('unsubscribe');
+ }
+
+ protected function doSubscribe($mode)
+ {
+ $orig = clone($this);
+ $this->verify_token = common_good_rand(16);
+ if ($mode == 'subscribe') {
+ $this->secret = common_good_rand(32);
+ }
+ $this->sub_state = $mode;
+ $this->update($orig);
+ unset($orig);
+
+ try {
+ $callback = common_local_url('pushcallback', array('feed' => $this->id));
+ $headers = array('Content-Type: application/x-www-form-urlencoded');
+ $post = array('hub.mode' => $mode,
+ 'hub.callback' => $callback,
+ 'hub.verify' => 'async',
+ 'hub.verify_token' => $this->verify_token,
+ 'hub.secret' => $this->secret,
+ //'hub.lease_seconds' => 0,
+ 'hub.topic' => $this->uri);
+ $client = new HTTPClient();
+ $response = $client->post($this->huburi, $headers, $post);
+ $status = $response->getStatus();
+ if ($status == 202) {
+ common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
+ return true;
+ } else if ($status == 204) {
+ common_log(LOG_INFO, __METHOD__ . ': sub req ok and verified');
+ return true;
+ } else if ($status >= 200 && $status < 300) {
+ common_log(LOG_ERR, __METHOD__ . ": sub req returned unexpected HTTP $status: " . $response->getBody());
+ return false;
+ } else {
+ common_log(LOG_ERR, __METHOD__ . ": sub req failed with HTTP $status: " . $response->getBody());
+ return false;
+ }
+ } catch (Exception $e) {
+ // wtf!
+ common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->uri");
+
+ $orig = clone($this);
+ $this->verify_token = null;
+ $this->sub_state = null;
+ $this->update($orig);
+ unset($orig);
+
+ return false;
+ }
+ }
+
+ /**
+ * Save PuSH subscription confirmation.
+ * Sets approximate lease start and end times and finalizes state.
+ *
+ * @param int $lease_seconds provided hub.lease_seconds parameter, if given
+ */
+ public function confirmSubscribe($lease_seconds=0)
+ {
+ $original = clone($this);
+
+ $this->sub_state = 'active';
+ $this->sub_start = common_sql_date(time());
+ if ($lease_seconds > 0) {
+ $this->sub_end = common_sql_date(time() + $lease_seconds);
+ } else {
+ $this->sub_end = null;
+ }
+ $this->lastupdate = common_sql_now();
+
+ return $this->update($original);
+ }
+
+ /**
+ * Save PuSH unsubscription confirmation.
+ * Wipes active PuSH sub info and resets state.
+ */
+ public function confirmUnsubscribe()
+ {
+ $original = clone($this);
+
+ // @fixme these should all be null, but DB_DataObject doesn't save null values...?????
+ $this->verify_token = '';
+ $this->secret = '';
+ $this->sub_state = '';
+ $this->sub_start = '';
+ $this->sub_end = '';
+ $this->lastupdate = common_sql_now();
+
+ return $this->update($original);
+ }
+
+ /**
+ * Accept updates from a PuSH feed. If validated, this object and the
+ * feed (as a DOMDocument) will be passed to the StartFeedSubHandleFeed
+ * and EndFeedSubHandleFeed events for processing.
+ *
+ * @param string $post source of Atom or RSS feed
+ * @param string $hmac X-Hub-Signature header, if present
+ */
+ public function receive($post, $hmac)
+ {
+ common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->uri\"! $hmac $post");
+
+ if ($this->sub_state != 'active') {
+ common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed $this->uri (in state '$this->sub_state')");
+ return;
+ }
+
+ if ($post === '') {
+ common_log(LOG_ERR, __METHOD__ . ": ignoring empty post");
+ return;
+ }
+
+ if (!$this->validatePushSig($post, $hmac)) {
+ // Per spec we silently drop input with a bad sig,
+ // while reporting receipt to the server.
+ return;
+ }
+
+ $feed = new DOMDocument();
+ if (!$feed->loadXML($post)) {
+ // @fixme might help to include the err message
+ common_log(LOG_ERR, __METHOD__ . ": ignoring invalid XML");
+ return;
+ }
+
+ Event::handle('StartFeedSubReceive', array($this, $feed));
+ Event::handle('EndFeedSubReceive', array($this, $feed));
+ }
+
+ /**
+ * Validate the given Atom chunk and HMAC signature against our
+ * shared secret that was set up at subscription time.
+ *
+ * If we don't have a shared secret, there should be no signature.
+ * If we we do, our the calculated HMAC should match theirs.
+ *
+ * @param string $post raw XML source as POSTed to us
+ * @param string $hmac X-Hub-Signature HTTP header value, or empty
+ * @return boolean true for a match
+ */
+ protected function validatePushSig($post, $hmac)
+ {
+ if ($this->secret) {
+ if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) {
+ $their_hmac = strtolower($matches[1]);
+ $our_hmac = hash_hmac('sha1', $post, $this->secret);
+ if ($their_hmac === $our_hmac) {
+ return true;
+ }
+ common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac");
+ } else {
+ common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
+ }
+ } else {
+ if (empty($hmac)) {
+ return true;
+ } else {
+ common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'");
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php
index 7071ee5b4..0cd4281f8 100644
--- a/plugins/OStatus/classes/HubSub.php
+++ b/plugins/OStatus/classes/HubSub.php
@@ -30,7 +30,6 @@ class HubSub extends Memcached_DataObject
public $topic;
public $callback;
public $secret;
- public $verify_token;
public $challenge;
public $lease;
public $sub_start;
@@ -62,7 +61,6 @@ class HubSub extends Memcached_DataObject
'topic' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'callback' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'secret' => DB_DATAOBJECT_STR,
- 'verify_token' => DB_DATAOBJECT_STR,
'challenge' => DB_DATAOBJECT_STR,
'lease' => DB_DATAOBJECT_INT,
'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
@@ -84,8 +82,6 @@ class HubSub extends Memcached_DataObject
255, false),
new ColumnDef('secret', 'text',
null, true),
- new ColumnDef('verify_token', 'text',
- null, true),
new ColumnDef('challenge', 'varchar',
32, true),
new ColumnDef('lease', 'int',
@@ -154,8 +150,9 @@ class HubSub extends Memcached_DataObject
/**
* Send a verification ping to subscriber
* @param string $mode 'subscribe' or 'unsubscribe'
+ * @param string $token hub.verify_token value, if provided by client
*/
- function verify($mode)
+ function verify($mode, $token=null)
{
assert($mode == 'subscribe' || $mode == 'unsubscribe');
@@ -172,8 +169,8 @@ class HubSub extends Memcached_DataObject
if ($mode == 'subscribe') {
$params['hub.lease_seconds'] = $this->lease;
}
- if ($this->verify_token) {
- $params['hub.verify_token'] = $this->verify_token;
+ if ($token !== null) {
+ $params['hub.verify_token'] = $token;
}
$url = $this->callback . '?' . http_build_query($params, '', '&'); // @fixme ugly urls
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index be01cdfe1..aab316c34 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -18,62 +18,24 @@
*/
/**
- * @package FeedSubPlugin
+ * @package OStatusPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
-/*
-PuSH subscription flow:
-
- $profile->subscribe()
- generate random verification token
- save to verify_token
- sends a sub request to the hub...
-
- main/push/callback
- hub sends confirmation back to us via GET
- We verify the request, then echo back the challenge.
- On our end, we save the time we subscribed and the lease expiration
-
- main/push/callback
- hub sends us updates via POST
-
-*/
-
-class FeedDBException extends FeedSubException
-{
- public $obj;
-
- function __construct($obj)
- {
- parent::__construct('Database insert failure');
- $this->obj = $obj;
- }
-}
-
class Ostatus_profile extends Memcached_DataObject
{
public $__table = 'ostatus_profile';
- public $id;
+ public $uri;
+
public $profile_id;
public $group_id;
public $feeduri;
- public $homeuri;
-
- // PuSH subscription data
- public $huburi;
- public $secret;
- public $verify_token;
- public $sub_state; // subscribe, active, unsubscribe
- public $sub_start;
- public $sub_end;
-
public $salmonuri;
public $created;
- public $lastupdate;
+ public $modified;
public /*static*/ function staticGet($k, $v=null)
{
@@ -91,56 +53,30 @@ class Ostatus_profile extends Memcached_DataObject
function table()
{
- return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ return array('uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'profile_id' => DB_DATAOBJECT_INT,
'group_id' => DB_DATAOBJECT_INT,
- 'feeduri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
- 'homeuri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
- 'huburi' => DB_DATAOBJECT_STR,
- 'secret' => DB_DATAOBJECT_STR,
- 'verify_token' => DB_DATAOBJECT_STR,
- 'sub_state' => DB_DATAOBJECT_STR,
- 'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
- 'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
+ 'feeduri' => DB_DATAOBJECT_STR,
'salmonuri' => DB_DATAOBJECT_STR,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
- 'lastupdate' => 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);
}
static function schemaDef()
{
- return array(new ColumnDef('id', 'integer',
- /*size*/ null,
- /*nullable*/ false,
- /*key*/ 'PRI',
- /*default*/ '0',
- /*extra*/ null,
- /*auto_increment*/ true),
+ return array(new ColumnDef('uri', 'varchar',
+ 255, false, 'PRI'),
new ColumnDef('profile_id', 'integer',
null, true, 'UNI'),
new ColumnDef('group_id', 'integer',
null, true, 'UNI'),
new ColumnDef('feeduri', 'varchar',
- 255, false, 'UNI'),
- new ColumnDef('homeuri', 'varchar',
- 255, false),
- new ColumnDef('huburi', 'text',
- null, true),
- new ColumnDef('verify_token', 'varchar',
- 32, true),
- new ColumnDef('secret', 'varchar',
- 64, true),
- new ColumnDef('sub_state', "enum('subscribe','active','unsubscribe')",
- null, true),
- new ColumnDef('sub_start', 'datetime',
- null, true),
- new ColumnDef('sub_end', 'datetime',
- null, true),
+ 255, true, 'UNI'),
new ColumnDef('salmonuri', 'text',
null, true),
new ColumnDef('created', 'datetime',
null, false),
- new ColumnDef('lastupdate', 'datetime',
+ new ColumnDef('modified', 'datetime',
null, false));
}
@@ -169,12 +105,12 @@ class Ostatus_profile extends Memcached_DataObject
function keyTypes()
{
- return array('id' => 'K', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U');
+ return array('uri' => 'K', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U');
}
function sequenceKey()
{
- return array('id', true, false);
+ return array(false, false, false);
}
/**
@@ -202,101 +138,6 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * @param FeedMunger $munger
- * @param boolean $isGroup is this a group record?
- * @return Ostatus_profile
- */
- public static function ensureProfile($munger)
- {
- $profile = $munger->ostatusProfile();
-
- $current = self::staticGet('feeduri', $profile->feeduri);
- if ($current) {
- // @fixme we should probably update info as necessary
- return $current;
- }
-
- $profile->query('BEGIN');
-
- try {
- $local = $munger->profile();
-
- if ($profile->isGroup()) {
- $group = new User_group();
- $group->nickname = $local->nickname . '@remote'; // @fixme
- $group->fullname = $local->fullname;
- $group->homepage = $local->homepage;
- $group->location = $local->location;
- $group->created = $local->created;
- $group->insert();
- if (empty($result)) {
- throw new FeedDBException($group);
- }
- $profile->group_id = $group->id;
- } else {
- $result = $local->insert();
- if (empty($result)) {
- throw new FeedDBException($local);
- }
- $profile->profile_id = $local->id;
- }
-
- $profile->created = common_sql_now();
- $profile->lastupdate = common_sql_now();
- $result = $profile->insert();
- if (empty($result)) {
- throw new FeedDBException($profile);
- }
-
- $profile->query('COMMIT');
- } catch (FeedDBException $e) {
- common_log_db_error($e->obj, 'INSERT', __FILE__);
- $profile->query('ROLLBACK');
- return false;
- }
-
- $avatar = $munger->getAvatar();
- if ($avatar) {
- try {
- $profile->updateAvatar($avatar);
- } catch (Exception $e) {
- common_log(LOG_ERR, "Exception setting OStatus avatar: " .
- $e->getMessage());
- }
- }
-
- return $profile;
- }
-
- /**
- * Download and update given avatar image
- * @param string $url
- * @throws Exception in various failure cases
- */
- public function updateAvatar($url)
- {
- // @fixme this should be better encapsulated
- // ripped from oauthstore.php (for old OMB client)
- $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
- copy($url, $temp_filename);
-
- // @fixme should we be using different ids?
- $imagefile = new ImageFile($this->id, $temp_filename);
- $filename = Avatar::filename($this->id,
- image_type_to_extension($imagefile->type),
- null,
- common_timestamp());
- rename($temp_filename, Avatar::path($filename));
- if ($this->isGroup()) {
- $group = $this->localGroup();
- $group->setOriginal($filename);
- } else {
- $profile = $this->localProfile();
- $profile->setOriginal($filename);
- }
- }
-
- /**
* Returns an XML string fragment with profile information as an
* Activity Streams noun object with the given element type.
*
@@ -308,7 +149,6 @@ class Ostatus_profile extends Memcached_DataObject
function asActivityNoun($element)
{
$xs = new XMLStringer(true);
-
$avatarHref = Avatar::defaultImage(AVATAR_PROFILE_SIZE);
$avatarType = 'image/png';
if ($this->isGroup()) {
@@ -332,8 +172,8 @@ class Ostatus_profile extends Memcached_DataObject
$self = $this->localProfile();
$avatar = $self->getAvatar(AVATAR_PROFILE_SIZE);
if ($avatar) {
- $avatarHref = $avatar->
- $avatarType = $avatar->mediatype;
+ $avatarHref = $avatar->url;
+ $avatarType = $avatar->mediatype;
}
}
$xs->elementStart('activity:' . $element);
@@ -345,7 +185,7 @@ class Ostatus_profile extends Memcached_DataObject
$xs->element(
'id',
null,
- $this->homeuri); // ?
+ $this->uri); // ?
$xs->element('title', null, $self->getBestName());
$xs->element(
@@ -370,188 +210,165 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * Send a subscription request to the hub for this feed.
- * The hub will later send us a confirmation POST to /main/push/callback.
+ * Subscribe a local user to this remote user.
+ * PuSH subscription will be started if necessary, and we'll
+ * send a Salmon notification to the remote server if available
+ * notifying them of the sub.
*
- * @return bool true on success, false on failure
- * @throws ServerException if feed state is not valid
+ * @param User $user
+ * @return boolean success
+ * @throws FeedException
*/
- public function subscribe($mode='subscribe')
+ public function subscribeLocalToRemote(User $user)
{
- if ($this->sub_state != '') {
- throw new ServerException("Attempting to start PuSH subscription to feed in state $this->sub_state");
+ if ($this->isGroup()) {
+ throw new ServerException("Can't subscribe to a remote group");
}
- if (empty($this->huburi)) {
- if (common_config('feedsub', 'nohub')) {
- // Fake it! We're just testing remote feeds w/o hubs.
+
+ if ($this->subscribe()) {
+ if ($user->subscribeTo($this->localProfile())) {
+ $this->notify($user->getProfile(), ActivityVerb::FOLLOW, $this);
return true;
- } else {
- throw new ServerException("Attempting to start PuSH subscription for feed with no hub");
}
}
-
- return $this->doSubscribe('subscribe');
+ return false;
}
/**
- * Send a PuSH unsubscription request to the hub for this feed.
- * The hub will later send us a confirmation POST to /main/push/callback.
+ * Mark this remote profile as subscribing to the given local user,
+ * and send appropriate notifications to the user.
*
- * @return bool true on success, false on failure
- * @throws ServerException if feed state is not valid
+ * This will generally be in response to a subscription notification
+ * from a foreign site to our local Salmon response channel.
+ *
+ * @param User $user
+ * @return boolean success
*/
- public function unsubscribe() {
- if ($this->sub_state != 'active') {
- throw new ServerException("Attempting to end PuSH subscription to feed in state $this->sub_state");
- }
- if (empty($this->huburi)) {
- if (common_config('feedsub', 'nohub')) {
- // Fake it! We're just testing remote feeds w/o hubs.
- return true;
- } else {
- throw new ServerException("Attempting to end PuSH subscription for feed with no hub");
- }
- }
-
- return $this->doSubscribe('unsubscribe');
- }
-
- protected function doSubscribe($mode)
+ public function subscribeRemoteToLocal(User $user)
{
- $orig = clone($this);
- $this->verify_token = common_good_rand(16);
- if ($mode == 'subscribe') {
- $this->secret = common_good_rand(32);
+ if ($this->isGroup()) {
+ throw new ServerException("Remote groups can't subscribe to local users");
}
- $this->sub_state = $mode;
- $this->update($orig);
- unset($orig);
- try {
- $callback = common_local_url('pushcallback', array('feed' => $this->id));
- $headers = array('Content-Type: application/x-www-form-urlencoded');
- $post = array('hub.mode' => $mode,
- 'hub.callback' => $callback,
- 'hub.verify' => 'async',
- 'hub.verify_token' => $this->verify_token,
- 'hub.secret' => $this->secret,
- //'hub.lease_seconds' => 0,
- 'hub.topic' => $this->feeduri);
- $client = new HTTPClient();
- $response = $client->post($this->huburi, $headers, $post);
- $status = $response->getStatus();
- if ($status == 202) {
- common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
- return true;
- } else if ($status == 204) {
- common_log(LOG_INFO, __METHOD__ . ': sub req ok and verified');
- return true;
- } else if ($status >= 200 && $status < 300) {
- common_log(LOG_ERR, __METHOD__ . ": sub req returned unexpected HTTP $status: " . $response->getBody());
- return false;
- } else {
- common_log(LOG_ERR, __METHOD__ . ": sub req failed with HTTP $status: " . $response->getBody());
- return false;
- }
- } catch (Exception $e) {
- // wtf!
- common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->feeduri");
+ // @fixme use regular channels for subbing, once they accept remote profiles
+ $sub = new Subscription();
+ $sub->subscriber = $this->profile_id;
+ $sub->subscribed = $user->id;
+ $sub->created = common_sql_now(); // current time
- $orig = clone($this);
- $this->verify_token = null;
- $this->sub_state = null;
- $this->update($orig);
- unset($orig);
-
- return false;
+ if ($sub->insert()) {
+ // @fixme use subs_notify() if refactored to take profiles?
+ mail_subscribe_notify_profile($user, $this->localProfile());
+ return true;
}
+ return false;
}
/**
- * Save PuSH subscription confirmation.
- * Sets approximate lease start and end times and finalizes state.
+ * Send a subscription request to the hub for this feed.
+ * The hub will later send us a confirmation POST to /main/push/callback.
*
- * @param int $lease_seconds provided hub.lease_seconds parameter, if given
+ * @return bool true on success, false on failure
+ * @throws ServerException if feed state is not valid
*/
- public function confirmSubscribe($lease_seconds=0)
+ public function subscribe()
{
- $original = clone($this);
-
- $this->sub_state = 'active';
- $this->sub_start = common_sql_date(time());
- if ($lease_seconds > 0) {
- $this->sub_end = common_sql_date(time() + $lease_seconds);
- } else {
- $this->sub_end = null;
+ $feedsub = FeedSub::ensureFeed($this->feeduri);
+ if ($feedsub->sub_state == 'active' || $feedsub->sub_state == 'subscribe') {
+ return true;
+ } else if ($feedsub->sub_state == '' || $feedsub->sub_state == 'inactive') {
+ return $feedsub->subscribe();
+ } else if ('unsubscribe') {
+ throw new FeedSubException("Unsub is pending, can't subscribe...");
}
- $this->lastupdate = common_sql_date();
-
- return $this->update($original);
}
/**
- * Save PuSH unsubscription confirmation.
- * Wipes active PuSH sub info and resets state.
+ * Send a PuSH unsubscription request to the hub for this feed.
+ * The hub will later send us a confirmation POST to /main/push/callback.
+ *
+ * @return bool true on success, false on failure
+ * @throws ServerException if feed state is not valid
*/
- public function confirmUnsubscribe()
- {
- $original = clone($this);
-
- $this->verify_token = null;
- $this->secret = null;
- $this->sub_state = null;
- $this->sub_start = null;
- $this->sub_end = null;
- $this->lastupdate = common_sql_date();
-
- return $this->update($original);
+ public function unsubscribe() {
+ $feedsub = FeedSub::staticGet('uri', $this->feeduri);
+ if ($feedsub->sub_state == 'active') {
+ return $feedsub->unsubscribe();
+ } else if ($feedsub->sub_state == '' || $feedsub->sub_state == 'inactive' || $feedsub->sub_state == 'unsubscribe') {
+ return true;
+ } else if ($feedsub->sub_state == 'subscribe') {
+ throw new FeedSubException("Feed is awaiting subscription, can't unsub...");
+ }
}
/**
* Send an Activity Streams notification to the remote Salmon endpoint,
* if so configured.
*
- * @param Profile $actor
- * @param $verb eg Activity::SUBSCRIBE or Activity::JOIN
- * @param $object object of the action; if null, the remote entity itself is assumed
+ * @param Profile $actor Actor who did the activity
+ * @param string $verb Activity::SUBSCRIBE or Activity::JOIN
+ * @param Object $object object of the action; must define asActivityNoun($tag)
*/
- public function notify(Profile $actor, $verb, $object=null)
+ public function notify($actor, $verb, $object=null)
{
+ if (!($actor instanceof Profile)) {
+ $type = gettype($actor);
+ if ($type == 'object') {
+ $type = get_class($actor);
+ }
+ throw new ServerException("Invalid actor passed to " . __METHOD__ . ": " . $type);
+ }
if ($object == null) {
$object = $this;
}
if ($this->salmonuri) {
- $text = 'update'; // @fixme
- $id = 'tag:' . common_config('site', 'server') .
- ':' . $verb .
- ':' . $actor->id .
- ':' . time(); // @fixme
-
- $entry = new Atom10Entry();
- $entry->elementStart('entry');
+
+ $text = 'update';
+ $id = TagURI::mint('%s:%s:%s',
+ $verb,
+ $actor->getURI(),
+ common_date_iso8601(time()));
+
+ // @fixme consolidate all these NS settings somewhere
+ $attributes = array('xmlns' => Activity::ATOM,
+ 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
+ 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
+ 'xmlns:georss' => 'http://www.georss.org/georss',
+ 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
+
+ $entry = new XMLStringer();
+ $entry->elementStart('entry', $attributes);
$entry->element('id', null, $id);
$entry->element('title', null, $text);
$entry->element('summary', null, $text);
- $entry->element('published', null, common_date_w3dtf());
+ $entry->element('published', null, common_date_w3dtf(common_sql_now()));
$entry->element('activity:verb', null, $verb);
- $entry->raw($profile->asAtomAuthor());
- $entry->raw($profile->asActivityActor());
+ $entry->raw($actor->asAtomAuthor());
+ $entry->raw($actor->asActivityActor());
$entry->raw($object->asActivityNoun('object'));
- $entry->elmentEnd('entry');
+ $entry->elementEnd('entry');
- $feed = $this->atomFeed($actor);
- $feed->initFeed();
- $feed->addEntry($entry);
- $feed->renderEntries();
- $feed->endFeed();
+ $xml = $entry->getString();
+ common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml");
- $xml = $feed->getString();
- common_log(LOG_INFO, "Posting to Salmon endpoint $salmon: $xml");
+ $salmon = new Salmon(); // ?
+ $salmon->post($this->salmonuri, $xml);
+ }
+ }
+
+ public function notifyActivity($activity)
+ {
+ if ($this->salmonuri) {
+
+ $xml = $activity->asString();
$salmon = new Salmon(); // ?
+
$salmon->post($this->salmonuri, $xml);
}
+
+ return;
}
function getBestName()
@@ -568,7 +385,7 @@ class Ostatus_profile extends Memcached_DataObject
$feed = new Atom10Feed();
// @fixme should these be set up somewhere else?
$feed->addNamespace('activity', 'http://activitystrea.ms/spec/1.0/');
- $feed->addNamesapce('thr', 'http://purl.org/syndication/thread/1.0');
+ $feed->addNamespace('thr', 'http://purl.org/syndication/thread/1.0');
$feed->addNamespace('georss', 'http://www.georss.org/georss');
$feed->addNamespace('ostatus', 'http://ostatus.org/schema/1.0');
@@ -579,14 +396,14 @@ class Ostatus_profile extends Memcached_DataObject
$feed->setUpdated(time());
$feed->setPublished(time());
- $feed->addLink(common_url('ApiTimelineUser',
- array('id' => $actor->id,
- 'type' => 'atom')),
+ $feed->addLink(common_local_url('ApiTimelineUser',
+ array('id' => $actor->id,
+ 'type' => 'atom')),
array('rel' => 'self',
'type' => 'application/atom+xml'));
- $feed->addLink(common_url('userbyid',
- array('id' => $actor->id)),
+ $feed->addLink(common_local_url('userbyid',
+ array('id' => $actor->id)),
array('rel' => 'alternate',
'type' => 'text/html'));
@@ -598,36 +415,10 @@ class Ostatus_profile extends Memcached_DataObject
* Currently assumes that all items in the feed are new,
* coming from a PuSH hub.
*
- * @param string $post source of Atom or RSS feed
- * @param string $hmac X-Hub-Signature header, if present
+ * @param DOMDocument $feed
*/
- public function postUpdates($post, $hmac)
+ public function processFeed($feed)
{
- common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $post");
-
- if ($this->sub_state != 'active') {
- common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed $this->feeduri (in state '$this->sub_state')");
- return;
- }
-
- if ($post === '') {
- common_log(LOG_ERR, __METHOD__ . ": ignoring empty post");
- return;
- }
-
- if (!$this->validatePushSig($post, $hmac)) {
- // Per spec we silently drop input with a bad sig,
- // while reporting receipt to the server.
- return;
- }
-
- $feed = new DOMDocument();
- if (!$feed->loadXML($post)) {
- // @fixme might help to include the err message
- common_log(LOG_ERR, __METHOD__ . ": ignoring invalid XML");
- return;
- }
-
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
if ($entries->length == 0) {
common_log(LOG_ERR, __METHOD__ . ": no entries in feed update, ignoring");
@@ -641,40 +432,6 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * Validate the given Atom chunk and HMAC signature against our
- * shared secret that was set up at subscription time.
- *
- * If we don't have a shared secret, there should be no signature.
- * If we we do, our the calculated HMAC should match theirs.
- *
- * @param string $post raw XML source as POSTed to us
- * @param string $hmac X-Hub-Signature HTTP header value, or empty
- * @return boolean true for a match
- */
- protected function validatePushSig($post, $hmac)
- {
- if ($this->secret) {
- if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) {
- $their_hmac = strtolower($matches[1]);
- $our_hmac = hash_hmac('sha1', $post, $this->secret);
- if ($their_hmac === $our_hmac) {
- return true;
- }
- common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac");
- } else {
- common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
- }
- } else {
- if (empty($hmac)) {
- return true;
- } else {
- common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'");
- }
- }
- return false;
- }
-
- /**
* Process a posted entry from this feed source.
*
* @param DOMElement $entry
@@ -702,41 +459,46 @@ class Ostatus_profile extends Memcached_DataObject
{
if ($this->isGroup()) {
// @fixme validate these profiles in some way!
- $oprofile = $this->ensureActorProfile($activity);
+ $oprofile = self::ensureActorProfile($activity);
} else {
- $actorUri = $this->getActorProfileURI($activity);
- if ($actorUri == $this->homeuri) {
+ $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->homeuri");
+ common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->uri");
//return;
}
$oprofile = $this;
}
- if ($activity->object->link) {
- $sourceUri = $activity->object->link;
- } else if (preg_match('!^https?://!', $activity->object->id)) {
- $sourceUri = $activity->object->id;
- } else {
- common_log(LOG_INFO, "OStatus: ignoring post with no source link: id $activity->object->id");
- return;
- }
+ $sourceUri = $activity->object->id;
$dupe = Notice::staticGet('uri', $sourceUri);
+
if ($dupe) {
- common_log(LOG_INFO, "OStatus: ignoring duplicate post: $noticeLink");
+ common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri");
return;
}
+ $sourceUrl = null;
+
+ if ($activity->object->link) {
+ $sourceUrl = $activity->object->link;
+ } else if (preg_match('!^https?://!', $activity->object->id)) {
+ $sourceUrl = $activity->object->id;
+ }
+
// @fixme sanitize and save HTML content if available
+
$content = $activity->object->title;
$params = array('is_local' => Notice::REMOTE_OMB,
+ 'url' => $sourceUrl,
'uri' => $sourceUri);
- $location = $this->getEntryLocation($activity->entry);
+ $location = $activity->context->location;
+
if ($location) {
$params['lat'] = $location->lat;
$params['lon'] = $location->lon;
@@ -756,33 +518,113 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * Parse location given as a GeoRSS-simple point, if provided.
- * http://www.georss.org/simple
- *
- * @param feed item $entry
- * @return mixed Location or false
+ * @param string $profile_url
+ * @return Ostatus_profile
+ * @throws FeedSubException
*/
- function getLocation($dom)
- {
- $points = $dom->getElementsByTagNameNS('http://www.georss.org/georss', 'point');
-
- for ($i = 0; $i < $points->length; $i++) {
- $point = $points->item(0)->textContent;
- $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace"
- $point = preg_replace('/\s+/', ' ', $point);
- $point = trim($point);
- $coords = explode(' ', $point);
- if (count($coords) == 2) {
- list($lat, $lon) = $coords;
- if (is_numeric($lat) && is_numeric($lon)) {
- common_log(LOG_INFO, "Looking up location for $lat $lon from georss");
- return Location::fromLatLon($lat, $lon);
- }
+ public static function ensureProfile($profile_uri)
+ {
+ // Get the canonical feed URI and check it
+ $discover = new FeedDiscovery();
+ $feeduri = $discover->discoverFromURL($profile_uri);
+
+ //$feedsub = FeedSub::ensureFeed($feeduri, $discover->feed);
+ $huburi = $discover->getAtomLink('hub');
+ $salmonuri = $discover->getAtomLink('salmon');
+
+ if (!$huburi) {
+ // We can only deal with folks with a PuSH hub
+ throw new FeedSubNoHubException();
+ }
+
+ // Try to get a profile from the feed activity:subject
+
+ $feedEl = $discover->feed->documentElement;
+
+ $subject = ActivityUtils::child($feedEl, Activity::SUBJECT, Activity::SPEC);
+
+ if (!empty($subject)) {
+ $subjObject = new ActivityObject($subject);
+ return self::ensureActivityObjectProfile($subjObject, $feeduri, $salmonuri);
+ }
+
+ // Otherwise, try the feed author
+
+ $author = ActivityUtils::child($feedEl, Activity::AUTHOR, Activity::ATOM);
+
+ if (!empty($author)) {
+ $authorObject = new ActivityObject($author);
+ return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri);
+ }
+
+ // Sheesh. Not a very nice feed! Let's try fingerpoken in the
+ // entries.
+
+ $entries = $discover->feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
+
+ if (!empty($entries) && $entries->length > 0) {
+
+ $entry = $entries->item(0);
+
+ $actor = ActivityUtils::child($entry, Activity::ACTOR, Activity::SPEC);
+
+ if (!empty($actor)) {
+ $actorObject = new ActivityObject($actor);
+ return self::ensureActivityObjectProfile($actorObject, $feeduri, $salmonuri);
+
+ }
+
+ $author = ActivityUtils::child($entry, Activity::AUTHOR, Activity::ATOM);
+
+ if (!empty($author)) {
+ $authorObject = new ActivityObject($author);
+ return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri);
}
- common_log(LOG_ERR, "Ignoring bogus georss:point value $point");
}
- return false;
+ // XXX: make some educated guesses here
+
+ throw new FeedSubException("Can't find enough profile information to make a feed.");
+ }
+
+ /**
+ *
+ * Download and update given avatar image
+ * @param string $url
+ * @throws Exception in various failure cases
+ */
+ protected function updateAvatar($url)
+ {
+ // @fixme this should be better encapsulated
+ // ripped from oauthstore.php (for old OMB client)
+ $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
+ copy($url, $temp_filename);
+
+ if ($this->isGroup()) {
+ $id = $this->group_id;
+ } else {
+ $id = $this->profile_id;
+ }
+ // @fixme should we be using different ids?
+ $imagefile = new ImageFile($id, $temp_filename);
+ $filename = Avatar::filename($id,
+ image_type_to_extension($imagefile->type),
+ null,
+ common_timestamp());
+ rename($temp_filename, Avatar::path($filename));
+ if ($this->isGroup()) {
+ $group = $this->localGroup();
+ $group->setOriginal($filename);
+ } else {
+ $profile = $this->localProfile();
+ $profile->setOriginal($filename);
+ }
+ }
+
+ protected static function getActivityObjectAvatar($object)
+ {
+ // XXX: go poke around in the feed
+ return $object->avatar;
}
/**
@@ -792,7 +634,8 @@ class Ostatus_profile extends Memcached_DataObject
* @param DOMElement $feed
* @return string
*/
- function getAvatar($actor, $feed)
+
+ protected static function getAvatar($actor, $feed)
{
$url = '';
$icon = '';
@@ -831,13 +674,25 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * @fixme move off of ostatus_profile or static?
+ * Fetch, or build if necessary, an Ostatus_profile for the actor
+ * in a given Activity Streams activity.
+ *
+ * @param Activity $activity
+ * @param string $feeduri if we already know the canonical feed URI!
+ * @param string $salmonuri if we already know the salmon return channel URI
+ * @return Ostatus_profile
*/
- function ensureActorProfile($activity)
+
+ public static function ensureActorProfile($activity, $feeduri=null, $salmonuri=null)
{
- $profile = $this->getActorProfile($activity);
+ return self::ensureActivityObjectProfile($activity->actor, $feeduri, $salmonuri);
+ }
+
+ public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null)
+ {
+ $profile = self::getActivityObjectProfile($object);
if (!$profile) {
- $profile = $this->createActorProfile($activity);
+ $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri);
}
return $profile;
}
@@ -846,10 +701,20 @@ class Ostatus_profile extends Memcached_DataObject
* @param Activity $activity
* @return mixed matching Ostatus_profile or false if none known
*/
- function getActorProfile($activity)
+ protected static function getActorProfile($activity)
+ {
+ return self::getActivityObjectProfile($activity->actor);
+ }
+
+ protected static function getActivityObjectProfile($object)
{
- $homeuri = $this->getActorProfileURI($activity);
- return Ostatus_profile::staticGet('homeuri', $homeuri);
+ $uri = self::getActivityObjectProfileURI($object);
+ return Ostatus_profile::staticGet('homeuri', $uri);
+ }
+
+ protected static function getActorProfileURI($activity)
+ {
+ return self::getActivityObjectProfileURI($activity->actor);
}
/**
@@ -857,34 +722,60 @@ class Ostatus_profile extends Memcached_DataObject
* @return string
* @throws ServerException
*/
- function getActorProfileURI($activity)
+ protected static function getActivityObjectProfileURI($object)
{
$opts = array('allowed_schemes' => array('http', 'https'));
- $actor = $activity->actor;
- if ($actor->id && Validate::uri($actor->id, $opts)) {
- return $actor->id;
+ if ($object->id && Validate::uri($object->id, $opts)) {
+ return $object->id;
}
- if ($actor->link && Validate::uri($actor->link, $opts)) {
- return $actor->link;
+ if ($object->link && Validate::uri($object->link, $opts)) {
+ return $object->link;
}
throw new ServerException("No author ID URI found");
}
/**
- *
+ * @fixme validate stuff somewhere
*/
- function createActorProfile($activity)
+
+ protected static function createActorProfile($activity, $feeduri=null, $salmonuri=null)
+ {
+ $actor = $activity->actor;
+
+ self::createActivityObjectProfile($actor, $feeduri, $salmonuri);
+ }
+
+ protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null)
{
- $actor = $activity->actor();
- $homeuri = $this->getActivityProfileURI($activity);
- $nickname = $this->getAuthorNick($activity);
- $avatar = $this->getAvatar($actor, $feed);
+ $homeuri = self::getActivityObjectProfileURI($object);
+ $nickname = self::getActivityObjectNickname($object);
+ $avatar = self::getActivityObjectAvatar($object);
+
+ if (!$homeuri) {
+ common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
+ throw new ServerException("No profile URI");
+ }
+
+ if (!$feeduri || !$salmonuri) {
+ // Get the canonical feed URI and check it
+ $discover = new FeedDiscovery();
+ $feeduri = $discover->discoverFromURL($homeuri);
+
+ $huburi = $discover->getAtomLink('hub');
+ $salmonuri = $discover->getAtomLink('salmon');
+
+ if (!$huburi) {
+ // We can only deal with folks with a PuSH hub
+ throw new FeedSubNoHubException();
+ }
+ }
$profile = new Profile();
$profile->nickname = $nickname;
- $profile->fullname = $actor->displayName;
- $profile->homepage = $actor->link; // @fixme
- $profile->profileurl = $homeuri;
+ $profile->fullname = $object->title;
+ $profile->profileurl = $object->link;
+ $profile->created = common_sql_now();
+
// @fixme bio
// @fixme tags/categories
// @fixme location?
@@ -892,45 +783,66 @@ class Ostatus_profile extends Memcached_DataObject
// @todo lat/lon/location?
$ok = $profile->insert();
- if ($ok) {
- $this->updateAvatar($profile, $avatar);
- } else {
+
+ if (!$ok) {
throw new ServerException("Can't save local profile");
}
// @fixme either need to do feed discovery here
// or need to split out some of the feed stuff
// so we can leave it empty until later.
+
$oprofile = new Ostatus_profile();
- $oprofile->homeuri = $homeuri;
+
+ $oprofile->uri = $homeuri;
+ $oprofile->feeduri = $feeduri;
+ $oprofile->salmonuri = $salmonuri;
$oprofile->profile_id = $profile->id;
+ $oprofile->created = common_sql_now();
+ $oprofile->modified = common_sql_now();
+
$ok = $oprofile->insert();
+
if ($ok) {
+ $oprofile->updateAvatar($avatar);
return $oprofile;
} else {
throw new ServerException("Can't save OStatus profile");
}
}
- /**
- * @fixme move this into Activity?
- * @param Activity $activity
- * @return string
- */
- function getAuthorNick($activity)
- {
- // @fixme not technically part of the actor?
- foreach (array($activity->entry, $activity->feed) as $source) {
- $author = ActivityUtil::child($source, 'author', Activity::ATOM);
- if ($author) {
- $name = ActivityUtil::child($author, 'name', Activity::ATOM);
- if ($name) {
- return trim($name->textContent);
- }
- }
+ protected static function getActivityObjectNickname($object)
+ {
+ // XXX: check whatever PoCo calls a nickname first
+
+ $nickname = self::nicknameFromURI($object->id);
+
+ if (empty($nickname)) {
+ $nickname = common_nicknamize($object->title);
}
- return false;
+
+ return $nickname;
}
+ protected static function nicknameFromURI($uri)
+ {
+ preg_match('/(\w+):/', $uri, $matches);
+
+ $protocol = $matches[1];
+
+ switch ($protocol) {
+ case 'acct':
+ case 'mailto':
+ if (preg_match("/^$protocol:(.*)?@.*\$/", $uri, $matches)) {
+ return common_canonical_nickname($matches[1]);
+ }
+ return null;
+ case 'http':
+ return common_url_to_nickname($uri);
+ break;
+ default:
+ return null;
+ }
+ }
}
diff --git a/plugins/OStatus/extlib/README b/plugins/OStatus/extlib/README
deleted file mode 100644
index 799b40c47..000000000
--- a/plugins/OStatus/extlib/README
+++ /dev/null
@@ -1,9 +0,0 @@
-XML_Feed_Parser 1.0.3 is not currently actively maintained, and has
-a nasty bug which breaks getting the feed target link from WordPress
-feeds and possibly others that are RSS2-formatted but include an
-<atom:link> self-link element as well.
-
-Patch from this bug report is included:
-http://pear.php.net/bugs/bug.php?id=16416
-
-If upgrading, be sure that fix is included with the future upgrade!
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser.php b/plugins/OStatus/extlib/XML/Feed/Parser.php
deleted file mode 100755
index ffe8220a5..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser.php
+++ /dev/null
@@ -1,351 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Key gateway class for XML_Feed_Parser package
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL
- * @version CVS: $Id: Parser.php,v 1.24 2006/08/15 13:04:00 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * XML_Feed_Parser_Type is an abstract class required by all of our
- * feed types. It makes sense to load it here to keep the other files
- * clean.
- */
-require_once 'XML/Feed/Parser/Type.php';
-
-/**
- * We will throw exceptions when errors occur.
- */
-require_once 'XML/Feed/Parser/Exception.php';
-
-/**
- * This is the core of the XML_Feed_Parser package. It identifies feed types
- * and abstracts access to them. It is an iterator, allowing for easy access
- * to the entire feed.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser implements Iterator
-{
- /**
- * This is where we hold the feed object
- * @var Object
- */
- private $feed;
-
- /**
- * To allow for extensions, we make a public reference to the feed model
- * @var DOMDocument
- */
- public $model;
-
- /**
- * A map between entry ID and offset
- * @var array
- */
- protected $idMappings = array();
-
- /**
- * A storage space for Namespace URIs.
- * @var array
- */
- private $feedNamespaces = array(
- 'rss2' => array(
- 'http://backend.userland.com/rss',
- 'http://backend.userland.com/rss2',
- 'http://blogs.law.harvard.edu/tech/rss'));
- /**
- * Detects feed types and instantiate appropriate objects.
- *
- * Our constructor takes care of detecting feed types and instantiating
- * appropriate classes. For now we're going to treat Atom 0.3 as Atom 1.0
- * but raise a warning. I do not intend to introduce full support for
- * Atom 0.3 as it has been deprecated, but others are welcome to.
- *
- * @param string $feed XML serialization of the feed
- * @param bool $strict Whether or not to validate the feed
- * @param bool $suppressWarnings Trigger errors for deprecated feed types?
- * @param bool $tidy Whether or not to try and use the tidy library on input
- */
- function __construct($feed, $strict = false, $suppressWarnings = false, $tidy = false)
- {
- $this->model = new DOMDocument;
- if (! $this->model->loadXML($feed)) {
- if (extension_loaded('tidy') && $tidy) {
- $tidy = new tidy;
- $tidy->parseString($feed,
- array('input-xml' => true, 'output-xml' => true));
- $tidy->cleanRepair();
- if (! $this->model->loadXML((string) $tidy)) {
- throw new XML_Feed_Parser_Exception('Invalid input: this is not ' .
- 'valid XML');
- }
- } else {
- throw new XML_Feed_Parser_Exception('Invalid input: this is not valid XML');
- }
-
- }
-
- /* detect feed type */
- $doc_element = $this->model->documentElement;
- $error = false;
-
- switch (true) {
- case ($doc_element->namespaceURI == 'http://www.w3.org/2005/Atom'):
- require_once 'XML/Feed/Parser/Atom.php';
- require_once 'XML/Feed/Parser/AtomElement.php';
- $class = 'XML_Feed_Parser_Atom';
- break;
- case ($doc_element->namespaceURI == 'http://purl.org/atom/ns#'):
- require_once 'XML/Feed/Parser/Atom.php';
- require_once 'XML/Feed/Parser/AtomElement.php';
- $class = 'XML_Feed_Parser_Atom';
- $error = 'Atom 0.3 deprecated, using 1.0 parser which won\'t provide ' .
- 'all options';
- break;
- case ($doc_element->namespaceURI == 'http://purl.org/rss/1.0/' ||
- ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
- && $doc_element->childNodes->item(1)->namespaceURI ==
- 'http://purl.org/rss/1.0/')):
- require_once 'XML/Feed/Parser/RSS1.php';
- require_once 'XML/Feed/Parser/RSS1Element.php';
- $class = 'XML_Feed_Parser_RSS1';
- break;
- case ($doc_element->namespaceURI == 'http://purl.org/rss/1.1/' ||
- ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
- && $doc_element->childNodes->item(1)->namespaceURI ==
- 'http://purl.org/rss/1.1/')):
- require_once 'XML/Feed/Parser/RSS11.php';
- require_once 'XML/Feed/Parser/RSS11Element.php';
- $class = 'XML_Feed_Parser_RSS11';
- break;
- case (($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
- && $doc_element->childNodes->item(1)->namespaceURI ==
- 'http://my.netscape.com/rdf/simple/0.9/') ||
- $doc_element->namespaceURI == 'http://my.netscape.com/rdf/simple/0.9/'):
- require_once 'XML/Feed/Parser/RSS09.php';
- require_once 'XML/Feed/Parser/RSS09Element.php';
- $class = 'XML_Feed_Parser_RSS09';
- break;
- case ($doc_element->tagName == 'rss' and
- $doc_element->hasAttribute('version') &&
- $doc_element->getAttribute('version') == 0.91):
- $error = 'RSS 0.91 has been superceded by RSS2.0. Using RSS2.0 parser.';
- require_once 'XML/Feed/Parser/RSS2.php';
- require_once 'XML/Feed/Parser/RSS2Element.php';
- $class = 'XML_Feed_Parser_RSS2';
- break;
- case ($doc_element->tagName == 'rss' and
- $doc_element->hasAttribute('version') &&
- $doc_element->getAttribute('version') == 0.92):
- $error = 'RSS 0.92 has been superceded by RSS2.0. Using RSS2.0 parser.';
- require_once 'XML/Feed/Parser/RSS2.php';
- require_once 'XML/Feed/Parser/RSS2Element.php';
- $class = 'XML_Feed_Parser_RSS2';
- break;
- case (in_array($doc_element->namespaceURI, $this->feedNamespaces['rss2'])
- || $doc_element->tagName == 'rss'):
- if (! $doc_element->hasAttribute('version') ||
- $doc_element->getAttribute('version') != 2) {
- $error = 'RSS version not specified. Parsing as RSS2.0';
- }
- require_once 'XML/Feed/Parser/RSS2.php';
- require_once 'XML/Feed/Parser/RSS2Element.php';
- $class = 'XML_Feed_Parser_RSS2';
- break;
- default:
- throw new XML_Feed_Parser_Exception('Feed type unknown');
- break;
- }
-
- if (! $suppressWarnings && ! empty($error)) {
- trigger_error($error, E_USER_WARNING);
- }
-
- /* Instantiate feed object */
- $this->feed = new $class($this->model, $strict);
- }
-
- /**
- * Proxy to allow feed element names to be used as method names
- *
- * For top-level feed elements we will provide access using methods or
- * attributes. This function simply passes on a request to the appropriate
- * feed type object.
- *
- * @param string $call - the method being called
- * @param array $attributes
- */
- function __call($call, $attributes)
- {
- $attributes = array_pad($attributes, 5, false);
- list($a, $b, $c, $d, $e) = $attributes;
- return $this->feed->$call($a, $b, $c, $d, $e);
- }
-
- /**
- * Proxy to allow feed element names to be used as attribute names
- *
- * To allow variable-like access to feed-level data we use this
- * method. It simply passes along to __call() which in turn passes
- * along to the relevant object.
- *
- * @param string $val - the name of the variable required
- */
- function __get($val)
- {
- return $this->feed->$val;
- }
-
- /**
- * Provides iteration functionality.
- *
- * Of course we must be able to iterate... This function simply increases
- * our internal counter.
- */
- function next()
- {
- if (isset($this->current_item) &&
- $this->current_item <= $this->feed->numberEntries - 1) {
- ++$this->current_item;
- } else if (! isset($this->current_item)) {
- $this->current_item = 0;
- } else {
- return false;
- }
- }
-
- /**
- * Return XML_Feed_Type object for current element
- *
- * @return XML_Feed_Parser_Type Object
- */
- function current()
- {
- return $this->getEntryByOffset($this->current_item);
- }
-
- /**
- * For iteration -- returns the key for the current stage in the array.
- *
- * @return int
- */
- function key()
- {
- return $this->current_item;
- }
-
- /**
- * For iteration -- tells whether we have reached the
- * end.
- *
- * @return bool
- */
- function valid()
- {
- return $this->current_item < $this->feed->numberEntries;
- }
-
- /**
- * For iteration -- resets the internal counter to the beginning.
- */
- function rewind()
- {
- $this->current_item = 0;
- }
-
- /**
- * Provides access to entries by ID if one is specified in the source feed.
- *
- * As well as allowing the items to be iterated over we want to allow
- * users to be able to access a specific entry. This is one of two ways of
- * doing that, the other being by offset. This method can be quite slow
- * if dealing with a large feed that hasn't yet been processed as it
- * instantiates objects for every entry until it finds the one needed.
- *
- * @param string $id Valid ID for the given feed format
- * @return XML_Feed_Parser_Type|false
- */
- function getEntryById($id)
- {
- if (isset($this->idMappings[$id])) {
- return $this->getEntryByOffset($this->idMappings[$id]);
- }
-
- /*
- * Since we have not yet encountered that ID, let's go through all the
- * remaining entries in order till we find it.
- * This is a fairly slow implementation, but it should work.
- */
- return $this->feed->getEntryById($id);
- }
-
- /**
- * Retrieve entry by numeric offset, starting from zero.
- *
- * As well as allowing the items to be iterated over we want to allow
- * users to be able to access a specific entry. This is one of two ways of
- * doing that, the other being by ID.
- *
- * @param int $offset The position of the entry within the feed, starting from 0
- * @return XML_Feed_Parser_Type|false
- */
- function getEntryByOffset($offset)
- {
- if ($offset < $this->feed->numberEntries) {
- if (isset($this->feed->entries[$offset])) {
- return $this->feed->entries[$offset];
- } else {
- try {
- $this->feed->getEntryByOffset($offset);
- } catch (Exception $e) {
- return false;
- }
- $id = $this->feed->entries[$offset]->getID();
- $this->idMappings[$id] = $offset;
- return $this->feed->entries[$offset];
- }
- } else {
- return false;
- }
- }
-
- /**
- * Retrieve version details from feed type class.
- *
- * @return void
- * @author James Stewart
- */
- function version()
- {
- return $this->feed->version;
- }
-
- /**
- * Returns a string representation of the feed.
- *
- * @return String
- **/
- function __toString()
- {
- return $this->feed->__toString();
- }
-}
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/Atom.php b/plugins/OStatus/extlib/XML/Feed/Parser/Atom.php
deleted file mode 100644
index c7e218a1e..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/Atom.php
+++ /dev/null
@@ -1,365 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Atom feed class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: Atom.php,v 1.29 2008/03/30 22:00:36 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
-*/
-
-/**
- * This is the class that determines how we manage Atom 1.0 feeds
- *
- * How we deal with constructs:
- * date - return as unix datetime for use with the 'date' function unless specified otherwise
- * text - return as is. optional parameter will give access to attributes
- * person - defaults to name, but parameter based access
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_Atom extends XML_Feed_Parser_Type
-{
- /**
- * The URI of the RelaxNG schema used to (optionally) validate the feed
- * @var string
- */
- private $relax = 'atom.rnc';
-
- /**
- * We're likely to use XPath, so let's keep it global
- * @var DOMXPath
- */
- public $xpath;
-
- /**
- * When performing XPath queries we will use this prefix
- * @var string
- */
- private $xpathPrefix = '//';
-
- /**
- * The feed type we are parsing
- * @var string
- */
- public $version = 'Atom 1.0';
-
- /**
- * The class used to represent individual items
- * @var string
- */
- protected $itemClass = 'XML_Feed_Parser_AtomElement';
-
- /**
- * The element containing entries
- * @var string
- */
- protected $itemElement = 'entry';
-
- /**
- * Here we map those elements we're not going to handle individually
- * to the constructs they are. The optional second parameter in the array
- * tells the parser whether to 'fall back' (not apt. at the feed level) or
- * fail if the element is missing. If the parameter is not set, the function
- * will simply return false and leave it to the client to decide what to do.
- * @var array
- */
- protected $map = array(
- 'author' => array('Person'),
- 'contributor' => array('Person'),
- 'icon' => array('Text'),
- 'logo' => array('Text'),
- 'id' => array('Text', 'fail'),
- 'rights' => array('Text'),
- 'subtitle' => array('Text'),
- 'title' => array('Text', 'fail'),
- 'updated' => array('Date', 'fail'),
- 'link' => array('Link'),
- 'generator' => array('Text'),
- 'category' => array('Category'));
-
- /**
- * Here we provide a few mappings for those very special circumstances in
- * which it makes sense to map back to the RSS2 spec. Key is RSS2 version
- * value is an array consisting of the equivalent in atom and any attributes
- * needed to make the mapping.
- * @var array
- */
- protected $compatMap = array(
- 'guid' => array('id'),
- 'links' => array('link'),
- 'tags' => array('category'),
- 'contributors' => array('contributor'));
-
- /**
- * Our constructor does nothing more than its parent.
- *
- * @param DOMDocument $xml A DOM object representing the feed
- * @param bool (optional) $string Whether or not to validate this feed
- */
- function __construct(DOMDocument $model, $strict = false)
- {
- $this->model = $model;
-
- if ($strict) {
- if (! $this->model->relaxNGValidateSource($this->relax)) {
- throw new XML_Feed_Parser_Exception('Failed required validation');
- }
- }
-
- $this->xpath = new DOMXPath($this->model);
- $this->xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
- $this->numberEntries = $this->count('entry');
- }
-
- /**
- * Implement retrieval of an entry based on its ID for atom feeds.
- *
- * This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate
- * is available, we also use that to store a reference to the entry in the array
- * used by getEntryByOffset so that method does not have to seek out the entry
- * if it's requested that way.
- *
- * @param string $id any valid Atom ID.
- * @return XML_Feed_Parser_AtomElement
- */
- function getEntryById($id)
- {
- if (isset($this->idMappings[$id])) {
- return $this->entries[$this->idMappings[$id]];
- }
-
- $entries = $this->xpath->query("//atom:entry[atom:id='$id']");
-
- if ($entries->length > 0) {
- $xmlBase = $entries->item(0)->baseURI;
- $entry = new $this->itemClass($entries->item(0), $this, $xmlBase);
-
- if (in_array('evaluate', get_class_methods($this->xpath))) {
- $offset = $this->xpath->evaluate("count(preceding-sibling::atom:entry)", $entries->item(0));
- $this->entries[$offset] = $entry;
- }
-
- $this->idMappings[$id] = $entry;
-
- return $entry;
- }
-
- }
-
- /**
- * Retrieves data from a person construct.
- *
- * Get a person construct. We default to the 'name' element but allow
- * access to any of the elements.
- *
- * @param string $method The name of the person construct we want
- * @param array $arguments An array which we hope gives a 'param'
- * @return string|false
- */
- protected function getPerson($method, $arguments)
- {
- $offset = empty($arguments[0]) ? 0 : $arguments[0];
- $parameter = empty($arguments[1]['param']) ? 'name' : $arguments[1]['param'];
- $section = $this->model->getElementsByTagName($method);
-
- if ($parameter == 'url') {
- $parameter = 'uri';
- }
-
- if ($section->length <= $offset) {
- return false;
- }
-
- $param = $section->item($offset)->getElementsByTagName($parameter);
- if ($param->length == 0) {
- return false;
- }
- return $param->item(0)->nodeValue;
- }
-
- /**
- * Retrieves an element's content where that content is a text construct.
- *
- * Get a text construct. When calling this method, the two arguments
- * allowed are 'offset' and 'attribute', so $parser->subtitle() would
- * return the content of the element, while $parser->subtitle(false, 'type')
- * would return the value of the type attribute.
- *
- * @todo Clarify overlap with getContent()
- * @param string $method The name of the text construct we want
- * @param array $arguments An array which we hope gives a 'param'
- * @return string
- */
- protected function getText($method, $arguments)
- {
- $offset = empty($arguments[0]) ? 0: $arguments[0];
- $attribute = empty($arguments[1]) ? false : $arguments[1];
- $tags = $this->model->getElementsByTagName($method);
-
- if ($tags->length <= $offset) {
- return false;
- }
-
- $content = $tags->item($offset);
-
- if (! $content->hasAttribute('type')) {
- $content->setAttribute('type', 'text');
- }
- $type = $content->getAttribute('type');
-
- if (! empty($attribute) and
- ! ($method == 'generator' and $attribute == 'name')) {
- if ($content->hasAttribute($attribute)) {
- return $content->getAttribute($attribute);
- } else if ($attribute == 'href' and $content->hasAttribute('uri')) {
- return $content->getAttribute('uri');
- }
- return false;
- }
-
- return $this->parseTextConstruct($content);
- }
-
- /**
- * Extract content appropriately from atom text constructs
- *
- * Because of different rules applied to the content element and other text
- * constructs, they are deployed as separate functions, but they share quite
- * a bit of processing. This method performs the core common process, which is
- * to apply the rules for different mime types in order to extract the content.
- *
- * @param DOMNode $content the text construct node to be parsed
- * @return String
- * @author James Stewart
- **/
- protected function parseTextConstruct(DOMNode $content)
- {
- if ($content->hasAttribute('type')) {
- $type = $content->getAttribute('type');
- } else {
- $type = 'text';
- }
-
- if (strpos($type, 'text/') === 0) {
- $type = 'text';
- }
-
- switch ($type) {
- case 'text':
- case 'html':
- return $content->textContent;
- break;
- case 'xhtml':
- $container = $content->getElementsByTagName('div');
- if ($container->length == 0) {
- return false;
- }
- $contents = $container->item(0);
- if ($contents->hasChildNodes()) {
- /* Iterate through, applying xml:base and store the result */
- $result = '';
- foreach ($contents->childNodes as $node) {
- $result .= $this->traverseNode($node);
- }
- return $result;
- }
- break;
- case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i', $type) > 0:
- return $content;
- break;
- case 'application/octet-stream':
- default:
- return base64_decode(trim($content->nodeValue));
- break;
- }
- return false;
- }
- /**
- * Get a category from the entry.
- *
- * A feed or entry can have any number of categories. A category can have the
- * attributes term, scheme and label.
- *
- * @param string $method The name of the text construct we want
- * @param array $arguments An array which we hope gives a 'param'
- * @return string
- */
- function getCategory($method, $arguments)
- {
- $offset = empty($arguments[0]) ? 0: $arguments[0];
- $attribute = empty($arguments[1]) ? 'term' : $arguments[1];
- $categories = $this->model->getElementsByTagName('category');
- if ($categories->length <= $offset) {
- $category = $categories->item($offset);
- if ($category->hasAttribute($attribute)) {
- return $category->getAttribute($attribute);
- }
- }
- return false;
- }
-
- /**
- * This element must be present at least once with rel="feed". This element may be
- * present any number of further times so long as there is no clash. If no 'rel' is
- * present and we're asked for one, we follow the example of the Universal Feed
- * Parser and presume 'alternate'.
- *
- * @param int $offset the position of the link within the container
- * @param string $attribute the attribute name required
- * @param array an array of attributes to search by
- * @return string the value of the attribute
- */
- function getLink($offset = 0, $attribute = 'href', $params = false)
- {
- if (is_array($params) and !empty($params)) {
- $terms = array();
- $alt_predicate = '';
- $other_predicate = '';
-
- foreach ($params as $key => $value) {
- if ($key == 'rel' && $value == 'alternate') {
- $alt_predicate = '[not(@rel) or @rel="alternate"]';
- } else {
- $terms[] = "@$key='$value'";
- }
- }
- if (!empty($terms)) {
- $other_predicate = '[' . join(' and ', $terms) . ']';
- }
- $query = $this->xpathPrefix . 'atom:link' . $alt_predicate . $other_predicate;
- $links = $this->xpath->query($query);
- } else {
- $links = $this->model->getElementsByTagName('link');
- }
- if ($links->length > $offset) {
- if ($links->item($offset)->hasAttribute($attribute)) {
- $value = $links->item($offset)->getAttribute($attribute);
- if ($attribute == 'href') {
- $value = $this->addBase($value, $links->item($offset));
- }
- return $value;
- } else if ($attribute == 'rel') {
- return 'alternate';
- }
- }
- return false;
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/AtomElement.php b/plugins/OStatus/extlib/XML/Feed/Parser/AtomElement.php
deleted file mode 100755
index 063ecb617..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/AtomElement.php
+++ /dev/null
@@ -1,261 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * AtomElement class for XML_Feed_Parser package
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: AtomElement.php,v 1.19 2007/03/26 12:43:11 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This class provides support for atom entries. It will usually be called by
- * XML_Feed_Parser_Atom with which it shares many methods.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_AtomElement extends XML_Feed_Parser_Atom
-{
- /**
- * This will be a reference to the parent object for when we want
- * to use a 'fallback' rule
- * @var XML_Feed_Parser_Atom
- */
- protected $parent;
-
- /**
- * When performing XPath queries we will use this prefix
- * @var string
- */
- private $xpathPrefix = '';
-
- /**
- * xml:base values inherited by the element
- * @var string
- */
- protected $xmlBase;
-
- /**
- * Here we provide a few mappings for those very special circumstances in
- * which it makes sense to map back to the RSS2 spec or to manage other
- * compatibilities (eg. with the Univeral Feed Parser). Key is the other version's
- * name for the command, value is an array consisting of the equivalent in our atom
- * api and any attributes needed to make the mapping.
- * @var array
- */
- protected $compatMap = array(
- 'guid' => array('id'),
- 'links' => array('link'),
- 'tags' => array('category'),
- 'contributors' => array('contributor'));
-
- /**
- * Our specific element map
- * @var array
- */
- protected $map = array(
- 'author' => array('Person', 'fallback'),
- 'contributor' => array('Person'),
- 'id' => array('Text', 'fail'),
- 'published' => array('Date'),
- 'updated' => array('Date', 'fail'),
- 'title' => array('Text', 'fail'),
- 'rights' => array('Text', 'fallback'),
- 'summary' => array('Text'),
- 'content' => array('Content'),
- 'link' => array('Link'),
- 'enclosure' => array('Enclosure'),
- 'category' => array('Category'));
-
- /**
- * Store useful information for later.
- *
- * @param DOMElement $element - this item as a DOM element
- * @param XML_Feed_Parser_Atom $parent - the feed of which this is a member
- */
- function __construct(DOMElement $element, $parent, $xmlBase = '')
- {
- $this->model = $element;
- $this->parent = $parent;
- $this->xmlBase = $xmlBase;
- $this->xpathPrefix = "//atom:entry[atom:id='" . $this->id . "']/";
- $this->xpath = $this->parent->xpath;
- }
-
- /**
- * Provides access to specific aspects of the author data for an atom entry
- *
- * Author data at the entry level is more complex than at the feed level.
- * If atom:author is not present for the entry we need to look for it in
- * an atom:source child of the atom:entry. If it's not there either, then
- * we look to the parent for data.
- *
- * @param array
- * @return string
- */
- function getAuthor($arguments)
- {
- /* Find out which part of the author data we're looking for */
- if (isset($arguments['param'])) {
- $parameter = $arguments['param'];
- } else {
- $parameter = 'name';
- }
-
- $test = $this->model->getElementsByTagName('author');
- if ($test->length > 0) {
- $item = $test->item(0);
- return $item->getElementsByTagName($parameter)->item(0)->nodeValue;
- }
-
- $source = $this->model->getElementsByTagName('source');
- if ($source->length > 0) {
- $test = $this->model->getElementsByTagName('author');
- if ($test->length > 0) {
- $item = $test->item(0);
- return $item->getElementsByTagName($parameter)->item(0)->nodeValue;
- }
- }
- return $this->parent->getAuthor($arguments);
- }
-
- /**
- * Returns the content of the content element or info on a specific attribute
- *
- * This element may or may not be present. It cannot be present more than
- * once. It may have a 'src' attribute, in which case there's no content
- * If not present, then the entry must have link with rel="alternate".
- * If there is content we return it, if not and there's a 'src' attribute
- * we return the value of that instead. The method can take an 'attribute'
- * argument, in which case we return the value of that attribute if present.
- * eg. $item->content("type") will return the type of the content. It is
- * recommended that all users check the type before getting the content to
- * ensure that their script is capable of handling the type of returned data.
- * (data carried in the content element can be either 'text', 'html', 'xhtml',
- * or any standard MIME type).
- *
- * @return string|false
- */
- protected function getContent($method, $arguments = array())
- {
- $attribute = empty($arguments[0]) ? false : $arguments[0];
- $tags = $this->model->getElementsByTagName('content');
-
- if ($tags->length == 0) {
- return false;
- }
-
- $content = $tags->item(0);
-
- if (! $content->hasAttribute('type')) {
- $content->setAttribute('type', 'text');
- }
- if (! empty($attribute)) {
- return $content->getAttribute($attribute);
- }
-
- $type = $content->getAttribute('type');
-
- if (! empty($attribute)) {
- if ($content->hasAttribute($attribute))
- {
- return $content->getAttribute($attribute);
- }
- return false;
- }
-
- if ($content->hasAttribute('src')) {
- return $content->getAttribute('src');
- }
-
- return $this->parseTextConstruct($content);
- }
-
- /**
- * For compatibility, this method provides a mapping to access enclosures.
- *
- * The Atom spec doesn't provide for an enclosure element, but it is
- * generally supported using the link element with rel='enclosure'.
- *
- * @param string $method - for compatibility with our __call usage
- * @param array $arguments - for compatibility with our __call usage
- * @return array|false
- */
- function getEnclosure($method, $arguments = array())
- {
- $offset = isset($arguments[0]) ? $arguments[0] : 0;
- $query = "//atom:entry[atom:id='" . $this->getText('id', false) .
- "']/atom:link[@rel='enclosure']";
-
- $encs = $this->parent->xpath->query($query);
- if ($encs->length > $offset) {
- try {
- if (! $encs->item($offset)->hasAttribute('href')) {
- return false;
- }
- $attrs = $encs->item($offset)->attributes;
- $length = $encs->item($offset)->hasAttribute('length') ?
- $encs->item($offset)->getAttribute('length') : false;
- return array(
- 'url' => $attrs->getNamedItem('href')->value,
- 'type' => $attrs->getNamedItem('type')->value,
- 'length' => $length);
- } catch (Exception $e) {
- return false;
- }
- }
- return false;
- }
-
- /**
- * Get details of this entry's source, if available/relevant
- *
- * Where an atom:entry is taken from another feed then the aggregator
- * is supposed to include an atom:source element which replicates at least
- * the atom:id, atom:title, and atom:updated metadata from the original
- * feed. Atom:source therefore has a very similar structure to atom:feed
- * and if we find it we will return it as an XML_Feed_Parser_Atom object.
- *
- * @return XML_Feed_Parser_Atom|false
- */
- function getSource()
- {
- $test = $this->model->getElementsByTagName('source');
- if ($test->length == 0) {
- return false;
- }
- $source = new XML_Feed_Parser_Atom($test->item(0));
- }
-
- /**
- * Get the entry as an XML string
- *
- * Return an XML serialization of the feed, should it be required. Most
- * users however, will already have a serialization that they used when
- * instantiating the object.
- *
- * @return string XML serialization of element
- */
- function __toString()
- {
- $simple = simplexml_import_dom($this->model);
- return $simple->asXML();
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/Exception.php b/plugins/OStatus/extlib/XML/Feed/Parser/Exception.php
deleted file mode 100755
index 1e76e3f85..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/Exception.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Keeps the exception class for XML_Feed_Parser.
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL
- * @version CVS: $Id: Exception.php,v 1.3 2005/11/07 01:52:35 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * We are extending PEAR_Exception
- */
-require_once 'PEAR/Exception.php';
-
-/**
- * XML_Feed_Parser_Exception is a simple extension of PEAR_Exception, existing
- * to help with identification of the source of exceptions.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_Exception extends PEAR_Exception
-{
-
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS09.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS09.php
deleted file mode 100755
index 07f38f911..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS09.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * RSS0.9 class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS09.php,v 1.5 2006/07/26 21:18:46 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This class handles RSS0.9 feeds.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- * @todo Find a Relax NG URI we can use
- */
-class XML_Feed_Parser_RSS09 extends XML_Feed_Parser_Type
-{
- /**
- * The URI of the RelaxNG schema used to (optionally) validate the feed
- * @var string
- */
- private $relax = '';
-
- /**
- * We're likely to use XPath, so let's keep it global
- * @var DOMXPath
- */
- protected $xpath;
-
- /**
- * The feed type we are parsing
- * @var string
- */
- public $version = 'RSS 0.9';
-
- /**
- * The class used to represent individual items
- * @var string
- */
- protected $itemClass = 'XML_Feed_Parser_RSS09Element';
-
- /**
- * The element containing entries
- * @var string
- */
- protected $itemElement = 'item';
-
- /**
- * Here we map those elements we're not going to handle individually
- * to the constructs they are. The optional second parameter in the array
- * tells the parser whether to 'fall back' (not apt. at the feed level) or
- * fail if the element is missing. If the parameter is not set, the function
- * will simply return false and leave it to the client to decide what to do.
- * @var array
- */
- protected $map = array(
- 'title' => array('Text'),
- 'link' => array('Text'),
- 'description' => array('Text'),
- 'image' => array('Image'),
- 'textinput' => array('TextInput'));
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS2.
- * @var array
- */
- protected $compatMap = array(
- 'title' => array('title'),
- 'link' => array('link'),
- 'subtitle' => array('description'));
-
- /**
- * We will be working with multiple namespaces and it is useful to
- * keep them together
- * @var array
- */
- protected $namespaces = array(
- 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
-
- /**
- * Our constructor does nothing more than its parent.
- *
- * @todo RelaxNG validation
- * @param DOMDocument $xml A DOM object representing the feed
- * @param bool (optional) $string Whether or not to validate this feed
- */
- function __construct(DOMDocument $model, $strict = false)
- {
- $this->model = $model;
-
- $this->xpath = new DOMXPath($model);
- foreach ($this->namespaces as $key => $value) {
- $this->xpath->registerNamespace($key, $value);
- }
- $this->numberEntries = $this->count('item');
- }
-
- /**
- * Included for compatibility -- will not work with RSS 0.9
- *
- * This is not something that will work with RSS0.9 as it does not have
- * clear restrictions on the global uniqueness of IDs.
- *
- * @param string $id any valid ID.
- * @return false
- */
- function getEntryById($id)
- {
- return false;
- }
-
- /**
- * Get details of the image associated with the feed.
- *
- * @return array|false an array simply containing the child elements
- */
- protected function getImage()
- {
- $images = $this->model->getElementsByTagName('image');
- if ($images->length > 0) {
- $image = $images->item(0);
- $details = array();
- if ($image->hasChildNodes()) {
- $details = array(
- 'title' => $image->getElementsByTagName('title')->item(0)->value,
- 'link' => $image->getElementsByTagName('link')->item(0)->value,
- 'url' => $image->getElementsByTagName('url')->item(0)->value);
- } else {
- $details = array('title' => false,
- 'link' => false,
- 'url' => $image->attributes->getNamedItem('resource')->nodeValue);
- }
- $details = array_merge($details,
- array('description' => false, 'height' => false, 'width' => false));
- if (! empty($details)) {
- return $details;
- }
- }
- return false;
- }
-
- /**
- * The textinput element is little used, but in the interests of
- * completeness we will support it.
- *
- * @return array|false
- */
- protected function getTextInput()
- {
- $inputs = $this->model->getElementsByTagName('textinput');
- if ($inputs->length > 0) {
- $input = $inputs->item(0);
- $results = array();
- $results['title'] = isset(
- $input->getElementsByTagName('title')->item(0)->value) ?
- $input->getElementsByTagName('title')->item(0)->value : null;
- $results['description'] = isset(
- $input->getElementsByTagName('description')->item(0)->value) ?
- $input->getElementsByTagName('description')->item(0)->value : null;
- $results['name'] = isset(
- $input->getElementsByTagName('name')->item(0)->value) ?
- $input->getElementsByTagName('name')->item(0)->value : null;
- $results['link'] = isset(
- $input->getElementsByTagName('link')->item(0)->value) ?
- $input->getElementsByTagName('link')->item(0)->value : null;
- if (empty($results['link']) &&
- $input->attributes->getNamedItem('resource')) {
- $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue;
- }
- if (! empty($results)) {
- return $results;
- }
- }
- return false;
- }
-
- /**
- * Get details of a link from the feed.
- *
- * In RSS1 a link is a text element but in order to ensure that we resolve
- * URLs properly we have a special function for them.
- *
- * @return string
- */
- function getLink($offset = 0, $attribute = 'href', $params = false)
- {
- $links = $this->model->getElementsByTagName('link');
- if ($links->length <= $offset) {
- return false;
- }
- $link = $links->item($offset);
- return $this->addBase($link->nodeValue, $link);
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS09Element.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS09Element.php
deleted file mode 100755
index d41f36e8d..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS09Element.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * RSS0.9 Element class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS09Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/*
- * This class provides support for RSS 0.9 entries. It will usually be called by
- * XML_Feed_Parser_RSS09 with which it shares many methods.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_RSS09Element extends XML_Feed_Parser_RSS09
-{
- /**
- * This will be a reference to the parent object for when we want
- * to use a 'fallback' rule
- * @var XML_Feed_Parser_RSS09
- */
- protected $parent;
-
- /**
- * Our specific element map
- * @var array
- */
- protected $map = array(
- 'title' => array('Text'),
- 'link' => array('Link'));
-
- /**
- * Store useful information for later.
- *
- * @param DOMElement $element - this item as a DOM element
- * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member
- */
- function __construct(DOMElement $element, $parent, $xmlBase = '')
- {
- $this->model = $element;
- $this->parent = $parent;
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS1.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS1.php
deleted file mode 100755
index 60c9938ba..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS1.php
+++ /dev/null
@@ -1,277 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * RSS1 class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS1.php,v 1.10 2006/07/27 13:52:05 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This class handles RSS1.0 feeds.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- * @todo Find a Relax NG URI we can use
- */
-class XML_Feed_Parser_RSS1 extends XML_Feed_Parser_Type
-{
- /**
- * The URI of the RelaxNG schema used to (optionally) validate the feed
- * @var string
- */
- private $relax = 'rss10.rnc';
-
- /**
- * We're likely to use XPath, so let's keep it global
- * @var DOMXPath
- */
- protected $xpath;
-
- /**
- * The feed type we are parsing
- * @var string
- */
- public $version = 'RSS 1.0';
-
- /**
- * The class used to represent individual items
- * @var string
- */
- protected $itemClass = 'XML_Feed_Parser_RSS1Element';
-
- /**
- * The element containing entries
- * @var string
- */
- protected $itemElement = 'item';
-
- /**
- * Here we map those elements we're not going to handle individually
- * to the constructs they are. The optional second parameter in the array
- * tells the parser whether to 'fall back' (not apt. at the feed level) or
- * fail if the element is missing. If the parameter is not set, the function
- * will simply return false and leave it to the client to decide what to do.
- * @var array
- */
- protected $map = array(
- 'title' => array('Text'),
- 'link' => array('Text'),
- 'description' => array('Text'),
- 'image' => array('Image'),
- 'textinput' => array('TextInput'),
- 'updatePeriod' => array('Text'),
- 'updateFrequency' => array('Text'),
- 'updateBase' => array('Date'),
- 'rights' => array('Text'), # dc:rights
- 'description' => array('Text'), # dc:description
- 'creator' => array('Text'), # dc:creator
- 'publisher' => array('Text'), # dc:publisher
- 'contributor' => array('Text'), # dc:contributor
- 'date' => array('Date') # dc:contributor
- );
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS2.
- * @var array
- */
- protected $compatMap = array(
- 'title' => array('title'),
- 'link' => array('link'),
- 'subtitle' => array('description'),
- 'author' => array('creator'),
- 'updated' => array('date'));
-
- /**
- * We will be working with multiple namespaces and it is useful to
- * keep them together
- * @var array
- */
- protected $namespaces = array(
- 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
- 'rss' => 'http://purl.org/rss/1.0/',
- 'dc' => 'http://purl.org/rss/1.0/modules/dc/',
- 'content' => 'http://purl.org/rss/1.0/modules/content/',
- 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/');
-
- /**
- * Our constructor does nothing more than its parent.
- *
- * @param DOMDocument $xml A DOM object representing the feed
- * @param bool (optional) $string Whether or not to validate this feed
- */
- function __construct(DOMDocument $model, $strict = false)
- {
- $this->model = $model;
- if ($strict) {
- $validate = $this->model->relaxNGValidate(self::getSchemaDir .
- DIRECTORY_SEPARATOR . $this->relax);
- if (! $validate) {
- throw new XML_Feed_Parser_Exception('Failed required validation');
- }
- }
-
- $this->xpath = new DOMXPath($model);
- foreach ($this->namespaces as $key => $value) {
- $this->xpath->registerNamespace($key, $value);
- }
- $this->numberEntries = $this->count('item');
- }
-
- /**
- * Allows retrieval of an entry by ID where the rdf:about attribute is used
- *
- * This is not really something that will work with RSS1 as it does not have
- * clear restrictions on the global uniqueness of IDs. We will employ the
- * _very_ hit and miss method of selecting entries based on the rdf:about
- * attribute. If DOMXPath::evaluate is available, we also use that to store
- * a reference to the entry in the array used by getEntryByOffset so that
- * method does not have to seek out the entry if it's requested that way.
- *
- * @param string $id any valid ID.
- * @return XML_Feed_Parser_RSS1Element
- */
- function getEntryById($id)
- {
- if (isset($this->idMappings[$id])) {
- return $this->entries[$this->idMappings[$id]];
- }
-
- $entries = $this->xpath->query("//rss:item[@rdf:about='$id']");
- if ($entries->length > 0) {
- $classname = $this->itemClass;
- $entry = new $classname($entries->item(0), $this);
- if (in_array('evaluate', get_class_methods($this->xpath))) {
- $offset = $this->xpath->evaluate("count(preceding-sibling::rss:item)", $entries->item(0));
- $this->entries[$offset] = $entry;
- }
- $this->idMappings[$id] = $entry;
- return $entry;
- }
- return false;
- }
-
- /**
- * Get details of the image associated with the feed.
- *
- * @return array|false an array simply containing the child elements
- */
- protected function getImage()
- {
- $images = $this->model->getElementsByTagName('image');
- if ($images->length > 0) {
- $image = $images->item(0);
- $details = array();
- if ($image->hasChildNodes()) {
- $details = array(
- 'title' => $image->getElementsByTagName('title')->item(0)->value,
- 'link' => $image->getElementsByTagName('link')->item(0)->value,
- 'url' => $image->getElementsByTagName('url')->item(0)->value);
- } else {
- $details = array('title' => false,
- 'link' => false,
- 'url' => $image->attributes->getNamedItem('resource')->nodeValue);
- }
- $details = array_merge($details, array('description' => false, 'height' => false, 'width' => false));
- if (! empty($details)) {
- return $details;
- }
- }
- return false;
- }
-
- /**
- * The textinput element is little used, but in the interests of
- * completeness we will support it.
- *
- * @return array|false
- */
- protected function getTextInput()
- {
- $inputs = $this->model->getElementsByTagName('textinput');
- if ($inputs->length > 0) {
- $input = $inputs->item(0);
- $results = array();
- $results['title'] = isset(
- $input->getElementsByTagName('title')->item(0)->value) ?
- $input->getElementsByTagName('title')->item(0)->value : null;
- $results['description'] = isset(
- $input->getElementsByTagName('description')->item(0)->value) ?
- $input->getElementsByTagName('description')->item(0)->value : null;
- $results['name'] = isset(
- $input->getElementsByTagName('name')->item(0)->value) ?
- $input->getElementsByTagName('name')->item(0)->value : null;
- $results['link'] = isset(
- $input->getElementsByTagName('link')->item(0)->value) ?
- $input->getElementsByTagName('link')->item(0)->value : null;
- if (empty($results['link']) and
- $input->attributes->getNamedItem('resource')) {
- $results['link'] =
- $input->attributes->getNamedItem('resource')->nodeValue;
- }
- if (! empty($results)) {
- return $results;
- }
- }
- return false;
- }
-
- /**
- * Employs various techniques to identify the author
- *
- * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher
- * elements for defining authorship in RSS1. We will try each of those in
- * turn in order to simulate the atom author element and will return it
- * as text.
- *
- * @return array|false
- */
- function getAuthor()
- {
- $options = array('creator', 'contributor', 'publisher');
- foreach ($options as $element) {
- $test = $this->model->getElementsByTagName($element);
- if ($test->length > 0) {
- return $test->item(0)->value;
- }
- }
- return false;
- }
-
- /**
- * Retrieve a link
- *
- * In RSS1 a link is a text element but in order to ensure that we resolve
- * URLs properly we have a special function for them.
- *
- * @return string
- */
- function getLink($offset = 0, $attribute = 'href', $params = false)
- {
- $links = $this->model->getElementsByTagName('link');
- if ($links->length <= $offset) {
- return false;
- }
- $link = $links->item($offset);
- return $this->addBase($link->nodeValue, $link);
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS11.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS11.php
deleted file mode 100755
index 3cd1ef15d..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS11.php
+++ /dev/null
@@ -1,276 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * RSS1.1 class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS11.php,v 1.6 2006/07/27 13:52:05 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This class handles RSS1.1 feeds. RSS1.1 is documented at:
- * http://inamidst.com/rss1.1/
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- * @todo Support for RDF:List
- * @todo Ensure xml:lang is accessible to users
- */
-class XML_Feed_Parser_RSS11 extends XML_Feed_Parser_Type
-{
- /**
- * The URI of the RelaxNG schema used to (optionally) validate the feed
- * @var string
- */
- private $relax = 'rss11.rnc';
-
- /**
- * We're likely to use XPath, so let's keep it global
- * @var DOMXPath
- */
- protected $xpath;
-
- /**
- * The feed type we are parsing
- * @var string
- */
- public $version = 'RSS 1.0';
-
- /**
- * The class used to represent individual items
- * @var string
- */
- protected $itemClass = 'XML_Feed_Parser_RSS1Element';
-
- /**
- * The element containing entries
- * @var string
- */
- protected $itemElement = 'item';
-
- /**
- * Here we map those elements we're not going to handle individually
- * to the constructs they are. The optional second parameter in the array
- * tells the parser whether to 'fall back' (not apt. at the feed level) or
- * fail if the element is missing. If the parameter is not set, the function
- * will simply return false and leave it to the client to decide what to do.
- * @var array
- */
- protected $map = array(
- 'title' => array('Text'),
- 'link' => array('Text'),
- 'description' => array('Text'),
- 'image' => array('Image'),
- 'updatePeriod' => array('Text'),
- 'updateFrequency' => array('Text'),
- 'updateBase' => array('Date'),
- 'rights' => array('Text'), # dc:rights
- 'description' => array('Text'), # dc:description
- 'creator' => array('Text'), # dc:creator
- 'publisher' => array('Text'), # dc:publisher
- 'contributor' => array('Text'), # dc:contributor
- 'date' => array('Date') # dc:contributor
- );
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS2.
- * @var array
- */
- protected $compatMap = array(
- 'title' => array('title'),
- 'link' => array('link'),
- 'subtitle' => array('description'),
- 'author' => array('creator'),
- 'updated' => array('date'));
-
- /**
- * We will be working with multiple namespaces and it is useful to
- * keep them together. We will retain support for some common RSS1.0 modules
- * @var array
- */
- protected $namespaces = array(
- 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
- 'rss' => 'http://purl.org/net/rss1.1#',
- 'dc' => 'http://purl.org/rss/1.0/modules/dc/',
- 'content' => 'http://purl.org/rss/1.0/modules/content/',
- 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/');
-
- /**
- * Our constructor does nothing more than its parent.
- *
- * @param DOMDocument $xml A DOM object representing the feed
- * @param bool (optional) $string Whether or not to validate this feed
- */
- function __construct(DOMDocument $model, $strict = false)
- {
- $this->model = $model;
-
- if ($strict) {
- $validate = $this->model->relaxNGValidate(self::getSchemaDir .
- DIRECTORY_SEPARATOR . $this->relax);
- if (! $validate) {
- throw new XML_Feed_Parser_Exception('Failed required validation');
- }
- }
-
- $this->xpath = new DOMXPath($model);
- foreach ($this->namespaces as $key => $value) {
- $this->xpath->registerNamespace($key, $value);
- }
- $this->numberEntries = $this->count('item');
- }
-
- /**
- * Attempts to identify an element by ID given by the rdf:about attribute
- *
- * This is not really something that will work with RSS1.1 as it does not have
- * clear restrictions on the global uniqueness of IDs. We will employ the
- * _very_ hit and miss method of selecting entries based on the rdf:about
- * attribute. Please note that this is even more hit and miss with RSS1.1 than
- * with RSS1.0 since RSS1.1 does not require the rdf:about attribute for items.
- *
- * @param string $id any valid ID.
- * @return XML_Feed_Parser_RSS1Element
- */
- function getEntryById($id)
- {
- if (isset($this->idMappings[$id])) {
- return $this->entries[$this->idMappings[$id]];
- }
-
- $entries = $this->xpath->query("//rss:item[@rdf:about='$id']");
- if ($entries->length > 0) {
- $classname = $this->itemClass;
- $entry = new $classname($entries->item(0), $this);
- return $entry;
- }
- return false;
- }
-
- /**
- * Get details of the image associated with the feed.
- *
- * @return array|false an array simply containing the child elements
- */
- protected function getImage()
- {
- $images = $this->model->getElementsByTagName('image');
- if ($images->length > 0) {
- $image = $images->item(0);
- $details = array();
- if ($image->hasChildNodes()) {
- $details = array(
- 'title' => $image->getElementsByTagName('title')->item(0)->value,
- 'url' => $image->getElementsByTagName('url')->item(0)->value);
- if ($image->getElementsByTagName('link')->length > 0) {
- $details['link'] =
- $image->getElementsByTagName('link')->item(0)->value;
- }
- } else {
- $details = array('title' => false,
- 'link' => false,
- 'url' => $image->attributes->getNamedItem('resource')->nodeValue);
- }
- $details = array_merge($details,
- array('description' => false, 'height' => false, 'width' => false));
- if (! empty($details)) {
- return $details;
- }
- }
- return false;
- }
-
- /**
- * The textinput element is little used, but in the interests of
- * completeness we will support it.
- *
- * @return array|false
- */
- protected function getTextInput()
- {
- $inputs = $this->model->getElementsByTagName('textinput');
- if ($inputs->length > 0) {
- $input = $inputs->item(0);
- $results = array();
- $results['title'] = isset(
- $input->getElementsByTagName('title')->item(0)->value) ?
- $input->getElementsByTagName('title')->item(0)->value : null;
- $results['description'] = isset(
- $input->getElementsByTagName('description')->item(0)->value) ?
- $input->getElementsByTagName('description')->item(0)->value : null;
- $results['name'] = isset(
- $input->getElementsByTagName('name')->item(0)->value) ?
- $input->getElementsByTagName('name')->item(0)->value : null;
- $results['link'] = isset(
- $input->getElementsByTagName('link')->item(0)->value) ?
- $input->getElementsByTagName('link')->item(0)->value : null;
- if (empty($results['link']) and
- $input->attributes->getNamedItem('resource')) {
- $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue;
- }
- if (! empty($results)) {
- return $results;
- }
- }
- return false;
- }
-
- /**
- * Attempts to discern authorship
- *
- * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher
- * elements for defining authorship in RSS1. We will try each of those in
- * turn in order to simulate the atom author element and will return it
- * as text.
- *
- * @return array|false
- */
- function getAuthor()
- {
- $options = array('creator', 'contributor', 'publisher');
- foreach ($options as $element) {
- $test = $this->model->getElementsByTagName($element);
- if ($test->length > 0) {
- return $test->item(0)->value;
- }
- }
- return false;
- }
-
- /**
- * Retrieve a link
- *
- * In RSS1 a link is a text element but in order to ensure that we resolve
- * URLs properly we have a special function for them.
- *
- * @return string
- */
- function getLink($offset = 0, $attribute = 'href', $params = false)
- {
- $links = $this->model->getElementsByTagName('link');
- if ($links->length <= $offset) {
- return false;
- }
- $link = $links->item($offset);
- return $this->addBase($link->nodeValue, $link);
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS11Element.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS11Element.php
deleted file mode 100755
index 75918beda..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS11Element.php
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * RSS1 Element class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS11Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/*
- * This class provides support for RSS 1.1 entries. It will usually be called by
- * XML_Feed_Parser_RSS11 with which it shares many methods.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_RSS11Element extends XML_Feed_Parser_RSS11
-{
- /**
- * This will be a reference to the parent object for when we want
- * to use a 'fallback' rule
- * @var XML_Feed_Parser_RSS1
- */
- protected $parent;
-
- /**
- * Our specific element map
- * @var array
- */
- protected $map = array(
- 'id' => array('Id'),
- 'title' => array('Text'),
- 'link' => array('Link'),
- 'description' => array('Text'), # or dc:description
- 'category' => array('Category'),
- 'rights' => array('Text'), # dc:rights
- 'creator' => array('Text'), # dc:creator
- 'publisher' => array('Text'), # dc:publisher
- 'contributor' => array('Text'), # dc:contributor
- 'date' => array('Date'), # dc:date
- 'content' => array('Content')
- );
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS1.
- * @var array
- */
- protected $compatMap = array(
- 'content' => array('content'),
- 'updated' => array('lastBuildDate'),
- 'published' => array('pubdate'),
- 'subtitle' => array('description'),
- 'updated' => array('date'),
- 'author' => array('creator'),
- 'contributor' => array('contributor')
- );
-
- /**
- * Store useful information for later.
- *
- * @param DOMElement $element - this item as a DOM element
- * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member
- */
- function __construct(DOMElement $element, $parent, $xmlBase = '')
- {
- $this->model = $element;
- $this->parent = $parent;
- }
-
- /**
- * If an rdf:about attribute is specified, return that as an ID
- *
- * There is no established way of showing an ID for an RSS1 entry. We will
- * simulate it using the rdf:about attribute of the entry element. This cannot
- * be relied upon for unique IDs but may prove useful.
- *
- * @return string|false
- */
- function getId()
- {
- if ($this->model->attributes->getNamedItem('about')) {
- return $this->model->attributes->getNamedItem('about')->nodeValue;
- }
- return false;
- }
-
- /**
- * Return the entry's content
- *
- * The official way to include full content in an RSS1 entry is to use
- * the content module's element 'encoded'. Often, however, the 'description'
- * element is used instead. We will offer that as a fallback.
- *
- * @return string|false
- */
- function getContent()
- {
- $options = array('encoded', 'description');
- foreach ($options as $element) {
- $test = $this->model->getElementsByTagName($element);
- if ($test->length == 0) {
- continue;
- }
- if ($test->item(0)->hasChildNodes()) {
- $value = '';
- foreach ($test->item(0)->childNodes as $child) {
- if ($child instanceof DOMText) {
- $value .= $child->nodeValue;
- } else {
- $simple = simplexml_import_dom($child);
- $value .= $simple->asXML();
- }
- }
- return $value;
- } else if ($test->length > 0) {
- return $test->item(0)->nodeValue;
- }
- }
- return false;
- }
-
- /**
- * How RSS1.1 should support for enclosures is not clear. For now we will return
- * false.
- *
- * @return false
- */
- function getEnclosure()
- {
- return false;
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS1Element.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS1Element.php
deleted file mode 100755
index 8e36d5a9b..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS1Element.php
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * RSS1 Element class for XML_Feed_Parser
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS1Element.php,v 1.6 2006/06/30 17:41:56 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/*
- * This class provides support for RSS 1.0 entries. It will usually be called by
- * XML_Feed_Parser_RSS1 with which it shares many methods.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_RSS1Element extends XML_Feed_Parser_RSS1
-{
- /**
- * This will be a reference to the parent object for when we want
- * to use a 'fallback' rule
- * @var XML_Feed_Parser_RSS1
- */
- protected $parent;
-
- /**
- * Our specific element map
- * @var array
- */
- protected $map = array(
- 'id' => array('Id'),
- 'title' => array('Text'),
- 'link' => array('Link'),
- 'description' => array('Text'), # or dc:description
- 'category' => array('Category'),
- 'rights' => array('Text'), # dc:rights
- 'creator' => array('Text'), # dc:creator
- 'publisher' => array('Text'), # dc:publisher
- 'contributor' => array('Text'), # dc:contributor
- 'date' => array('Date'), # dc:date
- 'content' => array('Content')
- );
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS1.
- * @var array
- */
- protected $compatMap = array(
- 'content' => array('content'),
- 'updated' => array('lastBuildDate'),
- 'published' => array('pubdate'),
- 'subtitle' => array('description'),
- 'updated' => array('date'),
- 'author' => array('creator'),
- 'contributor' => array('contributor')
- );
-
- /**
- * Store useful information for later.
- *
- * @param DOMElement $element - this item as a DOM element
- * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member
- */
- function __construct(DOMElement $element, $parent, $xmlBase = '')
- {
- $this->model = $element;
- $this->parent = $parent;
- }
-
- /**
- * If an rdf:about attribute is specified, return it as an ID
- *
- * There is no established way of showing an ID for an RSS1 entry. We will
- * simulate it using the rdf:about attribute of the entry element. This cannot
- * be relied upon for unique IDs but may prove useful.
- *
- * @return string|false
- */
- function getId()
- {
- if ($this->model->attributes->getNamedItem('about')) {
- return $this->model->attributes->getNamedItem('about')->nodeValue;
- }
- return false;
- }
-
- /**
- * How RSS1 should support for enclosures is not clear. For now we will return
- * false.
- *
- * @return false
- */
- function getEnclosure()
- {
- return false;
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS2.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS2.php
deleted file mode 100644
index 0936bd2f5..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS2.php
+++ /dev/null
@@ -1,335 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Class representing feed-level data for an RSS2 feed
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS2.php,v 1.12 2008/03/08 18:16:45 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This class handles RSS2 feeds.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type
-{
- /**
- * The URI of the RelaxNG schema used to (optionally) validate the feed
- * @var string
- */
- private $relax = 'rss20.rnc';
-
- /**
- * We're likely to use XPath, so let's keep it global
- * @var DOMXPath
- */
- protected $xpath;
-
- /**
- * The feed type we are parsing
- * @var string
- */
- public $version = 'RSS 2.0';
-
- /**
- * The class used to represent individual items
- * @var string
- */
- protected $itemClass = 'XML_Feed_Parser_RSS2Element';
-
- /**
- * The element containing entries
- * @var string
- */
- protected $itemElement = 'item';
-
- /**
- * Here we map those elements we're not going to handle individually
- * to the constructs they are. The optional second parameter in the array
- * tells the parser whether to 'fall back' (not apt. at the feed level) or
- * fail if the element is missing. If the parameter is not set, the function
- * will simply return false and leave it to the client to decide what to do.
- * @var array
- */
- protected $map = array(
- 'ttl' => array('Text'),
- 'pubDate' => array('Date'),
- 'lastBuildDate' => array('Date'),
- 'title' => array('Text'),
- 'link' => array('Link'),
- 'description' => array('Text'),
- 'language' => array('Text'),
- 'copyright' => array('Text'),
- 'managingEditor' => array('Text'),
- 'webMaster' => array('Text'),
- 'category' => array('Text'),
- 'generator' => array('Text'),
- 'docs' => array('Text'),
- 'ttl' => array('Text'),
- 'image' => array('Image'),
- 'skipDays' => array('skipDays'),
- 'skipHours' => array('skipHours'));
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS2.
- * @var array
- */
- protected $compatMap = array(
- 'title' => array('title'),
- 'rights' => array('copyright'),
- 'updated' => array('lastBuildDate'),
- 'subtitle' => array('description'),
- 'date' => array('pubDate'),
- 'author' => array('managingEditor'));
-
- protected $namespaces = array(
- 'dc' => 'http://purl.org/rss/1.0/modules/dc/',
- 'content' => 'http://purl.org/rss/1.0/modules/content/');
-
- /**
- * Our constructor does nothing more than its parent.
- *
- * @param DOMDocument $xml A DOM object representing the feed
- * @param bool (optional) $string Whether or not to validate this feed
- */
- function __construct(DOMDocument $model, $strict = false)
- {
- $this->model = $model;
-
- if ($strict) {
- if (! $this->model->relaxNGValidate($this->relax)) {
- throw new XML_Feed_Parser_Exception('Failed required validation');
- }
- }
-
- $this->xpath = new DOMXPath($this->model);
- foreach ($this->namespaces as $key => $value) {
- $this->xpath->registerNamespace($key, $value);
- }
- $this->numberEntries = $this->count('item');
- }
-
- /**
- * Retrieves an entry by ID, if the ID is specified with the guid element
- *
- * This is not really something that will work with RSS2 as it does not have
- * clear restrictions on the global uniqueness of IDs. But we can emulate
- * it by allowing access based on the 'guid' element. If DOMXPath::evaluate
- * is available, we also use that to store a reference to the entry in the array
- * used by getEntryByOffset so that method does not have to seek out the entry
- * if it's requested that way.
- *
- * @param string $id any valid ID.
- * @return XML_Feed_Parser_RSS2Element
- */
- function getEntryById($id)
- {
- if (isset($this->idMappings[$id])) {
- return $this->entries[$this->idMappings[$id]];
- }
-
- $entries = $this->xpath->query("//item[guid='$id']");
- if ($entries->length > 0) {
- $entry = new $this->itemElement($entries->item(0), $this);
- if (in_array('evaluate', get_class_methods($this->xpath))) {
- $offset = $this->xpath->evaluate("count(preceding-sibling::item)", $entries->item(0));
- $this->entries[$offset] = $entry;
- }
- $this->idMappings[$id] = $entry;
- return $entry;
- }
- }
-
- /**
- * Get a category from the element
- *
- * The category element is a simple text construct which can occur any number
- * of times. We allow access by offset or access to an array of results.
- *
- * @param string $call for compatibility with our overloading
- * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array
- * @return string|array|false
- */
- function getCategory($call, $arguments = array())
- {
- $categories = $this->model->getElementsByTagName('category');
- $offset = empty($arguments[0]) ? 0 : $arguments[0];
- $array = empty($arguments[1]) ? false : true;
- if ($categories->length <= $offset) {
- return false;
- }
- if ($array) {
- $list = array();
- foreach ($categories as $category) {
- array_push($list, $category->nodeValue);
- }
- return $list;
- }
- return $categories->item($offset)->nodeValue;
- }
-
- /**
- * Get details of the image associated with the feed.
- *
- * @return array|false an array simply containing the child elements
- */
- protected function getImage()
- {
- $images = $this->xpath->query("//image");
- if ($images->length > 0) {
- $image = $images->item(0);
- $desc = $image->getElementsByTagName('description');
- $description = $desc->length ? $desc->item(0)->nodeValue : false;
- $heigh = $image->getElementsByTagName('height');
- $height = $heigh->length ? $heigh->item(0)->nodeValue : false;
- $widt = $image->getElementsByTagName('width');
- $width = $widt->length ? $widt->item(0)->nodeValue : false;
- return array(
- 'title' => $image->getElementsByTagName('title')->item(0)->nodeValue,
- 'link' => $image->getElementsByTagName('link')->item(0)->nodeValue,
- 'url' => $image->getElementsByTagName('url')->item(0)->nodeValue,
- 'description' => $description,
- 'height' => $height,
- 'width' => $width);
- }
- return false;
- }
-
- /**
- * The textinput element is little used, but in the interests of
- * completeness...
- *
- * @return array|false
- */
- function getTextInput()
- {
- $inputs = $this->model->getElementsByTagName('input');
- if ($inputs->length > 0) {
- $input = $inputs->item(0);
- return array(
- 'title' => $input->getElementsByTagName('title')->item(0)->value,
- 'description' =>
- $input->getElementsByTagName('description')->item(0)->value,
- 'name' => $input->getElementsByTagName('name')->item(0)->value,
- 'link' => $input->getElementsByTagName('link')->item(0)->value);
- }
- return false;
- }
-
- /**
- * Utility function for getSkipDays and getSkipHours
- *
- * This is a general function used by both getSkipDays and getSkipHours. It simply
- * returns an array of the values of the children of the appropriate tag.
- *
- * @param string $tagName The tag name (getSkipDays or getSkipHours)
- * @return array|false
- */
- protected function getSkips($tagName)
- {
- $hours = $this->model->getElementsByTagName($tagName);
- if ($hours->length == 0) {
- return false;
- }
- $skipHours = array();
- foreach($hours->item(0)->childNodes as $hour) {
- if ($hour instanceof DOMElement) {
- array_push($skipHours, $hour->nodeValue);
- }
- }
- return $skipHours;
- }
-
- /**
- * Retrieve skipHours data
- *
- * The skiphours element provides a list of hours on which this feed should
- * not be checked. We return an array of those hours (integers, 24 hour clock)
- *
- * @return array
- */
- function getSkipHours()
- {
- return $this->getSkips('skipHours');
- }
-
- /**
- * Retrieve skipDays data
- *
- * The skipdays element provides a list of days on which this feed should
- * not be checked. We return an array of those days.
- *
- * @return array
- */
- function getSkipDays()
- {
- return $this->getSkips('skipDays');
- }
-
- /**
- * Return content of the little-used 'cloud' element
- *
- * The cloud element is rarely used. It is designed to provide some details
- * of a location to update the feed.
- *
- * @return array an array of the attributes of the element
- */
- function getCloud()
- {
- $cloud = $this->model->getElementsByTagName('cloud');
- if ($cloud->length == 0) {
- return false;
- }
- $cloudData = array();
- foreach ($cloud->item(0)->attributes as $attribute) {
- $cloudData[$attribute->name] = $attribute->value;
- }
- return $cloudData;
- }
-
- /**
- * Get link URL
- *
- * In RSS2 a link is a text element but in order to ensure that we resolve
- * URLs properly we have a special function for them. We maintain the
- * parameter used by the atom getLink method, though we only use the offset
- * parameter.
- *
- * @param int $offset The position of the link within the feed. Starts from 0
- * @param string $attribute The attribute of the link element required
- * @param array $params An array of other parameters. Not used.
- * @return string
- */
- function getLink($offset, $attribute = 'href', $params = array())
- {
- $xPath = new DOMXPath($this->model);
- $links = $xPath->query('//link');
-
- if ($links->length <= $offset) {
- return false;
- }
- $link = $links->item($offset);
- return $this->addBase($link->nodeValue, $link);
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/RSS2Element.php b/plugins/OStatus/extlib/XML/Feed/Parser/RSS2Element.php
deleted file mode 100755
index 6edf910dc..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/RSS2Element.php
+++ /dev/null
@@ -1,171 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Class representing entries in an RSS2 feed.
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: RSS2Element.php,v 1.11 2006/07/26 21:18:47 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This class provides support for RSS 2.0 entries. It will usually be
- * called by XML_Feed_Parser_RSS2 with which it shares many methods.
- *
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- * @package XML_Feed_Parser
- */
-class XML_Feed_Parser_RSS2Element extends XML_Feed_Parser_RSS2
-{
- /**
- * This will be a reference to the parent object for when we want
- * to use a 'fallback' rule
- * @var XML_Feed_Parser_RSS2
- */
- protected $parent;
-
- /**
- * Our specific element map
- * @var array
- */
- protected $map = array(
- 'title' => array('Text'),
- 'guid' => array('Guid'),
- 'description' => array('Text'),
- 'author' => array('Text'),
- 'comments' => array('Text'),
- 'enclosure' => array('Enclosure'),
- 'pubDate' => array('Date'),
- 'source' => array('Source'),
- 'link' => array('Text'),
- 'content' => array('Content'));
-
- /**
- * Here we map some elements to their atom equivalents. This is going to be
- * quite tricky to pull off effectively (and some users' methods may vary)
- * but is worth trying. The key is the atom version, the value is RSS2.
- * @var array
- */
- protected $compatMap = array(
- 'id' => array('guid'),
- 'updated' => array('lastBuildDate'),
- 'published' => array('pubdate'),
- 'guidislink' => array('guid', 'ispermalink'),
- 'summary' => array('description'));
-
- /**
- * Store useful information for later.
- *
- * @param DOMElement $element - this item as a DOM element
- * @param XML_Feed_Parser_RSS2 $parent - the feed of which this is a member
- */
- function __construct(DOMElement $element, $parent, $xmlBase = '')
- {
- $this->model = $element;
- $this->parent = $parent;
- }
-
- /**
- * Get the value of the guid element, if specified
- *
- * guid is the closest RSS2 has to atom's ID. It is usually but not always a
- * URI. The one attribute that RSS2 can posess is 'ispermalink' which specifies
- * whether the guid is itself dereferencable. Use of guid is not obligatory,
- * but is advisable. To get the guid you would call $item->id() (for atom
- * compatibility) or $item->guid(). To check if this guid is a permalink call
- * $item->guid("ispermalink").
- *
- * @param string $method - the method name being called
- * @param array $params - parameters required
- * @return string the guid or value of ispermalink
- */
- protected function getGuid($method, $params)
- {
- $attribute = (isset($params[0]) and $params[0] == 'ispermalink') ?
- true : false;
- $tag = $this->model->getElementsByTagName('guid');
- if ($tag->length > 0) {
- if ($attribute) {
- if ($tag->hasAttribute("ispermalink")) {
- return $tag->getAttribute("ispermalink");
- }
- }
- return $tag->item(0)->nodeValue;
- }
- return false;
- }
-
- /**
- * Access details of file enclosures
- *
- * The RSS2 spec is ambiguous as to whether an enclosure element must be
- * unique in a given entry. For now we will assume it needn't, and allow
- * for an offset.
- *
- * @param string $method - the method being called
- * @param array $parameters - we expect the first of these to be our offset
- * @return array|false
- */
- protected function getEnclosure($method, $parameters)
- {
- $encs = $this->model->getElementsByTagName('enclosure');
- $offset = isset($parameters[0]) ? $parameters[0] : 0;
- if ($encs->length > $offset) {
- try {
- if (! $encs->item($offset)->hasAttribute('url')) {
- return false;
- }
- $attrs = $encs->item($offset)->attributes;
- return array(
- 'url' => $attrs->getNamedItem('url')->value,
- 'length' => $attrs->getNamedItem('length')->value,
- 'type' => $attrs->getNamedItem('type')->value);
- } catch (Exception $e) {
- return false;
- }
- }
- return false;
- }
-
- /**
- * Get the entry source if specified
- *
- * source is an optional sub-element of item. Like atom:source it tells
- * us about where the entry came from (eg. if it's been copied from another
- * feed). It is not a rich source of metadata in the same way as atom:source
- * and while it would be good to maintain compatibility by returning an
- * XML_Feed_Parser_RSS2 element, it makes a lot more sense to return an array.
- *
- * @return array|false
- */
- protected function getSource()
- {
- $get = $this->model->getElementsByTagName('source');
- if ($get->length) {
- $source = $get->item(0);
- $array = array(
- 'content' => $source->nodeValue);
- foreach ($source->attributes as $attribute) {
- $array[$attribute->name] = $attribute->value;
- }
- return $array;
- }
- return false;
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/Parser/Type.php b/plugins/OStatus/extlib/XML/Feed/Parser/Type.php
deleted file mode 100644
index 75052619b..000000000
--- a/plugins/OStatus/extlib/XML/Feed/Parser/Type.php
+++ /dev/null
@@ -1,467 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Abstract class providing common methods for XML_Feed_Parser feeds.
- *
- * PHP versions 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category XML
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @copyright 2005 James Stewart <james@jystewart.net>
- * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
- * @version CVS: $Id: Type.php,v 1.25 2008/03/08 18:39:09 jystewart Exp $
- * @link http://pear.php.net/package/XML_Feed_Parser/
- */
-
-/**
- * This abstract class provides some general methods that are likely to be
- * implemented exactly the same way for all feed types.
- *
- * @package XML_Feed_Parser
- * @author James Stewart <james@jystewart.net>
- * @version Release: 1.0.3
- */
-abstract class XML_Feed_Parser_Type
-{
- /**
- * Where we store our DOM object for this feed
- * @var DOMDocument
- */
- public $model;
-
- /**
- * For iteration we'll want a count of the number of entries
- * @var int
- */
- public $numberEntries;
-
- /**
- * Where we store our entry objects once instantiated
- * @var array
- */
- public $entries = array();
-
- /**
- * Store mappings between entry IDs and their position in the feed
- */
- public $idMappings = array();
-
- /**
- * Proxy to allow use of element names as method names
- *
- * We are not going to provide methods for every entry type so this
- * function will allow for a lot of mapping. We rely pretty heavily
- * on this to handle our mappings between other feed types and atom.
- *
- * @param string $call - the method attempted
- * @param array $arguments - arguments to that method
- * @return mixed
- */
- function __call($call, $arguments = array())
- {
- if (! is_array($arguments)) {
- $arguments = array();
- }
-
- if (isset($this->compatMap[$call])) {
- $tempMap = $this->compatMap;
- $tempcall = array_pop($tempMap[$call]);
- if (! empty($tempMap)) {
- $arguments = array_merge($arguments, $tempMap[$call]);
- }
- $call = $tempcall;
- }
-
- /* To be helpful, we allow a case-insensitive search for this method */
- if (! isset($this->map[$call])) {
- foreach (array_keys($this->map) as $key) {
- if (strtoupper($key) == strtoupper($call)) {
- $call = $key;
- break;
- }
- }
- }
-
- if (empty($this->map[$call])) {
- return false;
- }
-
- $method = 'get' . $this->map[$call][0];
- if ($method == 'getLink') {
- $offset = empty($arguments[0]) ? 0 : $arguments[0];
- $attribute = empty($arguments[1]) ? 'href' : $arguments[1];
- $params = isset($arguments[2]) ? $arguments[2] : array();
- return $this->getLink($offset, $attribute, $params);
- }
- if (method_exists($this, $method)) {
- return $this->$method($call, $arguments);
- }
-
- return false;
- }
-
- /**
- * Proxy to allow use of element names as attribute names
- *
- * For many elements variable-style access will be desirable. This function
- * provides for that.
- *
- * @param string $value - the variable required
- * @return mixed
- */
- function __get($value)
- {
- return $this->__call($value, array());
- }
-
- /**
- * Utility function to help us resolve xml:base values
- *
- * We have other methods which will traverse the DOM and work out the different
- * xml:base declarations we need to be aware of. We then need to combine them.
- * If a declaration starts with a protocol then we restart the string. If it
- * starts with a / then we add on to the domain name. Otherwise we simply tag
- * it on to the end.
- *
- * @param string $base - the base to add the link to
- * @param string $link
- */
- function combineBases($base, $link)
- {
- if (preg_match('/^[A-Za-z]+:\/\//', $link)) {
- return $link;
- } else if (preg_match('/^\//', $link)) {
- /* Extract domain and suffix link to that */
- preg_match('/^([A-Za-z]+:\/\/.*)?\/*/', $base, $results);
- $firstLayer = $results[0];
- return $firstLayer . "/" . $link;
- } else if (preg_match('/^\.\.\//', $base)) {
- /* Step up link to find place to be */
- preg_match('/^((\.\.\/)+)(.*)$/', $link, $bases);
- $suffix = $bases[3];
- $count = preg_match_all('/\.\.\//', $bases[1], $steps);
- $url = explode("/", $base);
- for ($i = 0; $i <= $count; $i++) {
- array_pop($url);
- }
- return implode("/", $url) . "/" . $suffix;
- } else if (preg_match('/^(?!\/$)/', $base)) {
- $base = preg_replace('/(.*\/).*$/', '$1', $base) ;
- return $base . $link;
- } else {
- /* Just stick it on the end */
- return $base . $link;
- }
- }
-
- /**
- * Determine whether we need to apply our xml:base rules
- *
- * Gets us the xml:base data and then processes that with regard
- * to our current link.
- *
- * @param string
- * @param DOMElement
- * @return string
- */
- function addBase($link, $element)
- {
- if (preg_match('/^[A-Za-z]+:\/\//', $link)) {
- return $link;
- }
-
- return $this->combineBases($element->baseURI, $link);
- }
-
- /**
- * Get an entry by its position in the feed, starting from zero
- *
- * As well as allowing the items to be iterated over we want to allow
- * users to be able to access a specific entry. This is one of two ways of
- * doing that, the other being by ID.
- *
- * @param int $offset
- * @return XML_Feed_Parser_RSS1Element
- */
- function getEntryByOffset($offset)
- {
- if (! isset($this->entries[$offset])) {
- $entries = $this->model->getElementsByTagName($this->itemElement);
- if ($entries->length > $offset) {
- $xmlBase = $entries->item($offset)->baseURI;
- $this->entries[$offset] = new $this->itemClass(
- $entries->item($offset), $this, $xmlBase);
- if ($id = $this->entries[$offset]->id) {
- $this->idMappings[$id] = $this->entries[$offset];
- }
- } else {
- throw new XML_Feed_Parser_Exception('No entries found');
- }
- }
-
- return $this->entries[$offset];
- }
-
- /**
- * Return a date in seconds since epoch.
- *
- * Get a date construct. We use PHP's strtotime to return it as a unix datetime, which
- * is the number of seconds since 1970-01-01 00:00:00.
- *
- * @link http://php.net/strtotime
- * @param string $method The name of the date construct we want
- * @param array $arguments Included for compatibility with our __call usage
- * @return int|false datetime
- */
- protected function getDate($method, $arguments)
- {
- $time = $this->model->getElementsByTagName($method);
- if ($time->length == 0 || empty($time->item(0)->nodeValue)) {
- return false;
- }
- return strtotime($time->item(0)->nodeValue);
- }
-
- /**
- * Get a text construct.
- *
- * @param string $method The name of the text construct we want
- * @param array $arguments Included for compatibility with our __call usage
- * @return string
- */
- protected function getText($method, $arguments = array())
- {
- $tags = $this->model->getElementsByTagName($method);
- if ($tags->length > 0) {
- $value = $tags->item(0)->nodeValue;
- return $value;
- }
- return false;
- }
-
- /**
- * Apply various rules to retrieve category data.
- *
- * There is no single way of declaring a category in RSS1/1.1 as there is in RSS2
- * and Atom. Instead the usual approach is to use the dublin core namespace to
- * declare categories. For example delicious use both:
- * <dc:subject>PEAR</dc:subject> and: <taxo:topics><rdf:Bag>
- * <rdf:li resource="http://del.icio.us/tag/PEAR" /></rdf:Bag></taxo:topics>
- * to declare a categorisation of 'PEAR'.
- *
- * We need to be sensitive to this where possible.
- *
- * @param string $call for compatibility with our overloading
- * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array
- * @return string|array|false
- */
- protected function getCategory($call, $arguments)
- {
- $categories = $this->model->getElementsByTagName('subject');
- $offset = empty($arguments[0]) ? 0 : $arguments[0];
- $array = empty($arguments[1]) ? false : true;
- if ($categories->length <= $offset) {
- return false;
- }
- if ($array) {
- $list = array();
- foreach ($categories as $category) {
- array_push($list, $category->nodeValue);
- }
- return $list;
- }
- return $categories->item($offset)->nodeValue;
- }
-
- /**
- * Count occurrences of an element
- *
- * This function will tell us how many times the element $type
- * appears at this level of the feed.
- *
- * @param string $type the element we want to get a count of
- * @return int
- */
- protected function count($type)
- {
- if ($tags = $this->model->getElementsByTagName($type)) {
- return $tags->length;
- }
- return 0;
- }
-
- /**
- * Part of our xml:base processing code
- *
- * We need a couple of methods to access XHTML content stored in feeds.
- * This is because we dereference all xml:base references before returning
- * the element. This method handles the attributes.
- *
- * @param DOMElement $node The DOM node we are iterating over
- * @return string
- */
- function processXHTMLAttributes($node) {
- $return = '';
- foreach ($node->attributes as $attribute) {
- if ($attribute->name == 'src' or $attribute->name == 'href') {
- $attribute->value = $this->addBase(htmlentities($attribute->value, NULL, 'utf-8'), $attribute);
- }
- if ($attribute->name == 'base') {
- continue;
- }
- $return .= $attribute->name . '="' . htmlentities($attribute->value, NULL, 'utf-8') .'" ';
- }
- if (! empty($return)) {
- return ' ' . trim($return);
- }
- return '';
- }
-
- /**
- * Convert HTML entities based on the current character set.
- *
- * @param String
- * @return String
- */
- function processEntitiesForNodeValue($node)
- {
- if (function_exists('iconv')) {
- $current_encoding = $node->ownerDocument->encoding;
- $value = iconv($current_encoding, 'UTF-8', $node->nodeValue);
- } else if ($current_encoding == 'iso-8859-1') {
- $value = utf8_encode($node->nodeValue);
- } else {
- $value = $node->nodeValue;
- }
-
- $decoded = html_entity_decode($value, NULL, 'UTF-8');
- return htmlentities($decoded, NULL, 'UTF-8');
- }
-
- /**
- * Part of our xml:base processing code
- *
- * We need a couple of methods to access XHTML content stored in feeds.
- * This is because we dereference all xml:base references before returning
- * the element. This method recurs through the tree descending from the node
- * and builds our string.
- *
- * @param DOMElement $node The DOM node we are processing
- * @return string
- */
- function traverseNode($node)
- {
- $content = '';
-
- /* Add the opening of this node to the content */
- if ($node instanceof DOMElement) {
- $content .= '<' . $node->tagName .
- $this->processXHTMLAttributes($node) . '>';
- }
-
- /* Process children */
- if ($node->hasChildNodes()) {
- foreach ($node->childNodes as $child) {
- $content .= $this->traverseNode($child);
- }
- }
-
- if ($node instanceof DOMText) {
- $content .= $this->processEntitiesForNodeValue($node);
- }
-
- /* Add the closing of this node to the content */
- if ($node instanceof DOMElement) {
- $content .= '</' . $node->tagName . '>';
- }
-
- return $content;
- }
-
- /**
- * Get content from RSS feeds (atom has its own implementation)
- *
- * The official way to include full content in an RSS1 entry is to use
- * the content module's element 'encoded', and RSS2 feeds often duplicate that.
- * Often, however, the 'description' element is used instead. We will offer that
- * as a fallback. Atom uses its own approach and overrides this method.
- *
- * @return string|false
- */
- protected function getContent()
- {
- $options = array('encoded', 'description');
- foreach ($options as $element) {
- $test = $this->model->getElementsByTagName($element);
- if ($test->length == 0) {
- continue;
- }
- if ($test->item(0)->hasChildNodes()) {
- $value = '';
- foreach ($test->item(0)->childNodes as $child) {
- if ($child instanceof DOMText) {
- $value .= $child->nodeValue;
- } else {
- $simple = simplexml_import_dom($child);
- $value .= $simple->asXML();
- }
- }
- return $value;
- } else if ($test->length > 0) {
- return $test->item(0)->nodeValue;
- }
- }
- return false;
- }
-
- /**
- * Checks if this element has a particular child element.
- *
- * @param String
- * @param Integer
- * @return bool
- **/
- function hasKey($name, $offset = 0)
- {
- $search = $this->model->getElementsByTagName($name);
- return $search->length > $offset;
- }
-
- /**
- * Return an XML serialization of the feed, should it be required. Most
- * users however, will already have a serialization that they used when
- * instantiating the object.
- *
- * @return string XML serialization of element
- */
- function __toString()
- {
- $simple = simplexml_import_dom($this->model);
- return $simple->asXML();
- }
-
- /**
- * Get directory holding RNG schemas. Method is based on that
- * found in Contact_AddressBook.
- *
- * @return string PEAR data directory.
- * @access public
- * @static
- */
- static function getSchemaDir()
- {
- require_once 'PEAR/Config.php';
- $config = new PEAR_Config;
- return $config->get('data_dir') . '/XML_Feed_Parser/schemas';
- }
-}
-
-?> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/atom10-entryonly.xml b/plugins/OStatus/extlib/XML/Feed/samples/atom10-entryonly.xml
deleted file mode 100755
index 02e1c5800..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/atom10-entryonly.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<entry xmlns="http://www.w3.org/2005/Atom">
- <title>Atom draft-07 snapshot</title>
- <link rel="alternate" type="text/html"
- href="http://example.org/2005/04/02/atom"/>
- <link rel='enclosure' type="audio/mpeg" length="1337"
- href="http://example.org/audio/ph34r_my_podcast.mp3"/>
- <id>tag:example.org,2003:3.2397</id>
- <updated>2005-07-10T12:29:29Z</updated>
- <published>2003-12-13T08:29:29-04:00</published>
- <author>
- <name>Mark Pilgrim</name>
- <uri>http://example.org/</uri>
- <email>f8dy@example.com</email>
- </author>
- <contributor>
- <name>Sam Ruby</name>
- </contributor>
- <contributor>
- <name>Joe Gregorio</name>
- </contributor>
- <content type="xhtml" xml:lang="en"
- xml:base="http://diveintomark.org/">
- <div xmlns="http://www.w3.org/1999/xhtml">
- <p><i>[Update: The Atom draft is finished.]</i></p>
- </div>
- </content>
- </entry> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/atom10-example1.xml b/plugins/OStatus/extlib/XML/Feed/samples/atom10-example1.xml
deleted file mode 100755
index d181d2b6f..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/atom10-example1.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<feed xmlns="http://www.w3.org/2005/Atom">
-
- <title>Example Feed</title>
- <link href="http://example.org/"/>
- <updated>2003-12-13T18:30:02Z</updated>
- <author>
- <name>John Doe</name>
- </author>
- <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
-
- <entry>
- <title>Atom-Powered Robots Run Amok</title>
- <link href="http://example.org/2003/12/13/atom03"/>
- <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
- <updated>2003-12-13T18:30:02Z</updated>
- <summary>Some text.</summary>
- </entry>
-
-</feed> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/atom10-example2.xml b/plugins/OStatus/extlib/XML/Feed/samples/atom10-example2.xml
deleted file mode 100755
index 98abf9d54..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/atom10-example2.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
- <feed xmlns="http://www.w3.org/2005/Atom">
- <title type="text">dive into mark</title>
- <subtitle type="html">
- A &lt;em&gt;lot&lt;/em&gt; of effort
- went into making this effortless
- </subtitle>
- <updated>2005-07-31T12:29:29Z</updated>
- <id>tag:example.org,2003:3</id>
- <link rel="alternate" type="text/html"
- hreflang="en" href="http://example.org/"/>
- <link rel="self" type="application/atom+xml"
- href="http://example.org/feed.atom"/>
- <rights>Copyright (c) 2003, Mark Pilgrim</rights>
- <generator uri="http://www.example.com/" version="1.0">
- Example Toolkit
- </generator>
- <entry>
- <title>Atom draft-07 snapshot</title>
- <link rel="alternate" type="text/html"
- href="http://example.org/2005/04/02/atom"/>
- <link rel='enclosure' type="audio/mpeg" length="1337"
- href="http://example.org/audio/ph34r_my_podcast.mp3"/>
- <id>tag:example.org,2003:3.2397</id>
- <updated>2005-07-31T12:29:29Z</updated>
- <published>2003-12-13T08:29:29-04:00</published>
- <author>
- <name>Mark Pilgrim</name>
- <uri>http://example.org/</uri>
- <email>f8dy@example.com</email>
- </author>
- <contributor>
- <name>Sam Ruby</name>
- </contributor>
- <contributor>
- <name>Joe Gregorio</name>
- </contributor>
- <content type="xhtml" xml:lang="en"
- xml:base="http://diveintomark.org/">
- <div xmlns="http://www.w3.org/1999/xhtml">
- <p><i>[Update: The Atom draft is finished.]</i></p>
- </div>
- </content>
- </entry>
- </feed> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/delicious.feed b/plugins/OStatus/extlib/XML/Feed/samples/delicious.feed
deleted file mode 100755
index 32f9fa493..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/delicious.feed
+++ /dev/null
@@ -1,177 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<rdf:RDF
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns="http://purl.org/rss/1.0/"
- xmlns:cc="http://web.resource.org/cc/"
- xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
- xmlns:admin="http://webns.net/mvcb/"
->
-<channel rdf:about="http://del.icio.us/tag/greenbelt">
-<title>del.icio.us/tag/greenbelt</title>
-<link>http://del.icio.us/tag/greenbelt</link>
-<description>Text</description>
-<items>
- <rdf:Seq>
- <rdf:li rdf:resource="http://www.greenbelt.org.uk/" />
- <rdf:li rdf:resource="http://www.greenbelt.org.uk/" />
- <rdf:li rdf:resource="http://www.natuerlichwien.at/rundumadum/dergruenguertel/" />
- <rdf:li rdf:resource="http://www.flickerweb.co.uk/wiki/index.php/Tank#Seminars" />
- <rdf:li rdf:resource="http://www.greenbelt.ca/home.htm" />
- <rdf:li rdf:resource="http://pipwilsonbhp.blogspot.com/" />
- <rdf:li rdf:resource="http://maggidawn.typepad.com/maggidawn/" />
- <rdf:li rdf:resource="http://www.johndavies.org/" />
- <rdf:li rdf:resource="http://jonnybaker.blogs.com/" />
- </rdf:Seq>
-</items>
-</channel>
-
-<item rdf:about="http://www.greenbelt.org.uk/">
-<dc:title>Greenbelt - Homepage Section</dc:title>
-<link>http://www.greenbelt.org.uk/</link>
-<dc:creator>jonnybaker</dc:creator>
-<dc:date>2005-05-16T16:30:38Z</dc:date>
-<dc:subject>greenbelt</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/greenbelt" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://www.greenbelt.org.uk/">
-<title>Greenbelt festival (uk)</title>
-<link>http://www.greenbelt.org.uk/</link>
-<dc:creator>sssshhhh</dc:creator>
-<dc:date>2005-05-14T18:19:40Z</dc:date>
-<dc:subject>audiology festival gigs greenbelt</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/gigs" />
- <rdf:li resource="http://del.icio.us/tag/audiology" />
- <rdf:li resource="http://del.icio.us/tag/festival" />
- <rdf:li resource="http://del.icio.us/tag/greenbelt" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://www.natuerlichwien.at/rundumadum/dergruenguertel/">
-<title>Natuerlichwien.at - Rundumadum</title>
-<link>http://www.natuerlichwien.at/rundumadum/dergruenguertel/</link>
-<dc:creator>egmilman47</dc:creator>
-<dc:date>2005-05-06T21:33:41Z</dc:date>
-<dc:subject>Austria Vienna Wien greenbelt nature walking</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/Vienna" />
- <rdf:li resource="http://del.icio.us/tag/Wien" />
- <rdf:li resource="http://del.icio.us/tag/Austria" />
- <rdf:li resource="http://del.icio.us/tag/walking" />
- <rdf:li resource="http://del.icio.us/tag/nature" />
- <rdf:li resource="http://del.icio.us/tag/greenbelt" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://www.flickerweb.co.uk/wiki/index.php/Tank#Seminars">
-<title>Tank - GBMediaWiki</title>
-<link>http://www.flickerweb.co.uk/wiki/index.php/Tank#Seminars</link>
-<dc:creator>jystewart</dc:creator>
-<dc:date>2005-03-21T22:44:11Z</dc:date>
-<dc:subject>greenbelt</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/greenbelt" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://www.greenbelt.ca/home.htm">
-<title>Greenbelt homepage</title>
-<link>http://www.greenbelt.ca/home.htm</link>
-<dc:creator>Gooberoo</dc:creator>
-<dc:date>2005-03-01T22:43:17Z</dc:date>
-<dc:subject>greenbelt ontario</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/ontario" />
- <rdf:li resource="http://del.icio.us/tag/greenbelt" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://pipwilsonbhp.blogspot.com/">
-<title>Pip Wilson bhp ...... blog</title>
-<link>http://pipwilsonbhp.blogspot.com/</link>
-<dc:creator>sssshhhh</dc:creator>
-<dc:date>2004-12-27T11:20:51Z</dc:date>
-<dc:subject>Greenbelt friend ideas links thinking weblog</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/Greenbelt" />
- <rdf:li resource="http://del.icio.us/tag/thinking" />
- <rdf:li resource="http://del.icio.us/tag/ideas" />
- <rdf:li resource="http://del.icio.us/tag/links" />
- <rdf:li resource="http://del.icio.us/tag/friend" />
- <rdf:li resource="http://del.icio.us/tag/weblog" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://maggidawn.typepad.com/maggidawn/">
-<title>maggi dawn</title>
-<link>http://maggidawn.typepad.com/maggidawn/</link>
-<dc:creator>sssshhhh</dc:creator>
-<dc:date>2004-12-27T11:20:11Z</dc:date>
-<dc:subject>Greenbelt ideas links thinking weblog</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/Greenbelt" />
- <rdf:li resource="http://del.icio.us/tag/thinking" />
- <rdf:li resource="http://del.icio.us/tag/ideas" />
- <rdf:li resource="http://del.icio.us/tag/links" />
- <rdf:li resource="http://del.icio.us/tag/weblog" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://www.johndavies.org/">
-<title>John Davies</title>
-<link>http://www.johndavies.org/</link>
-<dc:creator>sssshhhh</dc:creator>
-<dc:date>2004-12-27T11:18:37Z</dc:date>
-<dc:subject>Greenbelt ideas links thinking weblog</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/Greenbelt" />
- <rdf:li resource="http://del.icio.us/tag/thinking" />
- <rdf:li resource="http://del.icio.us/tag/ideas" />
- <rdf:li resource="http://del.icio.us/tag/links" />
- <rdf:li resource="http://del.icio.us/tag/weblog" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-<item rdf:about="http://jonnybaker.blogs.com/">
-<title>jonnybaker</title>
-<link>http://jonnybaker.blogs.com/</link>
-<dc:creator>sssshhhh</dc:creator>
-<dc:date>2004-12-27T11:18:17Z</dc:date>
-<dc:subject>Greenbelt event ideas links resources thinking weblog youth</dc:subject>
-<taxo:topics>
- <rdf:Bag>
- <rdf:li resource="http://del.icio.us/tag/Greenbelt" />
- <rdf:li resource="http://del.icio.us/tag/thinking" />
- <rdf:li resource="http://del.icio.us/tag/ideas" />
- <rdf:li resource="http://del.icio.us/tag/links" />
- <rdf:li resource="http://del.icio.us/tag/weblog" />
- <rdf:li resource="http://del.icio.us/tag/youth" />
- <rdf:li resource="http://del.icio.us/tag/event" />
- <rdf:li resource="http://del.icio.us/tag/resources" />
- </rdf:Bag>
-</taxo:topics>
-</item>
-
-</rdf:RDF>
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/flickr.feed b/plugins/OStatus/extlib/XML/Feed/samples/flickr.feed
deleted file mode 100755
index 57e83af57..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/flickr.feed
+++ /dev/null
@@ -1,184 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<feed version="0.3" xmlns="http://purl.org/atom/ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/">
-
- <title>jamesstewart - Everyone's Tagged Photos</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/tags/jamesstewart/"/>
- <link rel="icon" type="image/jpeg" href="http://www.flickr.com/images/buddyicon.jpg"/>
- <info type="text/html" mode="escaped">A feed of jamesstewart - Everyone's Tagged Photos</info>
- <modified>2005-08-01T18:50:26Z</modified>
- <generator url="http://www.flickr.com/">Flickr</generator>
-
- <entry>
- <title>Oma and James</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/30484029@N00/30367516/"/>
- <link rel='enclosure' type="application/xml" href="http://james.anthropiccollective.org" />
- <id>tag:flickr.com,2004:/photo/30367516</id>
- <issued>2005-08-01T18:50:26Z</issued>
- <modified>2005-08-01T18:50:26Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/30484029@N00/&quot;&gt;kstewart&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/30484029@N00/30367516/&quot; title=&quot;Oma and James&quot;&gt;&lt;img src=&quot;http://photos23.flickr.com/30367516_1f685a16e8_m.jpg&quot; width=&quot;240&quot; height=&quot;180&quot; alt=&quot;Oma and James&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-&lt;p&gt;I have a beautiful Oma and a gorgeous husband.&lt;/p&gt;</content>
- <author>
- <name>kstewart</name>
- <url>http://www.flickr.com/people/30484029@N00/</url>
- </author>
- <dc:subject>jamesstewart oma stoelfamily</dc:subject>
- </entry>
- <entry>
- <title></title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/buddscreek/21376174/"/>
- <id>tag:flickr.com,2004:/photo/21376174</id>
- <issued>2005-06-25T02:00:35Z</issued>
- <modified>2005-06-25T02:00:35Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/buddscreek/&quot;&gt;Lan Rover&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/buddscreek/21376174/&quot; title=&quot;&quot;&gt;&lt;img src=&quot;http://photos17.flickr.com/21376174_4314fd8d5c_m.jpg&quot; width=&quot;240&quot; height=&quot;160&quot; alt=&quot;&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-&lt;p&gt;AMA Motocross Championship 2005, Budds Creek, Maryland&lt;/p&gt;</content>
- <author>
- <name>Lan Rover</name>
- <url>http://www.flickr.com/people/buddscreek/</url>
- </author>
- <dc:subject>amamotocrosschampionship buddscreek maryland 2005 fathersday motocrossnational rickycarmichael 259 jamesstewart 4</dc:subject>
- </entry>
- <entry>
- <title></title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/buddscreek/21375650/"/>
- <id>tag:flickr.com,2004:/photo/21375650</id>
- <issued>2005-06-25T01:56:24Z</issued>
- <modified>2005-06-25T01:56:24Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/buddscreek/&quot;&gt;Lan Rover&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/buddscreek/21375650/&quot; title=&quot;&quot;&gt;&lt;img src=&quot;http://photos16.flickr.com/21375650_5c60e0dab1_m.jpg&quot; width=&quot;240&quot; height=&quot;160&quot; alt=&quot;&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>Lan Rover</name>
- <url>http://www.flickr.com/people/buddscreek/</url>
- </author>
- <dc:subject>amamotocrosschampionship buddscreek maryland 2005 fathersday motocrossnational 259 jamesstewart</dc:subject>
- </entry>
- <entry>
- <title></title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/buddscreek/21375345/"/>
- <id>tag:flickr.com,2004:/photo/21375345</id>
- <issued>2005-06-25T01:54:11Z</issued>
- <modified>2005-06-25T01:54:11Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/buddscreek/&quot;&gt;Lan Rover&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/buddscreek/21375345/&quot; title=&quot;&quot;&gt;&lt;img src=&quot;http://photos15.flickr.com/21375345_4205fdd22b_m.jpg&quot; width=&quot;160&quot; height=&quot;240&quot; alt=&quot;&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>Lan Rover</name>
- <url>http://www.flickr.com/people/buddscreek/</url>
- </author>
- <dc:subject>amamotocrosschampionship buddscreek maryland 2005 fathersday motocrossnational 259 jamesstewart</dc:subject>
- </entry>
- <entry>
- <title>Lunch with Kari &amp; James, café in the crypt of St Martin in the fields</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/fidothe/16516618/"/>
- <id>tag:flickr.com,2004:/photo/16516618</id>
- <issued>2005-05-30T21:56:39Z</issued>
- <modified>2005-05-30T21:56:39Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/fidothe/&quot;&gt;fidothe&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/fidothe/16516618/&quot; title=&quot;Lunch with Kari &amp;amp; James, café in the crypt of St Martin in the fields&quot;&gt;&lt;img src=&quot;http://photos14.flickr.com/16516618_afaa4a395e_m.jpg&quot; width=&quot;240&quot; height=&quot;180&quot; alt=&quot;Lunch with Kari &amp;amp; James, café in the crypt of St Martin in the fields&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>fidothe</name>
- <url>http://www.flickr.com/people/fidothe/</url>
- </author>
- <dc:subject>nokia7610 london stmartininthefields clarepatterson jamesstewart parvinstewart jimstewart susanstewart</dc:subject>
- </entry>
- <entry>
- <title>Stewart keeping it low over the obstacle.</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/pqbon/10224728/"/>
- <id>tag:flickr.com,2004:/photo/10224728</id>
- <issued>2005-04-21T07:30:29Z</issued>
- <modified>2005-04-21T07:30:29Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/pqbon/&quot;&gt;pqbon&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/pqbon/10224728/&quot; title=&quot;Stewart keeping it low over the obstacle.&quot;&gt;&lt;img src=&quot;http://photos7.flickr.com/10224728_b756341957_m.jpg&quot; width=&quot;240&quot; height=&quot;180&quot; alt=&quot;Stewart keeping it low over the obstacle.&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>pqbon</name>
- <url>http://www.flickr.com/people/pqbon/</url>
- </author>
- <dc:subject>ama hangtown motocross jamesstewart bubba</dc:subject>
- </entry>
- <entry>
- <title>king james stewart</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/jjlook/7152910/"/>
- <id>tag:flickr.com,2004:/photo/7152910</id>
- <issued>2005-03-22T21:53:37Z</issued>
- <modified>2005-03-22T21:53:37Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/jjlook/&quot;&gt;jj look&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/jjlook/7152910/&quot; title=&quot;king james stewart&quot;&gt;&lt;img src=&quot;http://photos7.flickr.com/7152910_a02ab5a750_m.jpg&quot; width=&quot;180&quot; height=&quot;240&quot; alt=&quot;king james stewart&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-&lt;p&gt;11th&lt;/p&gt;</content>
- <author>
- <name>jj look</name>
- <url>http://www.flickr.com/people/jjlook/</url>
- </author>
- <dc:subject>dilomar05 eastside austin texas 78702 kingjames stewart jamesstewart borrowed</dc:subject>
- </entry>
- <entry>
- <title>It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/fidothe/1586562/"/>
- <id>tag:flickr.com,2004:/photo/1586562</id>
- <issued>2004-11-20T09:34:28Z</issued>
- <modified>2004-11-20T09:34:28Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/fidothe/&quot;&gt;fidothe&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/fidothe/1586562/&quot; title=&quot;It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)&quot;&gt;&lt;img src=&quot;http://photos2.flickr.com/1586562_0bc5313a3e_m.jpg&quot; width=&quot;240&quot; height=&quot;180&quot; alt=&quot;It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>fidothe</name>
- <url>http://www.flickr.com/people/fidothe/</url>
- </author>
- <dc:subject>holiday grandrapids jamesstewart</dc:subject>
- </entry>
- <entry>
- <title>It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/fidothe/1586539/"/>
- <id>tag:flickr.com,2004:/photo/1586539</id>
- <issued>2004-11-20T09:28:16Z</issued>
- <modified>2004-11-20T09:28:16Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/fidothe/&quot;&gt;fidothe&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/fidothe/1586539/&quot; title=&quot;It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)&quot;&gt;&lt;img src=&quot;http://photos2.flickr.com/1586539_c51e5f2e7a_m.jpg&quot; width=&quot;240&quot; height=&quot;180&quot; alt=&quot;It's a Grind, downtown Grand Rapids (James, Susan, Jim, Harv, Lawson)&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>fidothe</name>
- <url>http://www.flickr.com/people/fidothe/</url>
- </author>
- <dc:subject>holiday grandrapids jamesstewart</dc:subject>
- </entry>
- <entry>
- <title>It's a Grind, James and Jim can't decide)</title>
- <link rel="alternate" type="text/html" href="http://www.flickr.com/photos/fidothe/1586514/"/>
- <id>tag:flickr.com,2004:/photo/1586514</id>
- <issued>2004-11-20T09:25:05Z</issued>
- <modified>2004-11-20T09:25:05Z</modified>
- <content type="text/html" mode="escaped">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/people/fidothe/&quot;&gt;fidothe&lt;/a&gt; posted a photo:&lt;/p&gt;
-
-&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/fidothe/1586514/&quot; title=&quot;It's a Grind, James and Jim can't decide)&quot;&gt;&lt;img src=&quot;http://photos2.flickr.com/1586514_733c2dfa3e_m.jpg&quot; width=&quot;240&quot; height=&quot;180&quot; alt=&quot;It's a Grind, James and Jim can't decide)&quot; style=&quot;border: 1px solid #000000;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
-
-</content>
- <author>
- <name>fidothe</name>
- <url>http://www.flickr.com/people/fidothe/</url>
- </author>
- <dc:subject>holiday grandrapids jamesstewart johnkentish</dc:subject>
- </entry>
-
-</feed> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/grwifi-atom.xml b/plugins/OStatus/extlib/XML/Feed/samples/grwifi-atom.xml
deleted file mode 100755
index c351d3c16..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/grwifi-atom.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"> <title>Updates to Grand Rapids WiFi hotspot details</title> <link rel="alternate" type="text/html" href="http://grwifi.net/"/> <link rel="self" type="application/atom+xml" href="http://grwifi.net/atom/locations"/> <updated>2005-09-01T15:43:01-05:00</updated> <subtitle>WiFi Hotspots in Grand Rapids, MI</subtitle> <id>http://grwifi.net/atom/locations</id> <rights>Creative Commons Attribution-NonCommercial-ShareAlike 2.0 http://creativecommons.org/licenses/by-nc-sa/2.0/ </rights> <entry> <title>Hotspot Details Updated: Sweetwaters</title> <link rel="alternate" type="text/html" href="http://grwifi.net/location/sweetwaters"/> <id>http://grwifi.net/location/sweetwaters</id> <updated>2005-09-01T15:43:01-05:00</updated> <summary type="html"> The details of the WiFi hotspot at: Sweetwaters have been updated. Find out more at:
-http://grwifi.net/location/sweetwaters </summary> <author> <name>James</name> <uri>http://jystewart.net</uri> <email>james@jystewart.net</email> </author> <dc:subject>wifi hotspot</dc:subject> </entry> <entry> <title>Hotspot Details Updated: Common Ground Coffee Shop</title> <link rel="alternate" type="text/html" href="http://grwifi.net/location/common-ground"/> <id>http://grwifi.net/location/common-ground</id> <updated>2005-09-01T15:42:39-05:00</updated> <summary type="html"> The details of the WiFi hotspot at: Common Ground Coffee Shop have been updated. Find out more at:
-http://grwifi.net/location/common-ground </summary> <author> <name>James</name> <uri>http://jystewart.net</uri> <email>james@jystewart.net</email> </author> <dc:subject>wifi hotspot</dc:subject> </entry> <entry> <title>Hotspot Details Updated: Grand Rapids Public Library, Main Branch</title> <link rel="alternate" type="text/html" href="http://grwifi.net/location/grpl-main-branch"/> <id>http://grwifi.net/location/grpl-main-branch</id> <updated>2005-09-01T15:42:20-05:00</updated> <summary type="html"> The details of the WiFi hotspot at: Grand Rapids Public Library, Main Branch have been updated. Find out more at:
-http://grwifi.net/location/grpl-main-branch </summary> <author> <name>James</name> <uri>http://jystewart.net</uri> <email>james@jystewart.net</email> </author> <dc:subject>wifi hotspot</dc:subject> </entry> <entry> <title>Hotspot Details Updated: Four Friends Coffee House</title> <link rel="alternate" type="text/html" href="http://grwifi.net/location/four-friends"/> <id>http://grwifi.net/location/four-friends</id> <updated>2005-09-01T15:41:35-05:00</updated> <summary type="html"> The details of the WiFi hotspot at: Four Friends Coffee House have been updated. Find out more at:
-http://grwifi.net/location/four-friends </summary> <author> <name>James</name> <uri>http://jystewart.net</uri> <email>james@jystewart.net</email> </author> <dc:subject>wifi hotspot</dc:subject> </entry> <entry> <title>Hotspot Details Updated: Barnes and Noble, Rivertown Crossings</title> <link rel="alternate" type="text/html" href="http://grwifi.net/location/barnes-noble-rivertown"/> <id>http://grwifi.net/location/barnes-noble-rivertown</id> <updated>2005-09-01T15:40:41-05:00</updated> <summary type="html"> The details of the WiFi hotspot at: Barnes and Noble, Rivertown Crossings have been updated. Find out more at:
-http://grwifi.net/location/barnes-noble-rivertown </summary> <author> <name>James</name> <uri>http://jystewart.net</uri> <email>james@jystewart.net</email> </author> <dc:subject>wifi hotspot</dc:subject> </entry> <entry> <title>Hotspot Details Updated: The Boss Sports Bar &amp; Grille</title> <link rel="alternate" type="text/html" href="http://grwifi.net/location/boss-sports-bar"/> <id>http://grwifi.net/location/boss-sports-bar</id> <updated>2005-09-01T15:40:19-05:00</updated> <summary type="html"> The details of the WiFi hotspot at: The Boss Sports Bar &amp; Grille have been updated. Find out more at:
-http://grwifi.net/location/boss-sports-bar </summary> <author> <name>James</name> <uri>http://jystewart.net</uri> <email>james@jystewart.net</email> </author> <dc:subject>wifi hotspot</dc:subject> </entry> </feed> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/hoder.xml b/plugins/OStatus/extlib/XML/Feed/samples/hoder.xml
deleted file mode 100755
index 099463570..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/hoder.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<rss version="2.0"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
- xmlns:admin="http://webns.net/mvcb/"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-
-<channel>
-<title>Editor: Myself (Persian)</title>
-<link>http://editormyself.info</link>
-<description>This is a Persian (Farsi) weblog, written by Hossein Derakhshan (aka, Hoder), an Iranian Multimedia designer and a journalist who lives in Toronto since Dec 2000. He also keeps an English weblog with the same name.</description>
-<dc:language>en-us</dc:language>
-<dc:creator>hoder@hotmail.com</dc:creator>
-<dc:date>2005-10-12T19:45:32-05:00</dc:date>
-<admin:generatorAgent rdf:resource="http://www.movabletype.org/?v=3.15" />
-<sy:updatePeriod>hourly</sy:updatePeriod>
-<sy:updateFrequency>1</sy:updateFrequency>
-<sy:updateBase>2000-01-01T12:00+00:00</sy:updateBase>
-
-
-<item>
-<title>لينکدونی‌ | جلسه‌ی امریکن انترپرایز برای تقسیم قومی ایران</title>
-<link>http://www.aei.org/events/type.upcoming,eventID.1166,filter.all/event_detail.asp</link>
-<description>چطور بعضی‌ها فکر می‌کنند دست راستی‌های آمریکا از خامنه‌ای ملی‌گراترند</description>
-<guid isPermaLink="false">14645@http://i.hoder.com/</guid>
-<dc:subject>iran</dc:subject>
-<dc:date>2005-10-12T19:45:32-05:00</dc:date>
-</item>
-
-<item>
-<title>لينکدونی‌ | به صبحانه آگهی بدهید</title>
-<link>http://www.adbrite.com/mb/commerce/purchase_form.php?opid=24346&amp;afsid=1</link>
-<description>خیلی ارزان و راحت است</description>
-<guid isPermaLink="false">14644@http://i.hoder.com/</guid>
-<dc:subject>media/journalism</dc:subject>
-<dc:date>2005-10-12T17:23:15-05:00</dc:date>
-</item>
-
-<item>
-<title>لينکدونی‌ | نیروی انتظامی چگونه تابوهای هم‌جنس‌گرایانه را می‌شکند؛ فرنگوپولیس</title>
-<link>http://farangeopolis.blogspot.com/2005/10/blog-post_08.html</link>
-<description>از پس و پیش و حاشیه‌ی این ماجرا می‌توان یک مستند بی‌نظیر ساخت</description>
-<guid isPermaLink="false">14643@http://i.hoder.com/</guid>
-<dc:subject>soc_popculture</dc:subject>
-<dc:date>2005-10-12T17:06:40-05:00</dc:date>
-</item>
-
-<item>
-<title>لينکدونی‌ | بازتاب توقیف شد</title>
-<link>http://www.baztab.com/news/30201.php</link>
-<description>اگر گفتید یک وب‌سایت را چطور توقیف می‌کنند؟ لابد ماوس‌شان را قایم می‌کنند.</description>
-<guid isPermaLink="false">14642@http://i.hoder.com/</guid>
-<dc:subject>media/journalism</dc:subject>
-<dc:date>2005-10-12T14:41:57-05:00</dc:date>
-</item>
-
-<item>
-<title>لينکدونی‌ | رشد وب در سال 2005 از همیشه بیشتر بوده است&quot; بی.بی.سی</title>
-<link>http://news.bbc.co.uk/2/hi/technology/4325918.stm</link>
-<description></description>
-<guid isPermaLink="false">14640@http://i.hoder.com/</guid>
-<dc:subject>tech</dc:subject>
-<dc:date>2005-10-12T13:04:46-05:00</dc:date>
-</item>
-
-
-
-<item>
-<title>==قرعه کشی گرین کارد به زودی شروع می‌شود==</title>
-<link>http://nice.newsxphotos.biz/05/09/2007_dv_lottery_registration_to_begin_oct_5_14589.php</link>
-<description></description>
-<guid isPermaLink="false">14613@http://vagrantly.com</guid>
-<dc:subject>ads03</dc:subject>
-<dc:date>2005-09-27T04:49:22-05:00</dc:date>
-</item>
-
-
-
-
-
-
-<item>
-<title>پروژه‌ی هاروارد، قدم دوم</title>
-<link>http://editormyself.info/archives/2005/10/051012_014641.shtml</link>
-<description><![CDATA[<p>اگر یادتان باشد <a href="/archives/2005/09/050906_014504.shtml">چند وقت پیش نوشتم</a> که دانشگاه هاروارد پروژه‌ای دارد با نام آواهای جهانی که در آن به وبلاگ‌های غیر انگلیسی‌زبان می‌پردازد. خواشتم که اگر کسی علاقه دارد ایمیل بزند. تعداد زیادی جواب دادند و ابراز علاقه کردند. حالا وقت قدم دوم است.</p>
-
-<p>قدم دوم این است که برای اینکه مسوولین پروژه بتوانند تصمیم بگیرند که با چه کسی کار کنند، می‌خواهند نمونه‌ی کارهای علاقمندان مشارکت در این پرزو‌ه را ببینند.</p>
-
-<p>برای همین از همه‌ی علاقماندان، حتی کسانی که قبلا اعلام آمادگی نکرده بودند، می‌‌خواهم که یک موضوع رایج این روزهای وبلاگستان فارسی را انتخاب کنند و در هفتصد کلمه، به انگلیسی، بنویسند که وبلاگ‌دارهای درباره‌اش چه می‌گویند. لینک به پنج، شش وبلاگ و بازنویسی آنچه آنها از جنبه‌های گوناگون درباره‌ی آن موضوع نوشته‌اند با نقل قول مستقیم از آنها (البته ترجمه شده از فارسی) کافی است. دو سه جمله هم اول کار توضیح دهید که چرا این موضوع مهم است.</p>
-
-<p>متن نمونه را به آدرس ایمیل من hoder@hoder.com و نیز برای افراد زیر تا روز دوشنبه بفرستید:<br />
-ربکا : rmackinnon@cyber.law.harvard.edu<br />
-هیثم: haitham.sabbah@gmail.com</p>]]></description>
-<guid isPermaLink="false">14641@http://editormyself.info</guid>
-<dc:subject>weblog</dc:subject>
-<dc:date>2005-10-12T14:04:23-05:00</dc:date>
-</item>
-
-
-
-</channel>
-</rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/illformed_atom10.xml b/plugins/OStatus/extlib/XML/Feed/samples/illformed_atom10.xml
deleted file mode 100755
index 612186897..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/illformed_atom10.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<!--
-Description: entry author name
-Expect: bozo and entries[0]['author_detail']['name'] == u'Example author'
--->
-<feed xmlns="http://www.w3.org/2005/Atom">
-<entry>
- <author>
- <name>Example author</name>
- <email>me@example.com</email>
- <uri>http://example.com/</uri>
- </author>
-</entry>
-</feed \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss091-complete.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss091-complete.xml
deleted file mode 100755
index b0a1fee2d..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss091-complete.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE rss SYSTEM "http://my.netscape.com/publish/formats/rss-0.91.dtd">
-<rss version="0.91">
-<channel>
-<copyright>Copyright 1997-1999 UserLand Software, Inc.</copyright>
-<pubDate>Thu, 08 Jul 1999 07:00:00 GMT</pubDate>
-<lastBuildDate>Thu, 08 Jul 1999 16:20:26 GMT</lastBuildDate>
-<docs>http://my.userland.com/stories/storyReader$11</docs>
-<description>News and commentary from the cross-platform scripting community.</description>
-<link>http://www.scripting.com/</link>
-<title>Scripting News</title>
-<image>
-<link>http://www.scripting.com/</link>
-<title>Scripting News</title>
-<url>http://www.scripting.com/gifs/tinyScriptingNews.gif</url>
-<height>40</height>
-<width>78</width>
-<description>What is this used for?</description>
-</image>
-<managingEditor>dave@userland.com (Dave Winer)</managingEditor>
-<webMaster>dave@userland.com (Dave Winer)</webMaster>
-<language>en-us</language>
-<skipHours>
-<hour>6</hour>
-<hour>7</hour>
-<hour>8</hour>
-<hour>9</hour>
-<hour>10</hour>
-<hour>11</hour>
-</skipHours>
-<skipDays>
-<day>Sunday</day>
-</skipDays>
-<rating>(PICS-1.1 "http://www.rsac.org/ratingsv01.html" l gen true comment "RSACi North America Server" for "http://www.rsac.org" on "1996.04.16T08:15-0500" r (n 0 s 0 v 0 l 0))</rating>
-<item>
-<title>stuff</title>
-<link>http://bar</link>
-<description>This is an article about some stuff</description>
-</item>
-<textinput>
-<title>Search Now!</title>
-<description>Enter your search &lt;terms&gt;</description>
-<name>find</name>
-<link>http://my.site.com/search.cgi</link>
-</textinput>
-</channel>
-</rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss091-international.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss091-international.xml
deleted file mode 100755
index cfe91691f..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss091-international.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="EuC-JP"?>
-<!DOCTYPE rss SYSTEM "http://my.netscape.com/publish/formats/rss-0.91.dtd">
-<rss version="0.91">
-<channel>
-<title>膮ŸÛë´é´Ì´×´è´ŒÁ¹´Õ</title>
-<link>http://www.mozilla.org</link>
-<description>膮ŸÛë´é´Ì´×´è´ŒÁ¹´Õ</description>
-<language>ja</language> <!-- tagged as Japanese content -->
-<item>
-<title>NYÒ™Á¢¸»ÌêÛì15285.25´ƒ´‘Á£´Û´—´ÀÁ¹´ê´Ì´éÒ™Ûì¡êçÒÕ‰ÌêÁ£</title>
-<link>http://www.mozilla.org/status/</link>
-<description>This is an item description...</description>
-</item>
-<item>
-<title>‚§±Çç¡ËßÛÂҏéøÓ¸Á£Ë²®Ÿè†Ûè危ÇÌ’¡Íæ—éøë‡Á£</title>
-<link>http://www.mozilla.org/status/</link>
-<description>This is an item description...</description>
-</item>
-<item>
-<title>ËÜË”ïÌëÈšÁ¢È†Ë§æàÀ豎ˉۂÁ¢Ë‚åܼšÛ˜íËüËÁ£</title>
-<link>http://www.mozilla.org/status/</link>
-<description>This is an item description...</description>
-</item>
-<item>
-<title>2000‚øíŠåÁ¢«‘¦éÛ빏ېçéÛ§ÛÂè†ÒæÓ¸Á£Ì¾«…æ—ÕÝéøƒ¸Á£</title>
-<link>http://www.mozilla.org/status/</link>
-<description>This is an item description...</description>
-</item>
-</channel>
-</rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss091-simple.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss091-simple.xml
deleted file mode 100755
index f0964a227..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss091-simple.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE rss SYSTEM "http://my.netscape.com/publish/formats/rss-0.91.dtd">
-<rss version="0.91">
-<channel>
-<language>en</language>
-<description>News and commentary from the cross-platform scripting community.</description>
-<link>http://www.scripting.com/</link>
-<title>Scripting News</title>
-<image>
-<link>http://www.scripting.com/</link>
-<title>Scripting News</title>
-<url>http://www.scripting.com/gifs/tinyScriptingNews.gif</url>
-</image>
-</channel>
-</rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss092-sample.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss092-sample.xml
deleted file mode 100755
index 5d75c352b..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss092-sample.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0"?>
-<!-- RSS generation done by 'Radio UserLand' on Fri, 13 Apr 2001 19:23:02 GMT -->
-<rss version="0.92">
- <channel>
- <title>Dave Winer: Grateful Dead</title>
- <link>http://www.scripting.com/blog/categories/gratefulDead.html</link>
- <description>A high-fidelity Grateful Dead song every day. This is where we&apos;re experimenting with enclosures on RSS news items that download when you&apos;re not using your computer. If it works (it will) it will be the end of the Click-And-Wait multimedia experience on the Internet. </description>
- <lastBuildDate>Fri, 13 Apr 2001 19:23:02 GMT</lastBuildDate>
- <docs>http://backend.userland.com/rss092</docs>
- <managingEditor>dave@userland.com (Dave Winer)</managingEditor>
- <webMaster>dave@userland.com (Dave Winer)</webMaster>
- <cloud domain="data.ourfavoritesongs.com" port="80" path="/RPC2" registerProcedure="ourFavoriteSongs.rssPleaseNotify" protocol="xml-rpc"/>
- <item>
- <description>It&apos;s been a few days since I added a song to the Grateful Dead channel. Now that there are all these new Radio users, many of whom are tuned into this channel (it&apos;s #16 on the hotlist of upstreaming Radio users, there&apos;s no way of knowing how many non-upstreaming users are subscribing, have to do something about this..). Anyway, tonight&apos;s song is a live version of Weather Report Suite from Dick&apos;s Picks Volume 7. It&apos;s wistful music. Of course a beautiful song, oft-quoted here on Scripting News. &lt;i&gt;A little change, the wind and rain.&lt;/i&gt;
-</description>
- <enclosure url="http://www.scripting.com/mp3s/weatherReportDicksPicsVol7.mp3" length="6182912" type="audio/mpeg"/>
- </item>
- <item>
- <description>Kevin Drennan started a &lt;a href=&quot;http://deadend.editthispage.com/&quot;&gt;Grateful Dead Weblog&lt;/a&gt;. Hey it&apos;s cool, he even has a &lt;a href=&quot;http://deadend.editthispage.com/directory/61&quot;&gt;directory&lt;/a&gt;. &lt;i&gt;A Frontier 7 feature.&lt;/i&gt;</description>
- <source url="http://scriptingnews.userland.com/xml/scriptingNews2.xml">Scripting News</source>
- </item>
- <item>
- <description>&lt;a href=&quot;http://arts.ucsc.edu/GDead/AGDL/other1.html&quot;&gt;The Other One&lt;/a&gt;, live instrumental, One From The Vault. Very rhythmic very spacy, you can listen to it many times, and enjoy something new every time.</description>
- <enclosure url="http://www.scripting.com/mp3s/theOtherOne.mp3" length="6666097" type="audio/mpeg"/>
- </item>
- <item>
- <description>This is a test of a change I just made. Still diggin..</description>
- </item>
- <item>
- <description>The HTML rendering almost &lt;a href=&quot;http://validator.w3.org/check/referer&quot;&gt;validates&lt;/a&gt;. Close. Hey I wonder if anyone has ever published a style guide for ALT attributes on images? What are you supposed to say in the ALT attribute? I sure don&apos;t know. If you&apos;re blind send me an email if u cn rd ths. </description>
- </item>
- <item>
- <description>&lt;a href=&quot;http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/Franklin&apos;s_Tower.txt&quot;&gt;Franklin&apos;s Tower&lt;/a&gt;, a live version from One From The Vault.</description>
- <enclosure url="http://www.scripting.com/mp3s/franklinsTower.mp3" length="6701402" type="audio/mpeg"/>
- </item>
- <item>
- <description>Moshe Weitzman says Shakedown Street is what I&apos;m lookin for for tonight. I&apos;m listening right now. It&apos;s one of my favorites. &quot;Don&apos;t tell me this town ain&apos;t got no heart.&quot; Too bright. I like the jazziness of Weather Report Suite. Dreamy and soft. How about The Other One? &quot;Spanish lady come to me..&quot;</description>
- <source url="http://scriptingnews.userland.com/xml/scriptingNews2.xml">Scripting News</source>
- </item>
- <item>
- <description>&lt;a href=&quot;http://www.scripting.com/mp3s/youWinAgain.mp3&quot;&gt;The news is out&lt;/a&gt;, all over town..&lt;p&gt;
-You&apos;ve been seen, out runnin round. &lt;p&gt;
-The lyrics are &lt;a href=&quot;http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/You_Win_Again.txt&quot;&gt;here&lt;/a&gt;, short and sweet. &lt;p&gt;
-&lt;i&gt;You win again!&lt;/i&gt;
-</description>
- <enclosure url="http://www.scripting.com/mp3s/youWinAgain.mp3" length="3874816" type="audio/mpeg"/>
- </item>
- <item>
- <description>&lt;a href=&quot;http://www.getlyrics.com/lyrics/grateful-dead/wake-of-the-flood/07.htm&quot;&gt;Weather Report Suite&lt;/a&gt;: &quot;Winter rain, now tell me why, summers fade, and roses die? The answer came. The wind and rain. Golden hills, now veiled in grey, summer leaves have blown away. Now what remains? The wind and rain.&quot;</description>
- <enclosure url="http://www.scripting.com/mp3s/weatherReportSuite.mp3" length="12216320" type="audio/mpeg"/>
- </item>
- <item>
- <description>&lt;a href=&quot;http://arts.ucsc.edu/gdead/agdl/darkstar.html&quot;&gt;Dark Star&lt;/a&gt; crashes, pouring its light into ashes.</description>
- <enclosure url="http://www.scripting.com/mp3s/darkStar.mp3" length="10889216" type="audio/mpeg"/>
- </item>
- <item>
- <description>DaveNet: &lt;a href=&quot;http://davenet.userland.com/2001/01/21/theUsBlues&quot;&gt;The U.S. Blues&lt;/a&gt;.</description>
- </item>
- <item>
- <description>Still listening to the US Blues. &lt;i&gt;&quot;Wave that flag, wave it wide and high..&quot;&lt;/i&gt; Mistake made in the 60s. We gave our country to the assholes. Ah ah. Let&apos;s take it back. Hey I&apos;m still a hippie. &lt;i&gt;&quot;You could call this song The United States Blues.&quot;&lt;/i&gt;</description>
- </item>
- <item>
- <description>&lt;a href=&quot;http://www.sixties.com/html/garcia_stack_0.html&quot;&gt;&lt;img src=&quot;http://www.scripting.com/images/captainTripsSmall.gif&quot; height=&quot;51&quot; width=&quot;42&quot; border=&quot;0&quot; hspace=&quot;10&quot; vspace=&quot;10&quot; align=&quot;right&quot;&gt;&lt;/a&gt;In celebration of today&apos;s inauguration, after hearing all those great patriotic songs, America the Beautiful, even The Star Spangled Banner made my eyes mist up. It made my choice of Grateful Dead song of the night realllly easy. Here are the &lt;a href=&quot;http://searchlyrics2.homestead.com/gd_usblues.html&quot;&gt;lyrics&lt;/a&gt;. Click on the audio icon to the left to give it a listen. &quot;Red and white, blue suede shoes, I&apos;m Uncle Sam, how do you do?&quot; It&apos;s a different kind of patriotic music, but man I love my country and I love Jerry and the band. &lt;i&gt;I truly do!&lt;/i&gt;</description>
- <enclosure url="http://www.scripting.com/mp3s/usBlues.mp3" length="5272510" type="audio/mpeg"/>
- </item>
- <item>
- <description>Grateful Dead: &quot;Tennessee, Tennessee, ain&apos;t no place I&apos;d rather be.&quot;</description>
- <enclosure url="http://www.scripting.com/mp3s/tennesseeJed.mp3" length="3442648" type="audio/mpeg"/>
- </item>
- <item>
- <description>Ed Cone: &quot;Had a nice Deadhead experience with my wife, who never was one but gets the vibe and knows and likes a lot of the music. Somehow she made it to the age of 40 without ever hearing Wharf Rat. We drove to Jersey and back over Christmas with the live album commonly known as Skull and Roses in the CD player much of the way, and it was cool to see her discover one the band&apos;s finest moments. That song is unique and underappreciated. Fun to hear that disc again after a few years off -- you get Jerry as blues-guitar hero on Big Railroad Blues and a nice version of Bertha.&quot;</description>
- <enclosure url="http://www.scripting.com/mp3s/darkStarWharfRat.mp3" length="27503386" type="audio/mpeg"/>
- </item>
- <item>
- <description>&lt;a href=&quot;http://arts.ucsc.edu/GDead/AGDL/fotd.html&quot;&gt;Tonight&apos;s Song&lt;/a&gt;: &quot;If I get home before daylight I just might get some sleep tonight.&quot; </description>
- <enclosure url="http://www.scripting.com/mp3s/friendOfTheDevil.mp3" length="3219742" type="audio/mpeg"/>
- </item>
- <item>
- <description>&lt;a href=&quot;http://arts.ucsc.edu/GDead/AGDL/uncle.html&quot;&gt;Tonight&apos;s song&lt;/a&gt;: &quot;Come hear Uncle John&apos;s Band by the river side. Got some things to talk about here beside the rising tide.&quot;</description>
- <enclosure url="http://www.scripting.com/mp3s/uncleJohnsBand.mp3" length="4587102" type="audio/mpeg"/>
- </item>
- <item>
- <description>&lt;a href=&quot;http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/Me_and_My_Uncle.txt&quot;&gt;Me and My Uncle&lt;/a&gt;: &quot;I loved my uncle, God rest his soul, taught me good, Lord, taught me all I know. Taught me so well, I grabbed that gold and I left his dead ass there by the side of the road.&quot;
-</description>
- <enclosure url="http://www.scripting.com/mp3s/meAndMyUncle.mp3" length="2949248" type="audio/mpeg"/>
- </item>
- <item>
- <description>Truckin, like the doo-dah man, once told me gotta play your hand. Sometimes the cards ain&apos;t worth a dime, if you don&apos;t lay em down.</description>
- <enclosure url="http://www.scripting.com/mp3s/truckin.mp3" length="4847908" type="audio/mpeg"/>
- </item>
- <item>
- <description>Two-Way-Web: &lt;a href=&quot;http://www.thetwowayweb.com/payloadsForRss&quot;&gt;Payloads for RSS&lt;/a&gt;. &quot;When I started talking with Adam late last year, he wanted me to think about high quality video on the Internet, and I totally didn&apos;t want to hear about it.&quot;</description>
- </item>
- <item>
- <description>A touch of gray, kinda suits you anyway..</description>
- <enclosure url="http://www.scripting.com/mp3s/touchOfGrey.mp3" length="5588242" type="audio/mpeg"/>
- </item>
- <item>
- <description>&lt;a href=&quot;http://www.sixties.com/html/garcia_stack_0.html&quot;&gt;&lt;img src=&quot;http://www.scripting.com/images/captainTripsSmall.gif&quot; height=&quot;51&quot; width=&quot;42&quot; border=&quot;0&quot; hspace=&quot;10&quot; vspace=&quot;10&quot; align=&quot;right&quot;&gt;&lt;/a&gt;In celebration of today&apos;s inauguration, after hearing all those great patriotic songs, America the Beautiful, even The Star Spangled Banner made my eyes mist up. It made my choice of Grateful Dead song of the night realllly easy. Here are the &lt;a href=&quot;http://searchlyrics2.homestead.com/gd_usblues.html&quot;&gt;lyrics&lt;/a&gt;. Click on the audio icon to the left to give it a listen. &quot;Red and white, blue suede shoes, I&apos;m Uncle Sam, how do you do?&quot; It&apos;s a different kind of patriotic music, but man I love my country and I love Jerry and the band. &lt;i&gt;I truly do!&lt;/i&gt;</description>
- <enclosure url="http://www.scripting.com/mp3s/usBlues.mp3" length="5272510" type="audio/mpeg"/>
- </item>
- </channel>
- </rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss10-example1.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss10-example1.xml
deleted file mode 100755
index 0edecf58e..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss10-example1.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0"?>
-
-<rdf:RDF
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns="http://purl.org/rss/1.0/"
->
-
- <channel rdf:about="http://www.xml.com/xml/news.rss">
- <title>XML.com</title>
- <link>http://xml.com/pub</link>
- <description>
- XML.com features a rich mix of information and services
- for the XML community.
- </description>
-
- <image rdf:resource="http://xml.com/universal/images/xml_tiny.gif" />
-
- <items>
- <rdf:Seq>
- <rdf:li resource="http://xml.com/pub/2000/08/09/xslt/xslt.html" />
- <rdf:li resource="http://xml.com/pub/2000/08/09/rdfdb/index.html" />
- </rdf:Seq>
- </items>
-
- <textinput rdf:resource="http://search.xml.com" />
-
- </channel>
-
- <image rdf:about="http://xml.com/universal/images/xml_tiny.gif">
- <title>XML.com</title>
- <link>http://www.xml.com</link>
- <url>http://xml.com/universal/images/xml_tiny.gif</url>
- </image>
-
- <item rdf:about="http://xml.com/pub/2000/08/09/xslt/xslt.html">
- <title>Processing Inclusions with XSLT</title>
- <link>http://xml.com/pub/2000/08/09/xslt/xslt.html</link>
- <description>
- Processing document inclusions with general XML tools can be
- problematic. This article proposes a way of preserving inclusion
- information through SAX-based processing.
- </description>
- </item>
-
- <item rdf:about="http://xml.com/pub/2000/08/09/rdfdb/index.html">
- <title>Putting RDF to Work</title>
- <link>http://xml.com/pub/2000/08/09/rdfdb/index.html</link>
- <description>
- Tool and API support for the Resource Description Framework
- is slowly coming of age. Edd Dumbill takes a look at RDFDB,
- one of the most exciting new RDF toolkits.
- </description>
- </item>
-
- <textinput rdf:about="http://search.xml.com">
- <title>Search XML.com</title>
- <description>Search XML.com's XML collection</description>
- <name>s</name>
- <link>http://search.xml.com</link>
- </textinput>
-
-</rdf:RDF> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss10-example2.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss10-example2.xml
deleted file mode 100755
index 26235f78f..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss10-example2.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<rdf:RDF
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
- xmlns:co="http://purl.org/rss/1.0/modules/company/"
- xmlns:ti="http://purl.org/rss/1.0/modules/textinput/"
- xmlns="http://purl.org/rss/1.0/"
->
-
- <channel rdf:about="http://meerkat.oreillynet.com/?_fl=rss1.0">
- <title>Meerkat</title>
- <link>http://meerkat.oreillynet.com</link>
- <description>Meerkat: An Open Wire Service</description>
- <dc:publisher>The O'Reilly Network</dc:publisher>
- <dc:creator>Rael Dornfest (mailto:rael@oreilly.com)</dc:creator>
- <dc:rights>Copyright &#169; 2000 O'Reilly &amp; Associates, Inc.</dc:rights>
- <dc:date>2000-01-01T12:00+00:00</dc:date>
- <sy:updatePeriod>hourly</sy:updatePeriod>
- <sy:updateFrequency>2</sy:updateFrequency>
- <sy:updateBase>2000-01-01T12:00+00:00</sy:updateBase>
-
- <image rdf:resource="http://meerkat.oreillynet.com/icons/meerkat-powered.jpg" />
-
- <items>
- <rdf:Seq>
- <rdf:li resource="http://c.moreover.com/click/here.pl?r123" />
- </rdf:Seq>
- </items>
-
- <textinput rdf:resource="http://meerkat.oreillynet.com" />
-
- </channel>
-
- <image rdf:about="http://meerkat.oreillynet.com/icons/meerkat-powered.jpg">
- <title>Meerkat Powered!</title>
- <url>http://meerkat.oreillynet.com/icons/meerkat-powered.jpg</url>
- <link>http://meerkat.oreillynet.com</link>
- </image>
-
- <item rdf:about="http://c.moreover.com/click/here.pl?r123">
- <title>XML: A Disruptive Technology</title>
- <link>http://c.moreover.com/click/here.pl?r123</link>
- <dc:description>
- XML is placing increasingly heavy loads on the existing technical
- infrastructure of the Internet.
- </dc:description>
- <dc:publisher>The O'Reilly Network</dc:publisher>
- <dc:creator>Simon St.Laurent (mailto:simonstl@simonstl.com)</dc:creator>
- <dc:rights>Copyright &#169; 2000 O'Reilly &amp; Associates, Inc.</dc:rights>
- <dc:subject>XML</dc:subject>
- <co:name>XML.com</co:name>
- <co:market>NASDAQ</co:market>
- <co:symbol>XML</co:symbol>
- </item>
-
- <textinput rdf:about="http://meerkat.oreillynet.com">
- <title>Search Meerkat</title>
- <description>Search Meerkat's RSS Database...</description>
- <name>s</name>
- <link>http://meerkat.oreillynet.com/</link>
- <ti:function>search</ti:function>
- <ti:inputType>regex</ti:inputType>
- </textinput>
-
-</rdf:RDF> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/rss2sample.xml b/plugins/OStatus/extlib/XML/Feed/samples/rss2sample.xml
deleted file mode 100755
index 53483cc51..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/rss2sample.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0"?>
-<rss version="2.0" xmlns:content="http://purl.org/rss/1.0.modules/content/">
- <channel>
- <title>Liftoff News</title>
- <link>http://liftoff.msfc.nasa.gov/</link>
- <description>Liftoff to Space Exploration.</description>
- <language>en-us</language>
- <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
- <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
- <docs>http://blogs.law.harvard.edu/tech/rss</docs>
- <generator>Weblog Editor 2.0</generator>
- <managingEditor>editor@example.com</managingEditor>
- <webMaster>webmaster@example.com</webMaster>
- <item>
- <title>Star City</title>
- <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
- <description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.</description>
- <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
- <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
- </item>
- <item>
- <description>Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st.</description>
- <pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
- <guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
- </item>
- <item>
- <title>The Engine That Does More</title>
- <link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>
- <description>Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.</description>
- <pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>
- <guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>
- <content:encoded><![CDATA[<p>Test content</p>]]></content:encoded>
- </item>
- <item>
- <title>Astronauts' Dirty Laundry</title>
- <link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp</link>
- <description>Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.</description>
- <pubDate>Tue, 20 May 2003 08:56:02 GMT</pubDate>
- <guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>
- </item>
- </channel>
-</rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/sixapart-jp.xml b/plugins/OStatus/extlib/XML/Feed/samples/sixapart-jp.xml
deleted file mode 100755
index f8a04bba5..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/sixapart-jp.xml
+++ /dev/null
@@ -1,226 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<rss version="2.0">
-<channel>
-<title>Six Apart - News</title>
-<link>http://www.sixapart.jp/</link>
-<description></description>
-<language>ja</language>
-<copyright>Copyright 2005</copyright>
-<lastBuildDate>Fri, 07 Oct 2005 19:09:34 +0900</lastBuildDate>
-<generator>http://www.movabletype.org/?v=3.2-ja</generator>
-<docs>http://blogs.law.harvard.edu/tech/rss</docs>
-
-<item>
-<title>ファイブ・ディーが、Movable Typeでブログプロモーションをスタート</title>
-<description><![CDATA[<p><img alt="MIYAZAWAblog_banner.jpg" src="http://www.sixapart.jp/MIYAZAWAblog_banner.jpg" width="200" height="88" align="right" /><br />
-ファイブ・ディーは、Movable Typeで構築したプロモーション ブログ『宮沢和史 中南米ツアーblog Latin America 2005』を開設しました。</p>
-
-<p>9月21日に開設されたこのブログは、ブラジル、ホンジュラス、ニカラグア、メキシコ、キューバの5か国を巡る「Latin America 2005」ツアーに合わせ、そのツアーの模様を同行マネージャーがレポートしていきます。<br />
-さらに今月2日からは宮沢和史自身が日々録音した声をPodcastingするという点でも、ブログを使ったユニークなプロモーションとなっています。</p>
-
-<p><a href="http://www.five-d.co.jp/miyazawa/jp/blog/la2005/">「宮沢和史 中南米ツアーblog Latin America 2005」</a></p>
-
-<p>※シックス・アパートではこうしたブログを使ったプロモーションに最適な製品をご用意しております。<br />
-<ul><li><a href="/movabletype/">Movable Type</a><br />
-<li><a href="/typepad/typepad_promotion.html">TypePad Promotion</a><br />
-</ul></p>]]></description>
-<link>http://www.sixapart.jp/news/2005/10/07-1909.html</link>
-<guid>http://www.sixapart.jp/news/2005/10/07-1909.html</guid>
-<category>news</category>
-<pubDate>Fri, 07 Oct 2005 19:09:34 +0900</pubDate>
-</item>
-<item>
-<title>Movable Type 3.2日本語版の提供を開始</title>
-<description><![CDATA[<p><img alt="Movable Type Logo" src="/images/mt3-logo-small.gif" width="151" height="37"/></p>
-<p>シックス・アパートは、Movable Type 3.2日本語版の提供を開始いたしました。<br />
-ベータテストにご協力いただいた多くの皆様に、スタッフ一同、心から感謝いたします。</p>
-<p>製品概要など、詳しくは<a href="http://www.sixapart.jp/press_releases/2005/09/29-1529.html" title="Six Apart - News: シックス・アパートが、スパム対策強化の「Movable Type 3.2 日本語版」を提供開始">プレスリリース</a>をご参照下さい。</p>
-<p>ご購入のご検討は、<a href="http://www.sixapart.jp/movabletype/purchase-mt.html">Movable Typeのご購入</a>からどうぞ。</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/29-1530.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/29-1530.html</guid>
-<category>news</category>
-<pubDate>Thu, 29 Sep 2005 15:30:00 +0900</pubDate>
-</item>
-<item>
-<title>シックス・アパートが、スパム対策強化の「Movable Type 3.2 日本語版」を提供開始</title>
-<description><![CDATA[<p><プレスリリース資料></p>
-<ul>
- <li><a href="http://www.sixapart.jp/sixapart20050929.pdf">印刷用(PDF版)</a></li>
-</ul>
-<p><strong>シックス・アパートが、スパム対策強化の「Movable Type 3.2 日本語版」を提供開始 ~ スパムの自動判別機能や新ユーザー・インターフェースで、運用管理の機能を強化 ~</strong></p>
-<p>2005年9月29日<br />
-シックス・アパート株式会社</p>
-<p>ブログ・ソフトウェア大手のシックス・アパート株式会社(本社:東京都港区、代表取締役:関 信浩)は、「Movable Type(ムーバブル・タイプ) 3.2 日本語版」(URL:<a href="http://www.sixapart.jp/movabletype/">http://www.sixapart.jp/movabletype/</a>)を9月29日より提供開始いたします。</p>]]></description>
-<link>http://www.sixapart.jp/press_releases/2005/09/29-1529.html</link>
-<guid>http://www.sixapart.jp/press_releases/2005/09/29-1529.html</guid>
-<category>Press Releases</category>
-<pubDate>Thu, 29 Sep 2005 15:29:00 +0900</pubDate>
-</item>
-<item>
-<title>スタッフを募集しています</title>
-<description><![CDATA[<p>シックス・アパートはMovable TypeやTypePadの開発エンジニアなど、スタッフを広く募集しています。具体的な募集職種は次の通りです。</p>
-
-<ul>
-<li><a href="http://www.sixapart.jp/jobs/2005/09/13-0007.html">Movable Type開発エンジニア</a></li>
-<li><a href="http://www.sixapart.jp/jobs/2005/09/13-0004.html">TypePad開発エンジニア</a></li>
-<li><a href="http://www.sixapart.jp/jobs/2005/09/13-0003.html">カスタマーサポート・ディレクター</a></li>
-<li><a href="http://www.sixapart.jp/jobs/2005/09/13-0002.html">マーケティング・広報アシスタント</a></li>
-<li><a href="http://www.sixapart.jp/jobs/2005/09/13-0001.html">開発アシスタント</a></li>
-</ul>
-
-<p>拡大を続ける、日本のブログ市場を積極的にリードする人材を、シックス・アパートは募集しています。上記以外の職種につきましても、お気軽にお問い合わせください。詳しい募集要項や応募方法については、<a href="/jobs/">求人情報のページ</a>をご覧ください。<br />
-</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/27-0906.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/27-0906.html</guid>
-<category>news</category>
-<pubDate>Tue, 27 Sep 2005 09:06:10 +0900</pubDate>
-</item>
-<item>
-<title>サイト接続不具合に関するお詫びと復旧のお知らせ</title>
-<description><![CDATA[<p>9月24日(土)の14:45ごろから、同日18:30ごろまで、シックス・アパート社のウェブサイトが不安定になっており、断続的に接続できない不具合が発生しておりました。このため、この期間中にウェブサイトの閲覧や製品のダウンロードができませんでした。</p>
-
-<p>なお現在は不具合は解消しております。みなさまにご迷惑をおかけしたことをお詫びいたします。</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/26-1000.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/26-1000.html</guid>
-<category>news</category>
-<pubDate>Mon, 26 Sep 2005 10:00:56 +0900</pubDate>
-</item>
-<item>
-<title>企業ブログ向けパッケージ「TypePad Promotion」を新発売</title>
-<description><![CDATA[<p>シックス・アパートは、ウェブログ・サービスTypePadの企業ブログ向けパッケージ「TypePad Promotion」(タイプパッド・プロモーションの発売を10月下旬から開始いたします。</p>
-
-<p>詳しくは、<a href="http://www.sixapart.jp/press_releases/2005/09/20-1500.html" title="プレスリリース: 「TypePad Promotion」新発売">プレスリリース</a>をご参照下さい。</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/20-1500.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/20-1500.html</guid>
-<category>news</category>
-<pubDate>Tue, 20 Sep 2005 15:00:01 +0900</pubDate>
-</item>
-<item>
-<title>シックス・アパートが、法人向けブログパッケージ「TypePad Promotion」を発売</title>
-<description><![CDATA[<p><プレスリリース資料><br />
-<a href="http://www.sixapart.jp/sixapart20050920.pdf">印刷用(PDF版)</a></p>
-
-<p><br />
-<strong>シックス・アパートが、法人向けブログパッケージ「TypePad Promotion」を発売<br />
-~PR/IRサイトやキャンペーンサイトなど企業のプロモーションニーズに特化~<br />
-</strong><br />
-2005年9月20日<br />
-シックス・アパート株式会社</p>
-
-<p>ブログ・サービス大手のシックス・アパート株式会社(本社:東京都港区、代表取締役:関 信浩)は、法人向けプロモーションブログ・パッケージ「TypePad Promotion(タイプパッド・プロモーション)」(URL:<a href="http://www.sixapart.jp/typepad/typepad_promotion.html">http://www.sixapart.jp/typepad/typepad_promotion.html</a>)を10月下旬より販売開始いたします。</p>]]></description>
-<link>http://www.sixapart.jp/press_releases/2005/09/20-1500.html</link>
-<guid>http://www.sixapart.jp/press_releases/2005/09/20-1500.html</guid>
-<category>Press Releases</category>
-<pubDate>Tue, 20 Sep 2005 15:00:00 +0900</pubDate>
-</item>
-<item>
-<title>Six [days] Apart Week</title>
-<description><![CDATA[<p>本日、9月16日はSix Apartの創業者ミナ・トロットの誕生日です。<br />
-私たちの会社は、創業者のトロット夫妻(ベンとミナ)の誕生日が、6日離れていることからSix [days] Apart →Six Apartという風に名付けられています。本日から22日までの6日間を社名の由来となる Six [days] Apart Weekとして、私たちのプロダクトをご紹介させていただきます。</p>
-
-<p>今日は、ブログ・サービスのTypePad(タイプパッド)をご紹介します。<br />
-<img alt="tp-logo.gif" src="http://www.sixapart.jp/tp-logo.gif" width="227" height="52" /></p>
-
-<p>TypePadは、米国PC MAGAZINE誌の2003年EDITOR'S CHOICE とBEST OF THE YEARに選ばれております。<br />
-<img alt="pcmag-ad.gif" src="http://www.sixapart.jp/pcmag-ad.gif" width="297" height="100" /><br />
-</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/16-1941.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/16-1941.html</guid>
-<category>news</category>
-<pubDate>Fri, 16 Sep 2005 19:41:47 +0900</pubDate>
-</item>
-<item>
-<title>ハイパーワークスが商用フォントを利用できるMovable Typeホスティングサービスを開始</title>
-<description><![CDATA[<p>ソフト開発会社の<a href="http://www.hyperwrx.co.jp/">有限会社ハイパーワークス</a>は、商用フォントなど多彩なフォントをブログ上で利用できるブログ・サービス「<a href="http://glyph-on.jp/">Glyph-On!(グリフォン) Movable Type ホスティング サービス</a>」の提供を開始しました。<br />
-</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/14-1700.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/14-1700.html</guid>
-<category>news</category>
-<pubDate>Wed, 14 Sep 2005 17:00:00 +0900</pubDate>
-</item>
-<item>
-<title>Movable Type開発エンジニアの募集</title>
-<description><![CDATA[<p>
-勤務形態: フルタイム<br />
-勤務地: 東京 (赤坂)<br />
-職種: ソフトウェア・エンジニア<br />
-職務内容: Movable Typeの開発業務全般<br />
-募集人数: 若干名
-</p>]]></description>
-<link>http://www.sixapart.jp/jobs/2005/09/13-0007.html</link>
-<guid>http://www.sixapart.jp/jobs/2005/09/13-0007.html</guid>
-<category>Jobs</category>
-<pubDate>Tue, 13 Sep 2005 00:07:00 +0900</pubDate>
-</item>
-<item>
-<title>TypePad開発エンジニアの募集</title>
-<description><![CDATA[<p>
-勤務形態: フルタイム<br />
-勤務地: 東京 (赤坂)<br />
-職種: アプリケーション・エンジニア<br />
-職務内容: TypePadのカスタマイズ、周辺開発<br />
-募集人数: 若干名
-</p>]]></description>
-<link>http://www.sixapart.jp/jobs/2005/09/13-0004.html</link>
-<guid>http://www.sixapart.jp/jobs/2005/09/13-0004.html</guid>
-<category>Jobs</category>
-<pubDate>Tue, 13 Sep 2005 00:04:00 +0900</pubDate>
-</item>
-<item>
-<title>カスタマーサポート・ディレクターの募集</title>
-<description><![CDATA[<p>勤務形態: フルタイム<br />
-勤務地: 東京(赤坂)<br />
-職種: カスタマーサポート・ディレクター<br />
-職務内容: TypePadやMovable Typeのカスタマーサポート業務の統括<br />
-募集人数: 若干名
-</p>
-]]></description>
-<link>http://www.sixapart.jp/jobs/2005/09/13-0003.html</link>
-<guid>http://www.sixapart.jp/jobs/2005/09/13-0003.html</guid>
-<category>Jobs</category>
-<pubDate>Tue, 13 Sep 2005 00:03:30 +0900</pubDate>
-</item>
-<item>
-<title>アルバイト(マーケティング・広報アシスタント)の募集</title>
-<description><![CDATA[<p>勤務形態: アルバイト<br />
-勤務地: 東京(港区)<br />
-職種:マーケティング・PRのアシスタント業務<br />
-募集人数: 若干名<br />
-時給:1000円~(但し、試用期間終了後に応相談)。交通費支給<br />
-時間:平日10時30分~18時30分まで。週3日以上(応相談)<br />
-</p>]]></description>
-<link>http://www.sixapart.jp/jobs/2005/09/13-0002.html</link>
-<guid>http://www.sixapart.jp/jobs/2005/09/13-0002.html</guid>
-<category>Jobs</category>
-<pubDate>Tue, 13 Sep 2005 00:02:00 +0900</pubDate>
-</item>
-<item>
-<title>アルバイト(開発アシスタント)の募集</title>
-<description><![CDATA[<p>勤務形態: アルバイト<br />
-勤務地: 東京(港区)<br />
-職種: アプリケーション開発のアシスタント業務<br />
-募集人数: 若干名<br />
-時給:1000円~(但し、試用期間終了後に応相談)。交通費支給<br />
-時間:平日10時30分~18時30分まで。週3日以上(応相談)
-</p>]]></description>
-<link>http://www.sixapart.jp/jobs/2005/09/13-0001.html</link>
-<guid>http://www.sixapart.jp/jobs/2005/09/13-0001.html</guid>
-<category>Jobs</category>
-<pubDate>Tue, 13 Sep 2005 00:01:00 +0900</pubDate>
-</item>
-<item>
-<title>TypePad Japan がバージョンアップしました。</title>
-<description><![CDATA[<p><a href="http://www.sixapart.jp/typepad/">「TypePad Japan(タイプパッドジャパン)」</a>において、本日、「TypePad 1.6 日本語版」へのバージョンアップを行いました。最新版となる「TypePad 1.6 日本語版」では、ブログデザインの機能強化、ポッドキャスティング対応、モブログ対応に加え、今回新たに大幅な容量アップが行われております。皆様、新しくなった<a href="http://www.sixapart.jp/typepad/">TypePad Japan</a>にどうぞご期待ください。</p>
-
-<p>なお、TypePadの携帯対応強化に関しましては、本日よりTypePad Japanのお客様を対象にオープン・ベータを開始しております。</p>
-
-<p>2005年9月5日発表のTypePad日本語版 1.6プレスリリースは<a href="http://www.sixapart.jp/press_releases/2005/09/05-1420.html">こちら</a>をご覧下さい。</p>]]></description>
-<link>http://www.sixapart.jp/news/2005/09/12-1953.html</link>
-<guid>http://www.sixapart.jp/news/2005/09/12-1953.html</guid>
-<category>news</category>
-<pubDate>Mon, 12 Sep 2005 19:53:07 +0900</pubDate>
-</item>
-
-
-</channel>
-</rss> \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/samples/technorati.feed b/plugins/OStatus/extlib/XML/Feed/samples/technorati.feed
deleted file mode 100755
index 6274a32cd..000000000
--- a/plugins/OStatus/extlib/XML/Feed/samples/technorati.feed
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<rss version="2.0"
- xmlns:tapi="http://api.technorati.com/dtd/tapi-002.xml">
- <channel>
- <title>[Technorati] Tag results for greenbelt</title>
- <link>http://www.technorati.com/tag/greenbelt</link>
- <description>Posts tagged with "greenbelt" on Technorati.</description>
- <pubDate>Mon, 08 Aug 2005 15:15:08 GMT</pubDate>
- <category domain="http://www.technorati.com/tag">greenbelt</category>
- <tapi:inboundblogs>2</tapi:inboundblogs>
- <tapi:inboundlinks>2</tapi:inboundlinks>
- <cloud domain="rpc.sys.com" port="80" path="/RPC2" registerProcedure="myCloud.rssPleaseNotify" protocol="xml-rpc" />
- <generator>Technorati v1.0</generator>
- <image>
- <url>http://static.technorati.com/pix/logos/logo_reverse_sm.gif</url>
- <title>Technorati logo</title>
- <link>http://www.technorati.com</link>
- </image>
- <skipHours>
- <hour>1</hour>
- <hour>7</hour>
- <hour>9</hour>
- </skipHours>
- <webMaster>support@technorati.com (Technorati Support)</webMaster>
- <docs>http://blogs.law.harvad.edu/tech/rss</docs>
- <ttl>60</ttl>
- <item>
- <title>Greenbelt</title>
- <link>http://maggidawn.typepad.com/maggidawn/2005/07/greenbelt.html</link>
- <description>So if the plan goes according to plan (!)... I'll be speaking at Greenbelt at these times: Slot 1...</description>
- <guid isPermaLink="true">http://maggidawn.typepad.com/maggidawn/2005/07/greenbelt.html</guid>
- <pubDate>Mon, 18 Jul 2005 02:11:42 GMT</pubDate>
- <category>James</category>
- <tapi:linkcreated>2005-07-11 02:08:12</tapi:linkcreated>
- <comments>http://www.technorati.com/cosmos/search.html?url=http%3A%2F%2Fmaggidawn.typepad.com%2Fmaggidawn%2F2005%2F07%2Fgreenbelt.html</comments>
- <tapi:inboundblogs>190</tapi:inboundblogs>
- <tapi:inboundlinks>237</tapi:inboundlinks>
- <source url="http://maggidawn.typepad.com/maggidawn/index.rdf">maggi dawn</source>
- </item>
-
- <item>
- <title>Walking along the Greenbelt</title>
- <link>http://pictureshomeless.blogspot.com/2005/06/walking-along-greenbelt.html</link>
- <description>[IMG] Photo of homeless man walking near the greenbelt in Boise, Idaho Tags: photo homeless greenbelt Boise Idaho picture</description>
- <guid isPermaLink="true">http://pictureshomeless.blogspot.com/2005/06/walking-along-greenbelt.html</guid>
- <pubDate>Tue, 28 Jun 2005 01:41:24 GMT</pubDate>
- <tapi:linkcreated>2005-06-26 17:24:03</tapi:linkcreated>
- <comments>http://www.technorati.com/cosmos/search.html?url=http%3A%2F%2Fpictureshomeless.blogspot.com%2F2005%2F06%2Fwalking-along-greenbelt.html</comments>
- <tapi:inboundblogs>2</tapi:inboundblogs>
- <tapi:inboundlinks>2</tapi:inboundlinks>
- </item>
-
- </channel>
-</rss>
diff --git a/plugins/OStatus/extlib/XML/Feed/schemas/atom.rnc b/plugins/OStatus/extlib/XML/Feed/schemas/atom.rnc
deleted file mode 100755
index e662d2626..000000000
--- a/plugins/OStatus/extlib/XML/Feed/schemas/atom.rnc
+++ /dev/null
@@ -1,338 +0,0 @@
-# -*- rnc -*-
-# RELAX NG Compact Syntax Grammar for the
-# Atom Format Specification Version 11
-
-namespace atom = "http://www.w3.org/2005/Atom"
-namespace xhtml = "http://www.w3.org/1999/xhtml"
-namespace s = "http://www.ascc.net/xml/schematron"
-namespace local = ""
-
-start = atomFeed | atomEntry
-
-# Common attributes
-
-atomCommonAttributes =
- attribute xml:base { atomUri }?,
- attribute xml:lang { atomLanguageTag }?,
- undefinedAttribute*
-
-# Text Constructs
-
-atomPlainTextConstruct =
- atomCommonAttributes,
- attribute type { "text" | "html" }?,
- text
-
-atomXHTMLTextConstruct =
- atomCommonAttributes,
- attribute type { "xhtml" },
- xhtmlDiv
-
-atomTextConstruct = atomPlainTextConstruct | atomXHTMLTextConstruct
-
-# Person Construct
-
-atomPersonConstruct =
- atomCommonAttributes,
- (element atom:name { text }
- & element atom:uri { atomUri }?
- & element atom:email { atomEmailAddress }?
- & extensionElement*)
-
-# Date Construct
-
-atomDateConstruct =
- atomCommonAttributes,
- xsd:dateTime
-
-# atom:feed
-
-atomFeed =
- [
- s:rule [
- context = "atom:feed"
- s:assert [
- test = "atom:author or not(atom:entry[not(atom:author)])"
- "An atom:feed must have an atom:author unless all "
- ~ "of its atom:entry children have an atom:author."
- ]
- ]
- ]
- element atom:feed {
- atomCommonAttributes,
- (atomAuthor*
- & atomCategory*
- & atomContributor*
- & atomGenerator?
- & atomIcon?
- & atomId
- & atomLink*
- & atomLogo?
- & atomRights?
- & atomSubtitle?
- & atomTitle
- & atomUpdated
- & extensionElement*),
- atomEntry*
- }
-
-# atom:entry
-
-atomEntry =
- [
- s:rule [
- context = "atom:entry"
- s:assert [
- test = "atom:link[@rel='alternate'] "
- ~ "or atom:link[not(@rel)] "
- ~ "or atom:content"
- "An atom:entry must have at least one atom:link element "
- ~ "with a rel attribute of 'alternate' "
- ~ "or an atom:content."
- ]
- ]
- s:rule [
- context = "atom:entry"
- s:assert [
- test = "atom:author or "
- ~ "../atom:author or atom:source/atom:author"
- "An atom:entry must have an atom:author "
- ~ "if its feed does not."
- ]
- ]
- ]
- element atom:entry {
- atomCommonAttributes,
- (atomAuthor*
- & atomCategory*
- & atomContent?
- & atomContributor*
- & atomId
- & atomLink*
- & atomPublished?
- & atomRights?
- & atomSource?
- & atomSummary?
- & atomTitle
- & atomUpdated
- & extensionElement*)
- }
-
-# atom:content
-
-atomInlineTextContent =
- element atom:content {
- atomCommonAttributes,
- attribute type { "text" | "html" }?,
- (text)*
- }
-
-atomInlineXHTMLContent =
- element atom:content {
- atomCommonAttributes,
- attribute type { "xhtml" },
- xhtmlDiv
- }
-
-atomInlineOtherContent =
- element atom:content {
- atomCommonAttributes,
- attribute type { atomMediaType }?,
- (text|anyElement)*
- }
-
-atomOutOfLineContent =
- element atom:content {
- atomCommonAttributes,
- attribute type { atomMediaType }?,
- attribute src { atomUri },
- empty
- }
-
-atomContent = atomInlineTextContent
- | atomInlineXHTMLContent
- | atomInlineOtherContent
- | atomOutOfLineContent
-
-# atom:author
-
-atomAuthor = element atom:author { atomPersonConstruct }
-
-# atom:category
-
-atomCategory =
- element atom:category {
- atomCommonAttributes,
- attribute term { text },
- attribute scheme { atomUri }?,
- attribute label { text }?,
- undefinedContent
- }
-
-# atom:contributor
-
-atomContributor = element atom:contributor { atomPersonConstruct }
-
-# atom:generator
-
-atomGenerator = element atom:generator {
- atomCommonAttributes,
- attribute uri { atomUri }?,
- attribute version { text }?,
- text
-}
-
-# atom:icon
-
-atomIcon = element atom:icon {
- atomCommonAttributes,
- (atomUri)
-}
-
-# atom:id
-
-atomId = element atom:id {
- atomCommonAttributes,
- (atomUri)
-}
-
-# atom:logo
-
-atomLogo = element atom:logo {
- atomCommonAttributes,
- (atomUri)
-}
-
-# atom:link
-
-atomLink =
- element atom:link {
- atomCommonAttributes,
- attribute href { atomUri },
- attribute rel { atomNCName | atomUri }?,
- attribute type { atomMediaType }?,
- attribute hreflang { atomLanguageTag }?,
- attribute title { text }?,
- attribute length { text }?,
- undefinedContent
- }
-
-# atom:published
-
-atomPublished = element atom:published { atomDateConstruct }
-
-# atom:rights
-
-atomRights = element atom:rights { atomTextConstruct }
-
-# atom:source
-
-atomSource =
- element atom:source {
- atomCommonAttributes,
- (atomAuthor*
- & atomCategory*
- & atomContributor*
- & atomGenerator?
- & atomIcon?
- & atomId?
- & atomLink*
- & atomLogo?
- & atomRights?
- & atomSubtitle?
- & atomTitle?
- & atomUpdated?
- & extensionElement*)
- }
-
-# atom:subtitle
-
-atomSubtitle = element atom:subtitle { atomTextConstruct }
-
-# atom:summary
-
-atomSummary = element atom:summary { atomTextConstruct }
-
-# atom:title
-
-atomTitle = element atom:title { atomTextConstruct }
-
-# atom:updated
-
-atomUpdated = element atom:updated { atomDateConstruct }
-
-# Low-level simple types
-
-atomNCName = xsd:string { minLength = "1" pattern = "[^:]*" }
-
-# Whatever a media type is, it contains at least one slash
-atomMediaType = xsd:string { pattern = ".+/.+" }
-
-# As defined in RFC 3066
-atomLanguageTag = xsd:string {
- pattern = "[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*"
-}
-
-# Unconstrained; it's not entirely clear how IRI fit into
-# xsd:anyURI so let's not try to constrain it here
-atomUri = text
-
-# Whatever an email address is, it contains at least one @
-atomEmailAddress = xsd:string { pattern = ".+@.+" }
-
-# Simple Extension
-
-simpleExtensionElement =
- element * - atom:* {
- text
- }
-
-# Structured Extension
-
-structuredExtensionElement =
- element * - atom:* {
- (attribute * { text }+,
- (text|anyElement)*)
- | (attribute * { text }*,
- (text?, anyElement+, (text|anyElement)*))
- }
-
-# Other Extensibility
-
-extensionElement =
- simpleExtensionElement | structuredExtensionElement
-
-undefinedAttribute =
- attribute * - (xml:base | xml:lang | local:*) { text }
-
-undefinedContent = (text|anyForeignElement)*
-
-anyElement =
- element * {
- (attribute * { text }
- | text
- | anyElement)*
- }
-
-anyForeignElement =
- element * - atom:* {
- (attribute * { text }
- | text
- | anyElement)*
- }
-
-# XHTML
-
-anyXHTML = element xhtml:* {
- (attribute * { text }
- | text
- | anyXHTML)*
-}
-
-xhtmlDiv = element xhtml:div {
- (attribute * { text }
- | text
- | anyXHTML)*
-}
-
-# EOF \ No newline at end of file
diff --git a/plugins/OStatus/extlib/XML/Feed/schemas/rss10.rnc b/plugins/OStatus/extlib/XML/Feed/schemas/rss10.rnc
deleted file mode 100755
index 725094788..000000000
--- a/plugins/OStatus/extlib/XML/Feed/schemas/rss10.rnc
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<!-- http://www.xml.com/lpt/a/2002/01/23/relaxng.html -->
-<!-- http://www.oasis-open.org/committees/relax-ng/tutorial-20011203.html -->
-<!-- http://www.zvon.org/xxl/XMLSchemaTutorial/Output/ser_wildcards_st8.html -->
-
-<grammar xmlns='http://relaxng.org/ns/structure/1.0'
- xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
- xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
- ns='http://purl.org/rss/1.0/'
- datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'>
-
- <start>
- <element name='RDF' ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <ref name='RDFContent'/>
- </element>
- </start>
-
- <define name='RDFContent' ns='http://purl.org/rss/1.0/'>
- <interleave>
- <element name='channel'>
- <ref name='channelContent'/>
- </element>
- <optional>
- <element name='image'><ref name='imageContent'/></element>
- </optional>
- <oneOrMore>
- <element name='item'><ref name='itemContent'/></element>
- </oneOrMore>
- </interleave>
- </define>
-
- <define name='channelContent' combine="interleave">
- <interleave>
- <element name='title'><data type='string'/></element>
- <element name='link'><data type='anyURI'/></element>
- <element name='description'><data type='string'/></element>
- <element name='image'>
- <attribute name='resource' ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <data type='anyURI'/>
- </attribute>
- </element>
- <element name='items'>
- <ref name='itemsContent'/>
- </element>
- <attribute name='about' ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <data type='anyURI'/>
- </attribute>
- </interleave>
- </define>
-
- <define name="itemsContent">
- <element name="Seq" ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <oneOrMore>
- <element name="li" ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <choice>
- <attribute name='resource'> <!-- Why doesn't RDF/RSS1.0 ns qualify this attribute? -->
- <data type='anyURI'/>
- </attribute>
- <attribute name='resource' ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <data type='anyURI'/>
- </attribute>
- </choice>
- </element>
- </oneOrMore>
- </element>
- </define>
-
- <define name='imageContent'>
- <interleave>
- <element name='title'><data type='string'/></element>
- <element name='link'><data type='anyURI'/></element>
- <element name='url'><data type='anyURI'/></element>
- <attribute name='about' ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <data type='anyURI'/>
- </attribute>
- </interleave>
- </define>
-
- <define name='itemContent'>
- <interleave>
- <element name='title'><data type='string'/></element>
- <element name='link'><data type='anyURI'/></element>
- <optional><element name='description'><data type='string'/></element></optional>
- <ref name="anyThing"/>
- <attribute name='about' ns='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
- <data type='anyURI'/>
- </attribute>
- </interleave>
- </define>
-
-
- <define name='anyThing'>
- <zeroOrMore>
- <choice>
- <text/>
- <element>
- <anyName>
- <except>
- <nsName/>
- </except>
- </anyName>
- <ref name='anyThing'/>
- <zeroOrMore>
- <attribute>
- <anyName/>
- </attribute>
- </zeroOrMore>
- </element>
- </choice>
- </zeroOrMore>
- </define>
-
-</grammar>
diff --git a/plugins/OStatus/extlib/XML/Feed/schemas/rss11.rnc b/plugins/OStatus/extlib/XML/Feed/schemas/rss11.rnc
deleted file mode 100755
index c8633766f..000000000
--- a/plugins/OStatus/extlib/XML/Feed/schemas/rss11.rnc
+++ /dev/null
@@ -1,218 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- RELAX NG Compact Schema for RSS 1.1
- Sean B. Palmer, inamidst.com
- Christopher Schmidt, crschmidt.net
- License: This schema is in the public domain
--->
-<grammar xmlns:rss="http://purl.org/net/rss1.1#" xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns="http://purl.org/net/rss1.1#" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
- <start>
- <ref name="Channel"/>
- </start>
- <define name="Channel">
- <a:documentation>http://purl.org/net/rss1.1#Channel</a:documentation>
- <element name="Channel">
- <ref name="Channel.content"/>
-
- </element>
- </define>
- <define name="Channel.content">
- <optional>
- <ref name="AttrXMLLang"/>
- </optional>
- <optional>
- <ref name="AttrXMLBase"/>
- </optional>
-
- <ref name="AttrRDFAbout"/>
- <interleave>
- <ref name="title"/>
- <ref name="link"/>
- <ref name="description"/>
- <optional>
- <ref name="image"/>
- </optional>
- <zeroOrMore>
-
- <ref name="Any"/>
- </zeroOrMore>
- <ref name="items"/>
- </interleave>
- </define>
- <define name="title">
- <a:documentation>http://purl.org/net/rss1.1#title</a:documentation>
- <element name="title">
-
- <ref name="title.content"/>
- </element>
- </define>
- <define name="title.content">
- <optional>
- <ref name="AttrXMLLang"/>
- </optional>
- <text/>
- </define>
-
- <define name="link">
- <a:documentation>http://purl.org/net/rss1.1#link</a:documentation>
- <element name="link">
- <ref name="link.content"/>
- </element>
- </define>
- <define name="link.content">
- <data type="anyURI"/>
-
- </define>
- <define name="description">
- <a:documentation>http://purl.org/net/rss1.1#description</a:documentation>
- <element name="description">
- <ref name="description.content"/>
- </element>
- </define>
- <define name="description.content">
-
- <optional>
- <ref name="AttrXMLLang"/>
- </optional>
- <text/>
- </define>
- <define name="image">
- <a:documentation>http://purl.org/net/rss1.1#image</a:documentation>
- <element name="image">
-
- <ref name="image.content"/>
- </element>
- </define>
- <define name="image.content">
- <optional>
- <ref name="AttrXMLLang"/>
- </optional>
- <ref name="AttrRDFResource"/>
- <interleave>
-
- <ref name="title"/>
- <optional>
- <ref name="link"/>
- </optional>
- <ref name="url"/>
- <zeroOrMore>
- <ref name="Any"/>
- </zeroOrMore>
- </interleave>
-
- </define>
- <define name="url">
- <a:documentation>http://purl.org/net/rss1.1#url</a:documentation>
- <element name="url">
- <ref name="url.content"/>
- </element>
- </define>
- <define name="url.content">
-
- <data type="anyURI"/>
- </define>
- <define name="items">
- <a:documentation>http://purl.org/net/rss1.1#items</a:documentation>
- <element name="items">
- <ref name="items.content"/>
- </element>
- </define>
-
- <define name="items.content">
- <optional>
- <ref name="AttrXMLLang"/>
- </optional>
- <ref name="AttrRDFCollection"/>
- <zeroOrMore>
- <ref name="item"/>
- </zeroOrMore>
- </define>
-
- <define name="item">
- <a:documentation>http://purl.org/net/rss1.1#item</a:documentation>
- <element name="item">
- <ref name="item.content"/>
- </element>
- </define>
- <define name="item.content">
- <optional>
-
- <ref name="AttrXMLLang"/>
- </optional>
- <ref name="AttrRDFAbout"/>
- <interleave>
- <ref name="title"/>
- <ref name="link"/>
- <optional>
- <ref name="description"/>
- </optional>
-
- <optional>
- <ref name="image"/>
- </optional>
- <zeroOrMore>
- <ref name="Any"/>
- </zeroOrMore>
- </interleave>
- </define>
- <define name="Any">
-
- <a:documentation>http://purl.org/net/rss1.1#Any</a:documentation>
- <element>
- <anyName>
- <except>
- <nsName/>
- </except>
- </anyName>
- <ref name="Any.content"/>
-
- </element>
- </define>
- <define name="Any.content">
- <zeroOrMore>
- <attribute>
- <anyName>
- <except>
- <nsName/>
- <nsName ns=""/>
-
- </except>
- </anyName>
- </attribute>
- </zeroOrMore>
- <mixed>
- <zeroOrMore>
- <ref name="Any"/>
- </zeroOrMore>
- </mixed>
-
- </define>
- <define name="AttrXMLLang">
- <attribute name="xml:lang">
- <data type="language"/>
- </attribute>
- </define>
- <define name="AttrXMLBase">
- <attribute name="xml:base">
- <data type="anyURI"/>
-
- </attribute>
- </define>
- <define name="AttrRDFAbout">
- <attribute name="rdf:about">
- <data type="anyURI"/>
- </attribute>
- </define>
- <define name="AttrRDFResource">
- <attribute name="rdf:parseType">
-
- <value>Resource</value>
- </attribute>
- </define>
- <define name="AttrRDFCollection">
- <attribute name="rdf:parseType">
- <value>Collection</value>
- </attribute>
- </define>
-
-</grammar>
diff --git a/plugins/OStatus/extlib/xml-feed-parser-bug-16416.patch b/plugins/OStatus/extlib/xml-feed-parser-bug-16416.patch
deleted file mode 100644
index c53bd9737..000000000
--- a/plugins/OStatus/extlib/xml-feed-parser-bug-16416.patch
+++ /dev/null
@@ -1,14 +0,0 @@
-diff --git a/htdocs/lib/pear/XML/Feed/Parser/RSS2.php b/htdocs/lib/pear/XML/Feed/Parser/RSS2.php
-index c5d79d1..308a4ab 100644
---- a/htdocs/lib/pear/XML/Feed/Parser/RSS2.php
-+++ b/htdocs/lib/pear/XML/Feed/Parser/RSS2.php
-@@ -321,7 +321,8 @@ class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type
- */
- function getLink($offset, $attribute = 'href', $params = array())
- {
-- $links = $this->model->getElementsByTagName('link');
-+ $xPath = new DOMXPath($this->model);
-+ $links = $xPath->query('//link');
-
- if ($links->length <= $offset) {
- return false;
diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php
index f137946ab..f25a843c3 100644
--- a/plugins/OStatus/lib/activity.php
+++ b/plugins/OStatus/lib/activity.php
@@ -55,6 +55,9 @@ class ActivityUtils
const TYPE = 'type';
const HREF = 'href';
+ const CONTENT = 'content';
+ const SRC = 'src';
+
/**
* Get the permalink for an Activity object
*
@@ -139,6 +142,64 @@ class ActivityUtils
return $el->textContent;
}
}
+
+ /**
+ * Get the content of an atom:entry-like object
+ *
+ * @param DOMElement $element The element to examine.
+ *
+ * @return string unencoded HTML content of the element, like "This -&lt; is <b>HTML</b>."
+ *
+ * @todo handle remote content
+ * @todo handle embedded XML mime types
+ * @todo handle base64-encoded non-XML and non-text mime types
+ */
+
+ static function getContent($element)
+ {
+ $contentEl = ActivityUtils::child($element, self::CONTENT);
+
+ if (!empty($contentEl)) {
+
+ $src = $contentEl->getAttribute(self::SRC);
+
+ if (!empty($src)) {
+ throw new ClientException(_("Can't handle remote content yet."));
+ }
+
+ $type = $contentEl->getAttribute(self::TYPE);
+
+ // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3
+
+ if ($type == 'text') {
+ return $contentEl->textContent;
+ } else if ($type == 'html') {
+ $text = $contentEl->textContent;
+ return htmlspecialchars_decode($text, ENT_QUOTES);
+ } else if ($type == 'xhtml') {
+ $divEl = ActivityUtils::child($contentEl, 'div');
+ if (empty($divEl)) {
+ return null;
+ }
+ $doc = $divEl->ownerDocument;
+ $text = '';
+ $children = $divEl->childNodes;
+
+ for ($i = 0; $i < $children->length; $i++) {
+ $child = $children->item($i);
+ $text .= $doc->saveXML($child);
+ }
+ return trim($text);
+ } else if (in_array(array('text/xml', 'application/xml'), $type) ||
+ preg_match('#(+|/)xml$#', $type)) {
+ throw new ClientException(_("Can't handle embedded XML content yet."));
+ } else if (strncasecmp($type, 'text/', 5)) {
+ return $contentEl->textContent;
+ } else {
+ throw new ClientException(_("Can't handle embedded Base64 content yet."));
+ }
+ }
+ }
}
/**
@@ -182,7 +243,6 @@ class ActivityObject
const TITLE = 'title';
const SUMMARY = 'summary';
- const CONTENT = 'content';
const ID = 'id';
const SOURCE = 'source';
@@ -198,6 +258,7 @@ class ActivityObject
public $content;
public $link;
public $source;
+ public $avatar;
/**
* Constructor
@@ -209,8 +270,12 @@ class ActivityObject
* @param DOMElement $element DOM thing to turn into an Activity thing
*/
- function __construct($element)
+ function __construct($element = null)
{
+ if (empty($element)) {
+ return;
+ }
+
$this->element = $element;
if ($element->tagName == 'author') {
@@ -239,10 +304,11 @@ class ActivityObject
$this->id = $this->_childContent($element, self::ID);
$this->title = $this->_childContent($element, self::TITLE);
$this->summary = $this->_childContent($element, self::SUMMARY);
- $this->content = $this->_childContent($element, self::CONTENT);
$this->source = $this->_getSource($element);
+ $this->content = ActivityUtils::getContent($element);
+
$this->link = ActivityUtils::getPermalink($element);
// XXX: grab PoCo stuff
@@ -279,6 +345,65 @@ class ActivityObject
}
}
}
+
+ static function fromNotice($notice)
+ {
+ $object = new ActivityObject();
+
+ $object->type = ActivityObject::NOTE;
+
+ $object->id = $notice->uri;
+ $object->title = $notice->content;
+ $object->content = $notice->rendered;
+ $object->link = $notice->bestUrl();
+
+ return $object;
+ }
+
+ static function fromProfile($profile)
+ {
+ $object = new ActivityObject();
+
+ $object->type = ActivityObject::PERSON;
+ $object->id = $profile->getUri();
+ $object->title = $profile->getBestName();
+ $object->link = $profile->profileurl;
+
+ return $object;
+ }
+
+ function asString($tag='activity:object')
+ {
+ $xs = new XMLStringer(true);
+
+ $xs->elementStart($tag);
+
+ $xs->element('activity:object-type', null, $this->type);
+
+ $xs->element(self::ID, null, $this->id);
+
+ if (!empty($this->title)) {
+ $xs->element(self::TITLE, null, $this->title);
+ }
+
+ if (!empty($this->summary)) {
+ $xs->element(self::SUMMARY, null, $this->summary);
+ }
+
+ if (!empty($this->content)) {
+ // XXX: assuming HTML content here
+ $xs->element(self::CONTENT, array('type' => 'html'), $this->content);
+ }
+
+ if (!empty($this->link)) {
+ $xs->element('link', array('rel' => 'alternate', 'type' => 'text/html'),
+ $this->content);
+ }
+
+ $xs->elementEnd($tag);
+
+ return $xs->getString();
+ }
}
/**
@@ -303,6 +428,93 @@ class ActivityVerb
const FRIEND = 'http://activitystrea.ms/schema/1.0/make-friend';
const JOIN = 'http://activitystrea.ms/schema/1.0/join';
const TAG = 'http://activitystrea.ms/schema/1.0/tag';
+
+ // Custom OStatus verbs for the flipside until they're standardized
+ const DELETE = 'http://ostatus.org/schema/1.0/unfollow';
+ const UNFAVORITE = 'http://ostatus.org/schema/1.0/unfavorite';
+ const UNFOLLOW = 'http://ostatus.org/schema/1.0/unfollow';
+ const LEAVE = 'http://ostatus.org/schema/1.0/leave';
+}
+
+class ActivityContext
+{
+ public $replyToID;
+ public $replyToUrl;
+ public $location;
+ public $attention = array();
+ public $conversation;
+
+ const THR = 'http://purl.org/syndication/thread/1.0';
+ const GEORSS = 'http://www.georss.org/georss';
+ const OSTATUS = 'http://ostatus.org/schema/1.0';
+
+ const INREPLYTO = 'in-reply-to';
+ const REF = 'ref';
+ const HREF = 'href';
+
+ const POINT = 'point';
+
+ const ATTENTION = 'ostatus:attention';
+ const CONVERSATION = 'ostatus:conversation';
+
+ function __construct($element)
+ {
+ $replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR);
+
+ if (!empty($replyToEl)) {
+ $this->replyToID = $replyToEl->getAttribute(self::REF);
+ $this->replyToUrl = $replyToEl->getAttribute(self::HREF);
+ }
+
+ $this->location = $this->getLocation($element);
+
+ $this->conversation = ActivityUtils::getLink($element, self::CONVERSATION);
+
+ // Multiple attention links allowed
+
+ $links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK);
+
+ for ($i = 0; $i < $links->length; $i++) {
+
+ $link = $links->item($i);
+
+ $linkRel = $link->getAttribute(ActivityUtils::REL);
+
+ if ($linkRel == self::ATTENTION) {
+ $this->attention[] = $link->getAttribute(self::HREF);
+ }
+ }
+ }
+
+ /**
+ * Parse location given as a GeoRSS-simple point, if provided.
+ * http://www.georss.org/simple
+ *
+ * @param feed item $entry
+ * @return mixed Location or false
+ */
+ function getLocation($dom)
+ {
+ $points = $dom->getElementsByTagNameNS(self::GEORSS, self::POINT);
+
+ for ($i = 0; $i < $points->length; $i++) {
+ $point = $points->item($i)->textContent;
+ $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace"
+ $point = preg_replace('/\s+/', ' ', $point);
+ $point = trim($point);
+ $coords = explode(' ', $point);
+ if (count($coords) == 2) {
+ list($lat, $lon) = $coords;
+ if (is_numeric($lat) && is_numeric($lon)) {
+ common_log(LOG_INFO, "Looking up location for $lat $lon from georss");
+ return Location::fromLatLon($lat, $lon);
+ }
+ }
+ common_log(LOG_ERR, "Ignoring bogus georss:point value $point");
+ }
+
+ return null;
+ }
}
/**
@@ -351,6 +563,11 @@ class Activity
public $entry; // the source entry
public $feed; // the source feed
+ public $summary; // summary of activity
+ public $content; // HTML content of activity
+ public $id; // ID of the activity
+ public $title; // title of the activity
+
/**
* Turns a regular old Atom <entry> into a magical activity
*
@@ -358,8 +575,12 @@ class Activity
* @param DOMElement $feed Atom feed, for context
*/
- function __construct($entry, $feed = null)
+ function __construct($entry = null, $feed = null)
{
+ if (is_null($entry)) {
+ return;
+ }
+
$this->entry = $entry;
$this->feed = $feed;
@@ -420,7 +641,9 @@ class Activity
$contextEl = $this->_child($entry, self::CONTEXT);
if (!empty($contextEl)) {
- $this->context = new ActivityObject($contextEl);
+ $this->context = new ActivityContext($contextEl);
+ } else {
+ $this->context = new ActivityContext($entry);
}
$targetEl = $this->_child($entry, self::TARGET);
@@ -428,6 +651,10 @@ class Activity
if (!empty($targetEl)) {
$this->target = new ActivityObject($targetEl);
}
+
+ $this->summary = ActivityUtils::childContent($entry, 'summary');
+ $this->id = ActivityUtils::childContent($entry, 'id');
+ $this->content = ActivityUtils::getContent($entry);
}
/**
@@ -441,6 +668,47 @@ class Activity
return null;
}
+ function asString($namespace=false)
+ {
+ $xs = new XMLStringer(true);
+
+ if ($namespace) {
+ $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
+ 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
+ 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
+ } else {
+ $attrs = array();
+ }
+
+ $xs->elementStart('entry', $attrs);
+
+ $xs->element('id', null, $this->id);
+ $xs->element('title', null, $this->title);
+ $xs->element('published', null, common_date_iso8601($this->time));
+ $xs->element('content', array('type' => 'html'), $this->content);
+
+ if (!empty($this->summary)) {
+ $xs->element('summary', null, $this->summary);
+ }
+
+ if (!empty($this->link)) {
+ $xs->element('link', array('rel' => 'alternate',
+ 'type' => 'text/html'),
+ $this->link);
+ }
+
+ // XXX: add context
+ // XXX: add target
+
+ $xs->raw($this->actor->asString());
+ $xs->element('activity:verb', null, $this->verb);
+ $xs->raw($this->object->asString());
+
+ $xs->elementEnd('entry');
+
+ return $xs->getString();
+ }
+
private function _child($element, $tag, $namespace=self::SPEC)
{
return ActivityUtils::child($element, $tag, $namespace);
diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php
index 39985fc90..7afb71bdc 100644
--- a/plugins/OStatus/lib/feeddiscovery.php
+++ b/plugins/OStatus/lib/feeddiscovery.php
@@ -48,6 +48,14 @@ class FeedSubNoFeedException extends FeedSubException
{
}
+class FeedSubBadXmlException extends FeedSubException
+{
+}
+
+class FeedSubNoHubException extends FeedSubException
+{
+}
+
/**
* Given a web page or feed URL, discover the final location of the feed
* and return its current contents.
@@ -57,21 +65,25 @@ class FeedSubNoFeedException extends FeedSubException
* if ($feed->discoverFromURL($url)) {
* print $feed->uri;
* print $feed->type;
- * processFeed($feed->body);
+ * processFeed($feed->feed); // DOMDocument
* }
*/
class FeedDiscovery
{
public $uri;
public $type;
- public $body;
+ public $feed;
+ /** Post-initialize query helper... */
+ public function getLink($rel, $type=null)
+ {
+ // @fixme check for non-Atom links in RSS2 feeds as well
+ return self::getAtomLink($rel, $type);
+ }
- public function feedMunger()
+ public function getAtomLink($rel, $type=null)
{
- require_once 'XML/Feed/Parser.php';
- $feed = new XML_Feed_Parser($this->body, false, false, true); // @fixme
- return new FeedMunger($feed, $this->uri);
+ return ActivityUtils::getLink($this->feed->documentElement, $rel, $type);
}
/**
@@ -90,6 +102,7 @@ class FeedDiscovery
$client = new HTTPClient();
$response = $client->get($url);
} catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_ERR, __METHOD__ . " Failure for $url - " . $e->getMessage());
throw new FeedSubBadURLException($e);
}
@@ -107,7 +120,12 @@ class FeedDiscovery
return $this->initFromResponse($response);
}
-
+
+ function discoverFromFeedURL($url)
+ {
+ return $this->discoverFromURL($url, false);
+ }
+
function initFromResponse($response)
{
if (!$response->isOk()) {
@@ -122,16 +140,26 @@ class FeedDiscovery
$type = $response->getHeader('Content-Type');
if (preg_match('!^(text/xml|application/xml|application/(rss|atom)\+xml)!i', $type)) {
- $this->uri = $sourceurl;
- $this->type = $type;
- $this->body = $body;
- return true;
+ return $this->init($sourceurl, $type, $body);
} else {
common_log(LOG_WARNING, "Unrecognized feed type $type for $sourceurl");
throw new FeedSubUnrecognizedTypeException($type);
}
}
+ function init($sourceurl, $type, $body)
+ {
+ $feed = new DOMDocument();
+ if ($feed->loadXML($body)) {
+ $this->uri = $sourceurl;
+ $this->type = $type;
+ $this->feed = $feed;
+ return $this->uri;
+ } else {
+ throw new FeedSubBadXmlException($url);
+ }
+ }
+
/**
* @param string $url source URL, used to resolve relative links
* @param string $body HTML body text
diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php
deleted file mode 100644
index e8c46de90..000000000
--- a/plugins/OStatus/lib/feedmunger.php
+++ /dev/null
@@ -1,350 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, 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/>.
- */
-
-/**
- * @package FeedSubPlugin
- * @maintainer Brion Vibber <brion@status.net>
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-
-class FeedSubPreviewNotice extends Notice
-{
- protected $fetched = true;
-
- function __construct($profile)
- {
- $this->profile = $profile;
- $this->profile_id = 0;
- }
-
- function getProfile()
- {
- return $this->profile;
- }
-
- function find()
- {
- return true;
- }
-
- function fetch()
- {
- $got = $this->fetched;
- $this->fetched = false;
- return $got;
- }
-}
-
-class FeedSubPreviewProfile extends Profile
-{
- function getAvatar($width, $height=null)
- {
- return new FeedSubPreviewAvatar($width, $height, $this->avatar);
- }
-}
-
-class FeedSubPreviewAvatar extends Avatar
-{
- function __construct($width, $height, $remote)
- {
- $this->remoteImage = $remote;
- }
-
- function displayUrl() {
- return $this->remoteImage;
- }
-}
-
-class FeedMunger
-{
- /**
- * @param XML_Feed_Parser $feed
- */
- function __construct($feed, $url=null)
- {
- $this->feed = $feed;
- $this->url = $url;
- }
-
- function ostatusProfile()
- {
- $profile = new Ostatus_profile();
- $profile->feeduri = $this->url;
- $profile->homeuri = $this->feed->link;
- $profile->huburi = $this->getHubLink();
- $salmon = $this->getSalmonLink();
- if ($salmon) {
- $profile->salmonuri = $salmon;
- }
- return $profile;
- }
-
- function getAtomLink($item, $attribs=array())
- {
- // XML_Feed_Parser gets confused by multiple <link> elements.
- $dom = $item->model;
-
- // Note that RSS feeds would embed an <atom:link> so this should work for both.
- /// http://code.google.com/p/pubsubhubbub/wiki/RssFeeds
- // <link rel='hub' href='http://pubsubhubbub.appspot.com/'/>
- $links = $dom->getElementsByTagNameNS('http://www.w3.org/2005/Atom', 'link');
- for ($i = 0; $i < $links->length; $i++) {
- $node = $links->item($i);
- if ($node->hasAttributes()) {
- $href = $node->attributes->getNamedItem('href');
- if ($href) {
- $matches = 0;
- foreach ($attribs as $name => $val) {
- $attrib = $node->attributes->getNamedItem($name);
- if ($attrib && $attrib->value == $val) {
- $matches++;
- }
- }
- if ($matches == count($attribs)) {
- return $href->value;
- }
- }
- }
- }
- return false;
- }
-
- function getRssLink($item)
- {
- // XML_Feed_Parser gets confused by multiple <link> elements.
- $dom = $item->model;
-
- // Note that RSS feeds would embed an <atom:link> so this should work for both.
- /// http://code.google.com/p/pubsubhubbub/wiki/RssFeeds
- // <link rel='hub' href='http://pubsubhubbub.appspot.com/'/>
- $links = $dom->getElementsByTagName('link');
- for ($i = 0; $i < $links->length; $i++) {
- $node = $links->item($i);
- if (!$node->hasAttributes()) {
- return $node->textContent;
- }
- }
- return false;
- }
-
- function getAltLink($item)
- {
- // Check for an atom link...
- $link = $this->getAtomLink($item, array('rel' => 'alternate', 'type' => 'text/html'));
- if (!$link) {
- $link = $this->getRssLink($item);
- }
- return $link;
- }
-
- function getHubLink()
- {
- return $this->getAtomLink($this->feed, array('rel' => 'hub'));
- }
-
- function getSalmonLink()
- {
- return $this->getAtomLink($this->feed, array('rel' => 'salmon'));
- }
-
- function getSelfLink()
- {
- return $this->getAtomLink($this->feed, array('rel' => 'self'));
- }
-
- /**
- * Get an appropriate avatar image source URL, if available.
- * @return mixed string or false
- */
- function getAvatar()
- {
- $logo = $this->feed->logo;
- if ($logo) {
- return $logo;
- }
- $icon = $this->feed->icon;
- if ($icon) {
- return $icon;
- }
- return common_path('plugins/OStatus/images/48px-Feed-icon.svg.png');
- }
-
- function profile($preview=false)
- {
- if ($preview) {
- $profile = new FeedSubPreviewProfile();
- } else {
- $profile = new Profile();
- }
-
- // @todo validate/normalize nick?
- $profile->nickname = $this->feed->title;
- $profile->fullname = $this->feed->title;
- $profile->homepage = $this->getAltLink($this->feed);
- $profile->bio = $this->feed->description;
- $profile->profileurl = $this->getAltLink($this->feed);
-
- if ($preview) {
- $profile->avatar = $this->getAvatar();
- }
-
- // @todo tags from categories
- // @todo lat/lon/location?
-
- return $profile;
- }
-
- function notice($index=1, $preview=false)
- {
- $entry = $this->feed->getEntryByOffset($index);
- if (!$entry) {
- return null;
- }
-
- if ($preview) {
- $notice = new FeedSubPreviewNotice($this->profile(true));
- $notice->id = -1;
- } else {
- $notice = new Notice();
- $notice->profile_id = $this->profileIdForEntry($index);
- }
-
- $link = $this->getAltLink($entry);
- if (empty($link)) {
- if (preg_match('!^https?://!', $entry->id)) {
- $link = $entry->id;
- common_log(LOG_DEBUG, "No link on entry, using URL from id: $link");
- }
- }
- $notice->uri = $link;
- $notice->url = $link;
- $notice->content = $this->noticeFromEntry($entry);
- $notice->rendered = common_render_content($notice->content, $notice); // @fixme this is failing on group posts
- $notice->created = common_sql_date($entry->updated); // @fixme
- $notice->is_local = Notice::GATEWAY;
- $notice->source = 'feed';
-
- $location = $this->getLocation($entry);
- if ($location) {
- if ($location->location_id) {
- $notice->location_ns = $location->location_ns;
- $notice->location_id = $location->location_id;
- }
- $notice->lat = $location->lat;
- $notice->lon = $location->lon;
- }
-
- return $notice;
- }
-
- function profileIdForEntry($index=1)
- {
- // hack hack hack
- // should get profile for this entry's author...
- $feeduri = $this->getSelfLink();
- $remote = Ostatus_profile::staticGet('feeduri', $feeduri);
- if ($remote) {
- return $remote->profile_id;
- } else {
- throw new Exception("Can't find feed profile for $feeduri");
- }
- }
-
- /**
- * Parse location given as a GeoRSS-simple point, if provided.
- * http://www.georss.org/simple
- *
- * @param feed item $entry
- * @return mixed Location or false
- */
- function getLocation($entry)
- {
- $dom = $entry->model;
- $points = $dom->getElementsByTagNameNS('http://www.georss.org/georss', 'point');
-
- for ($i = 0; $i < $points->length; $i++) {
- $point = $points->item(0)->textContent;
- $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace"
- $point = preg_replace('/\s+/', ' ', $point);
- $point = trim($point);
- $coords = explode(' ', $point);
- if (count($coords) == 2) {
- list($lat, $lon) = $coords;
- if (is_numeric($lat) && is_numeric($lon)) {
- common_log(LOG_INFO, "Looking up location for $lat $lon from georss");
- return Location::fromLatLon($lat, $lon);
- }
- }
- common_log(LOG_ERR, "Ignoring bogus georss:point value $point");
- }
-
- return false;
- }
-
- /**
- * @param XML_Feed_Type $entry
- * @return string notice text, within post size limit
- */
- function noticeFromEntry($entry)
- {
- $max = Notice::maxContent();
- $ellipsis = "\xe2\x80\xa6"; // U+2026 HORIZONTAL ELLIPSIS
- $title = $entry->title;
- $link = $entry->link;
-
- // @todo We can get <category> entries like this:
- // $cats = $entry->getCategory('category', array(0, true));
- // but it feels like an awful hack. If it's accessible cleanly,
- // try adding #hashtags from the categories/tags on a post.
-
- $title = $entry->title;
- $link = $this->getAltLink($entry);
- if ($link) {
- // Blog post or such...
- // @todo Should we force a language here?
- $format = _m('New post: "%1$s" %2$s');
- $out = sprintf($format, $title, $link);
-
- // Trim link if needed...
- if (mb_strlen($out) > $max) {
- $link = common_shorten_url($link);
- $out = sprintf($format, $title, $link);
- }
-
- // Trim title if needed...
- if (mb_strlen($out) > $max) {
- $used = mb_strlen($out) - mb_strlen($title);
- $available = $max - $used - mb_strlen($ellipsis);
- $title = mb_substr($title, 0, $available) . $ellipsis;
- $out = sprintf($format, $title, $link);
- }
- } else {
- // No link? Consider a bare status update.
- if (mb_strlen($title) > $max) {
- $available = $max - mb_strlen($ellipsis);
- $out = mb_substr($title, 0, $available) . $ellipsis;
- } else {
- $out = $title;
- }
- }
-
- return $out;
- }
-}
diff --git a/plugins/OStatus/lib/hubverifyqueuehandler.php b/plugins/OStatus/lib/hubverifyqueuehandler.php
index 125d13a77..7ce9e1431 100644
--- a/plugins/OStatus/lib/hubverifyqueuehandler.php
+++ b/plugins/OStatus/lib/hubverifyqueuehandler.php
@@ -33,13 +33,14 @@ class HubVerifyQueueHandler extends QueueHandler
{
$sub = $data['sub'];
$mode = $data['mode'];
+ $token = $data['token'];
assert($sub instanceof HubSub);
assert($mode === 'subscribe' || $mode === 'unsubscribe');
common_log(LOG_INFO, __METHOD__ . ": $mode $sub->callback $sub->topic");
try {
- $sub->verify($mode);
+ $sub->verify($mode, $token);
} catch (Exception $e) {
common_log(LOG_ERR, "Failed PuSH $mode verify to $sub->callback for $sub->topic: " .
$e->getMessage());
diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php
index 8c77222a6..df17a7006 100644
--- a/plugins/OStatus/lib/salmon.php
+++ b/plugins/OStatus/lib/salmon.php
@@ -41,9 +41,12 @@ class Salmon
$client->setBody($xml);
$response = $client->post($endpoint_uri, $headers);
} catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_ERR, "Salmon post to $endpoint_uri failed: " . $e->getMessage());
return false;
}
if ($response->getStatus() != 200) {
+ common_log(LOG_ERR, "Salmon at $endpoint_uri returned status " .
+ $response->getStatus() . ': ' . $response->getBody());
return false;
}
diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php
new file mode 100644
index 000000000..4e5ed7fe6
--- /dev/null
+++ b/plugins/OStatus/lib/salmonaction.php
@@ -0,0 +1,209 @@
+<?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/>.
+ */
+
+/**
+ * @package OStatusPlugin
+ * @author James Walker <james@status.net>
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class SalmonAction extends Action
+{
+ var $xml = null;
+ var $activity = null;
+
+ function prepare($args)
+ {
+ StatusNet::setApi(true); // Send smaller error pages
+
+ parent::prepare($args);
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->clientError(_('This method requires a POST.'));
+ }
+
+ if ($_SERVER['CONTENT_TYPE'] != 'application/atom+xml') {
+ $this->clientError(_('Salmon requires application/atom+xml'));
+ }
+
+ $xml = file_get_contents('php://input');
+
+ $dom = DOMDocument::loadXML($xml);
+
+ if ($dom->documentElement->namespaceURI != Activity::ATOM ||
+ $dom->documentElement->localName != 'entry') {
+ common_log(LOG_DEBUG, "Got invalid Salmon post: $xml");
+ $this->clientError(_m('Salmon post must be an Atom entry.'));
+ }
+ // XXX: check the signature
+
+ $this->act = new Activity($dom->documentElement);
+ return true;
+ }
+
+ /**
+ * Check the posted activity type and break out to appropriate processing.
+ */
+
+ function handle($args)
+ {
+ StatusNet::setApi(true); // Send smaller error pages
+
+ // TODO : Insert new $xml -> notice code
+
+ if (Event::handle('StartHandleSalmon', array($this->activity))) {
+ switch ($this->act->verb)
+ {
+ case ActivityVerb::POST:
+ $this->handlePost();
+ break;
+ case ActivityVerb::SHARE:
+ $this->handleShare();
+ break;
+ case ActivityVerb::FAVORITE:
+ $this->handleFavorite();
+ break;
+ case ActivityVerb::UNFAVORITE:
+ $this->handleUnfavorite();
+ break;
+ case ActivityVerb::FOLLOW:
+ case ActivityVerb::FRIEND:
+ $this->handleFollow();
+ break;
+ case ActivityVerb::UNFOLLOW:
+ $this->handleUnfollow();
+ break;
+ case ActivityVerb::JOIN:
+ $this->handleJoin();
+ break;
+ default:
+ throw new ClientException(_("Unimplemented."));
+ }
+ Event::handle('EndHandleSalmon', array($this->activity));
+ }
+ }
+
+ function handlePost()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ function handleFollow()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ function handleUnfollow()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ function handleFavorite()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ /**
+ * Remote user doesn't like one of our posts after all!
+ * Confirm the post is ours, and delete a local favorite event.
+ */
+
+ function handleUnfavorite()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ /**
+ * Hmmmm
+ */
+ function handleShare()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ /**
+ * Hmmmm
+ */
+ function handleJoin()
+ {
+ throw new ClientException(_("Unimplemented!"));
+ }
+
+ /**
+ * @return Ostatus_profile
+ */
+ function ensureProfile()
+ {
+ $actor = $this->act->actor;
+ if (empty($actor->id)) {
+ common_log(LOG_ERR, "broken actor: " . var_export($actor, true));
+ throw new Exception("Received a salmon slap from unidentified actor.");
+ }
+
+ return Ostatus_profile::ensureActorProfile($this->act);
+ }
+
+ 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;
+
+ $rendered = HTMLPurifier::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);
+
+ 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_time($this->act->time);
+ }
+
+ return Notice::saveNew($oprofile->profile_id,
+ $content,
+ 'ostatus+salmon',
+ $options);
+ }
+}
diff --git a/plugins/OStatus/tests/ActivityParseTests.php b/plugins/OStatus/tests/ActivityParseTests.php
index fa8bcdda2..d7305dede 100644
--- a/plugins/OStatus/tests/ActivityParseTests.php
+++ b/plugins/OStatus/tests/ActivityParseTests.php
@@ -22,8 +22,15 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
$act = new Activity($dom->documentElement);
$this->assertFalse(empty($act));
+
$this->assertEquals($act->time, 1243860840);
$this->assertEquals($act->verb, ActivityVerb::POST);
+
+ $this->assertFalse(empty($act->object));
+ $this->assertEquals($act->object->title, 'Punctuation Changeset');
+ $this->assertEquals($act->object->type, 'http://versioncentral.example.org/activity/changeset');
+ $this->assertEquals($act->object->summary, 'Fixing punctuation because it makes it more readable.');
+ $this->assertEquals($act->object->id, 'tag:versioncentral.example.org,2009:/change/1643245');
}
public function testExample3()
@@ -57,12 +64,39 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
$this->assertEquals($act->object->summary, 'Some text.');
$this->assertEquals($act->object->link, 'http://example.org/2003/12/13/atom03.html');
- $this->assertTrue(empty($act->context));
+ $this->assertFalse(empty($act->context));
+
$this->assertTrue(empty($act->target));
$this->assertEquals($act->entry, $entry);
$this->assertEquals($act->feed, $feed);
}
+
+ public function testExample4()
+ {
+ global $_example4;
+ $dom = DOMDocument::loadXML($_example4);
+
+ $entry = $dom->documentElement;
+
+ $act = new Activity($entry);
+
+ $this->assertFalse(empty($act));
+ $this->assertEquals(1266547958, $act->time);
+ $this->assertEquals('http://example.net/notice/14', $act->link);
+
+ $this->assertFalse(empty($act->context));
+ $this->assertEquals('http://example.net/notice/12', $act->context->replyToID);
+ $this->assertEquals('http://example.net/notice/12', $act->context->replyToUrl);
+ $this->assertEquals('http://example.net/conversation/11', $act->context->conversation);
+ $this->assertEquals(array('http://example.net/user/1'), $act->context->attention);
+
+ $this->assertFalse(empty($act->object));
+ $this->assertEquals($act->object->content,
+ '@<span class="vcard"><a href="http://example.net/user/1" class="url"><span class="fn nickname">evan</span></a></span> now is the time for all good men to come to the aid of their country. #<span class="tag"><a href="http://example.net/tag/thetime" rel="tag">thetime</a></span>');
+
+ $this->assertFalse(empty($act->actor));
+ }
}
$_example1 = <<<EXAMPLE1
@@ -145,3 +179,31 @@ $_example3 = <<<EXAMPLE3
</feed>
EXAMPLE3;
+
+$_example4 = <<<EXAMPLE4
+<?xml version='1.0' encoding='UTF-8'?>
+<entry 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:ostatus="http://ostatus.org/schema/1.0">
+ <title>@evan now is the time for all good men to come to the aid of their country. #thetime</title>
+ <summary>@evan now is the time for all good men to come to the aid of their country. #thetime</summary>
+<author>
+ <name>spock</name>
+ <uri>http://example.net/user/2</uri>
+</author>
+<activity:actor>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
+ <id>http://example.net/user/2</id>
+ <title>spock</title>
+ <link type="image/png" rel="avatar" href="http://example.net/theme/identica/default-avatar-profile.png"></link>
+</activity:actor>
+ <link rel="alternate" type="text/html" href="http://example.net/notice/14"/>
+ <id>http://example.net/notice/14</id>
+ <published>2010-02-19T02:52:38+00:00</published>
+ <updated>2010-02-19T02:52:38+00:00</updated>
+ <link rel="related" href="http://example.net/notice/12"/>
+ <thr:in-reply-to ref="http://example.net/notice/12" href="http://example.net/notice/12"></thr:in-reply-to>
+ <link rel="ostatus:conversation" href="http://example.net/conversation/11"/>
+ <link rel="ostatus:attention" href="http://example.net/user/1"/>
+ <content type="html">@&lt;span class=&quot;vcard&quot;&gt;&lt;a href=&quot;http://example.net/user/1&quot; class=&quot;url&quot;&gt;&lt;span class=&quot;fn nickname&quot;&gt;evan&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; now is the time for all good men to come to the aid of their country. #&lt;span class=&quot;tag&quot;&gt;&lt;a href=&quot;http://example.net/tag/thetime&quot; rel=&quot;tag&quot;&gt;thetime&lt;/a&gt;&lt;/span&gt;</content>
+ <category term="thetime"></category>
+</entry>
+EXAMPLE4;
diff --git a/plugins/OStatus/tests/FeedMungerTest.php b/plugins/OStatus/tests/FeedMungerTest.php
deleted file mode 100644
index 0ce24c9fb..000000000
--- a/plugins/OStatus/tests/FeedMungerTest.php
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-
-if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
- print "This script must be run from the command line\n";
- exit();
-}
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
-define('STATUSNET', true);
-define('LACONICA', true);
-
-require_once INSTALLDIR . '/lib/common.php';
-require_once INSTALLDIR . '/plugins/FeedSub/feedsub.php';
-
-require_once 'XML/Feed/Parser.php';
-
-class FeedMungerTest extends PHPUnit_Framework_TestCase
-{
- /**
- * @dataProvider profileProvider
- *
- */
- public function testProfiles($xml, $expected)
- {
- $feed = new XML_Feed_Parser($xml, false, false, true);
-
- $munger = new FeedMunger($feed);
- $profile = $munger->profile();
-
- foreach ($expected as $field => $val) {
- $this->assertEquals($expected[$field], $profile->$field, "profile->$field");
- }
- }
-
- static public function profileProvider()
- {
- return array(
- array(self::samplefeed(),
- array('nickname' => 'leŭksman', // @todo does this need to be asciified?
- 'fullname' => 'leŭksman',
- 'bio' => 'reticula, electronica, & oddities',
- 'homepage' => 'http://leuksman.com/log')));
- }
-
- /**
- * @dataProvider noticeProvider
- *
- */
- public function testNotices($xml, $entryIndex, $expected)
- {
- $feed = new XML_Feed_Parser($xml, false, false, true);
- $entry = $feed->getEntryByOffset($entryIndex);
-
- $munger = new FeedMunger($feed);
- $notice = $munger->noticeFromEntry($entry);
-
- $this->assertTrue(mb_strlen($notice) <= Notice::maxContent());
- $this->assertEquals($expected, $notice);
- }
-
- static public function noticeProvider()
- {
- return array(
- array('<rss version="2.0"><channel><item><title>A fairly short title</title><link>http://example.com/short/link</link></item></channel></rss>', 0,
- 'New post: "A fairly short title" http://example.com/short/link'),
- // Requires URL shortening ...
- array('<rss version="2.0"><channel><item><title>A fairly short title</title><link>http://example.com/but/a/very/long/link/indeed/this/is/far/too/long/for/mere/humans/to/comprehend/oh/my/gosh</link></item></channel></rss>', 0,
- 'New post: "A fairly short title" http://ur1.ca/g2o1'),
- array('<rss version="2.0"><channel><item><title>A fairly long title in this case, which will have to get cut down at some point alongside its very long link. Really who even makes titles this long? It\'s just ridiculous imo...</title><link>http://example.com/but/a/very/long/link/indeed/this/is/far/too/long/for/mere/humans/to/comprehend/oh/my/gosh</link></item></channel></rss>', 0,
- 'New post: "A fairly long title in this case, which will have to get cut down at some point alongside its very long li…" http://ur1.ca/g2o1'),
- // Some real sample feeds
- array(self::samplefeed(), 0,
- 'New post: "Compiling PHP on Snow Leopard" http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/'),
- array(self::samplefeedBlogspot(), 0,
- 'New post: "I love posting" http://briontest.blogspot.com/2009/11/i-love-posting.html'),
- array(self::samplefeedBlogspot(), 1,
- 'New post: "Hey dude" http://briontest.blogspot.com/2009/11/hey-dude.html'),
- );
- }
-
- static protected function samplefeed()
- {
- $xml = '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
- $samplefeed = $xml . <<<END
-<rss version="2.0"
- xmlns:content="http://purl.org/rss/1.0/modules/content/"
- xmlns:wfw="http://wellformedweb.org/CommentAPI/"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:atom="http://www.w3.org/2005/Atom"
- xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
- xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
- >
-
-<channel>
- <title>leŭksman</title>
- <atom:link href="http://leuksman.com/log/feed/" rel="self" type="application/rss+xml" />
- <link>http://leuksman.com/log</link>
- <description>reticula, electronica, &#38; oddities</description>
-
- <lastBuildDate>Thu, 12 Nov 2009 17:44:42 +0000</lastBuildDate>
- <generator>http://wordpress.org/?v=2.8.6</generator>
- <language>en</language>
- <sy:updatePeriod>hourly</sy:updatePeriod>
- <sy:updateFrequency>1</sy:updateFrequency>
- <item>
-
- <title>Compiling PHP on Snow Leopard</title>
- <link>http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/</link>
- <comments>http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/#comments</comments>
- <pubDate>Thu, 12 Nov 2009 17:44:42 +0000</pubDate>
- <dc:creator>brion</dc:creator>
- <category><![CDATA[apple]]></category>
-
- <category><![CDATA[devel]]></category>
-
- <guid isPermaLink="false">http://leuksman.com/log/?p=649</guid>
- <description><![CDATA[If you&#8217;ve been having trouble compiling your own PHP installations on Mac OS X 10.6, here&#8217;s the secret to making it not suck! After running the configure script, edit the generated Makefile and make these fixes:
-
-Find the EXTRA_LIBS definition and add -lresolv to the end
-Find the EXE_EXT definition and remove .dSYM
-
-Standard make and make install [...]]]></description>
- <content:encoded><![CDATA[<p>If you&#8217;ve been having trouble compiling your own PHP installations on Mac OS X 10.6, here&#8217;s the secret to making it not suck! After running the configure script, edit the generated Makefile and make these fixes:</p>
-<ul>
-<li>Find the <strong>EXTRA_LIBS</strong> definition and add <strong>-lresolv</strong> to the end</li>
-<li>Find the <strong>EXE_EXT</strong> definition and remove <strong>.dSYM</strong></li>
-</ul>
-<p>Standard make and make install should work from here&#8230;</p>
-<p>For reference, here&#8217;s the whole configure line I currently use; MySQL is installed from the downloadable installer; other deps from MacPorts:</p>
-<p>&#8216;./configure&#8217; &#8216;&#8211;prefix=/opt/php52&#8242; &#8216;&#8211;with-mysql=/usr/local/mysql&#8217; &#8216;&#8211;with-zlib&#8217; &#8216;&#8211;with-bz2&#8242; &#8216;&#8211;enable-mbstring&#8217; &#8216;&#8211;enable-exif&#8217; &#8216;&#8211;enable-fastcgi&#8217; &#8216;&#8211;with-xmlrpc&#8217; &#8216;&#8211;with-xsl&#8217; &#8216;&#8211;with-readline=/opt/local&#8217; &#8211;without-iconv &#8211;with-gd &#8211;with-png-dir=/opt/local &#8211;with-jpeg-dir=/opt/local &#8211;with-curl &#8211;with-gettext=/opt/local &#8211;with-mysqli=/usr/local/mysql/bin/mysql_config &#8211;with-tidy=/opt/local &#8211;enable-pcntl &#8211;with-openssl</p>
-]]></content:encoded>
- <wfw:commentRss>http://leuksman.com/log/2009/11/12/compiling-php-on-snow-leopard/feed/</wfw:commentRss>
- <slash:comments>0</slash:comments>
- </item>
-</channel>
-</rss>
-END;
- return $samplefeed;
- }
-
- static protected function samplefeedBlogspot()
- {
- return <<<END
-<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss'><id>tag:blogger.com,1999:blog-7780083508531697167</id><updated>2009-11-19T12:56:11.233-08:00</updated><title type='text'>Brion's Cool Test Blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://briontest.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7780083508531697167/posts/default'/><link rel='alternate' type='text/html' href='http://briontest.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>brion</name><uri>http://www.blogger.com/profile/12932299467049762017</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>2</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7780083508531697167.post-8456671879000290677</id><published>2009-11-19T12:55:00.000-08:00</published><updated>2009-11-19T12:56:11.241-08:00</updated><title type='text'>I love posting</title><content type='html'>It's pretty awesome, if you like that sort of thing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7780083508531697167-8456671879000290677?l=briontest.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://briontest.blogspot.com/feeds/8456671879000290677/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://briontest.blogspot.com/2009/11/i-love-posting.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7780083508531697167/posts/default/8456671879000290677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7780083508531697167/posts/default/8456671879000290677'/><link rel='alternate' type='text/html' href='http://briontest.blogspot.com/2009/11/i-love-posting.html' title='I love posting'/><author><name>brion</name><uri>http://www.blogger.com/profile/12932299467049762017</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05912464053145602436'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7780083508531697167.post-8202296917897346633</id><published>2009-11-18T13:52:00.001-08:00</published><updated>2009-11-18T13:52:48.444-08:00</updated><title type='text'>Hey dude</title><content type='html'>testingggggggggg&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7780083508531697167-8202296917897346633?l=briontest.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://briontest.blogspot.com/feeds/8202296917897346633/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://briontest.blogspot.com/2009/11/hey-dude.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7780083508531697167/posts/default/8202296917897346633'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7780083508531697167/posts/default/8202296917897346633'/><link rel='alternate' type='text/html' href='http://briontest.blogspot.com/2009/11/hey-dude.html' title='Hey dude'/><author><name>brion</name><uri>http://www.blogger.com/profile/12932299467049762017</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05912464053145602436'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry></feed>
-END;
- }
-}