* @link http://status.net/
*/
-if (!defined('STATUSNET')
-{
+if (!defined('STATUSNET')) {
exit(1);
}
*
* @return void
*/
- function validate
+ function validate()
{
}
$this->authors = array();
$this->links = array();
$this->entries = array();
- $this->addNamespace('xmlns', 'http://www.w3.org/2005/Atom');
+ $this->addNamespace('', 'http://www.w3.org/2005/Atom');
}
/**
{
$this->xw->startDocument('1.0', 'UTF-8');
$commonAttrs = array('xml:lang' => 'en-US');
- $commonAttrs = array_merge($commonAttrs, $this->namespaces);
+ foreach ($this->namespaces as $prefix => $uri) {
+ if ($prefix == '') {
+ $attr = 'xmlns';
+ } else {
+ $attr = 'xmlns:' . $prefix;
+ }
+ $commonAttrs[$attr] = $uri;
+ }
$this->elementStart('feed', $commonAttrs);
$this->element('id', null, $this->id);
// Feeds containing notice info use these namespaces
$this->addNamespace(
- 'xmlns:thr',
+ 'thr',
'http://purl.org/syndication/thread/1.0'
);
$this->addNamespace(
- 'xmlns:georss',
+ 'georss',
'http://www.georss.org/georss'
);
$this->addNamespace(
- 'xmlns:activity',
+ 'activity',
'http://activitystrea.ms/spec/1.0/'
);
// XXX: What should the uri be?
$this->addNamespace(
- 'xmlns:ostatus',
+ 'ostatus',
'http://ostatus.org/schema/1.0'
);
}
* 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;
{
$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') {
}
/**
- * Garbage collect unused feeds on unsubscribe
+ * Notify remote server when one of our users subscribes.
+ * @fixme Check and restart the PuSH subscription if needed
+ *
+ * @param User $user
+ * @param Profile $other
+ * @return hook return value
+ */
+ function onEndSubscribe($user, $other)
+ {
+ $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
+ if ($oprofile) {
+ // Notify the remote server of the unsub, if supported.
+ $oprofile->notify($user->getProfile(), ActivityVerb::FOLLOW, $oprofile);
+ }
+ return true;
+ }
+
+ /**
+ * 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)
{
- $profile = Ostatus_profile::staticGet('profile_id', $other->id);
- if ($feed) {
+ $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
+ if ($oprofile) {
+ // Notify the remote server of the unsub, if supported.
+ $oprofile->notify($user->getProfile(), ActivityVerb::UNFOLLOW, $oprofile);
+
+ // Drop the PuSH subscription if there are no other subscribers.
$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();
+ common_log(LOG_INFO, "Unsubscribing from now-unused feed $oprofile->feeduri on hub $oprofile->huburi");
+ $oprofile->unsubscribe();
}
}
return true;
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') {
if ($profile->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) {
{
$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)) {
$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);
// @fixme check errors ;)
- $data = array('sub' => $sub, 'mode' => 'subscribe');
+ $data = array('sub' => $sub, 'mode' => 'subscribe', 'token' => $token);
$qm = QueueManager::get();
$qm->enqueue($data, 'hubverify');
* 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()
{
$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 {
function prepare($args)
{
+ parent::prepare($args);
+
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
$this->clientError(_('This method requires a POST.'));
}
public $topic;
public $callback;
public $secret;
- public $verify_token;
public $challenge;
public $lease;
public $sub_start;
'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,
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',
/**
* 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');
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
} else {
$this->sub_end = null;
}
- $this->lastupdate = common_sql_date();
+ $this->lastupdate = common_sql_now();
return $this->update($original);
}
{
$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();
+ // @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);
}
':' . $actor->id .
':' . time(); // @fixme
- $entry = new Atom10Entry();
+ //$entry = new Atom10Entry();
+ $entry = new XMLStringer();
$entry->elementStart('entry');
$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(time()));
$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->initFeed();
$feed->addEntry($entry);
- $feed->renderEntries();
- $feed->endFeed();
+ #$feed->renderEntries();
+ #$feed->endFeed();
$xml = $feed->getString();
common_log(LOG_INFO, "Posting to Salmon endpoint $salmon: $xml");
$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');
$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'));
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';
}
/**
{
$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());