From bc4e843f396dc450b04b612e7de14246084469d1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Feb 2010 19:22:55 -0800 Subject: Disable deprecated 'since' parameter on public_timeline API; causes performance problems. (since_id will work cleanly) --- actions/apitimelinepublic.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index 3f4a46c0f..0fb0788e9 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -74,6 +74,10 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction parent::prepare($args); $this->notices = $this->getNotices(); + + if ($this->since) { + throw new ServerException("since parameter is disabled for performance; use since_id", 403); + } return true; } @@ -145,7 +149,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction $notice = Notice::publicStream( ($this->page - 1) * $this->count, $this->count, $this->since_id, - $this->max_id, $this->since + $this->max_id ); while ($notice->fetch()) { -- cgit v1.2.3-54-g00ecf From b56b154b51ede363ee8e49ec5b9b9332b8df923c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 8 Feb 2010 21:52:05 -0800 Subject: Better checking for duplicate app names --- actions/editapplication.php | 2 +- actions/newapplication.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index ca5dba1e4..64cf0a574 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -277,7 +277,7 @@ class EditApplicationAction extends OwnerDesignAction function nameExists($name) { $newapp = Oauth_application::staticGet('name', $name); - if (!$newapp) { + if (empty($newapp)) { return false; } else { return $newapp->id != $this->app->id; diff --git a/actions/newapplication.php b/actions/newapplication.php index c0c520797..0f819b349 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -290,7 +290,7 @@ class NewApplicationAction extends OwnerDesignAction function nameExists($name) { $app = Oauth_application::staticGet('name', $name); - return ($app !== false); + return !empty($app); } } -- cgit v1.2.3-54-g00ecf From 70d5f39ed66cb277e925ea71dcc24ca580110b3d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 8 Feb 2010 21:52:05 -0800 Subject: Better checking for duplicate app names --- actions/editapplication.php | 2 +- actions/newapplication.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index ca5dba1e4..64cf0a574 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -277,7 +277,7 @@ class EditApplicationAction extends OwnerDesignAction function nameExists($name) { $newapp = Oauth_application::staticGet('name', $name); - if (!$newapp) { + if (empty($newapp)) { return false; } else { return $newapp->id != $this->app->id; diff --git a/actions/newapplication.php b/actions/newapplication.php index c0c520797..0f819b349 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -290,7 +290,7 @@ class NewApplicationAction extends OwnerDesignAction function nameExists($name) { $app = Oauth_application::staticGet('name', $name); - return ($app !== false); + return !empty($app); } } -- cgit v1.2.3-54-g00ecf From 841981a38140b793b7e950a0d2e057c720816ccb Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Feb 2010 01:37:45 -0500 Subject: discovery piece - hand merged :P --- plugins/OStatus/OStatusPlugin.php | 37 +++++- plugins/OStatus/actions/hostmeta.php | 42 ++++++ plugins/OStatus/actions/ostatusinit.php | 128 ++++++++++++++++++ plugins/OStatus/actions/ostatussub.php | 226 ++++++++++++++++++++++++++++++++ plugins/OStatus/actions/webfinger.php | 70 ++++++++++ plugins/OStatus/lib/Webfinger.php | 139 ++++++++++++++++++++ plugins/OStatus/lib/XRD.php | 183 ++++++++++++++++++++++++++ 7 files changed, 824 insertions(+), 1 deletion(-) create mode 100644 plugins/OStatus/actions/hostmeta.php create mode 100644 plugins/OStatus/actions/ostatusinit.php create mode 100644 plugins/OStatus/actions/ostatussub.php create mode 100644 plugins/OStatus/actions/webfinger.php create mode 100644 plugins/OStatus/lib/Webfinger.php create mode 100644 plugins/OStatus/lib/XRD.php diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 4e8b892c6..ce33344d2 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -53,6 +53,19 @@ class OStatusPlugin extends Plugin */ function onRouterInitialized($m) { + $m->connect('.well-known/host-meta', + array('action' => 'hostmeta')); + $m->connect('main/webfinger', + array('action' => 'webfinger')); + $m->connect('main/ostatus', + array('action' => 'ostatusinit')); + $m->connect('main/ostatus?nickname=:nickname', + array('action' => 'ostatusinit'), array('nickname' => '[A-Za-z0-9_-]+')); + $m->connect('main/ostatussub', + array('action' => 'ostatussub')); + $m->connect('main/ostatussub', + array('action' => 'ostatussub'), array('feed' => '[A-Za-z0-9\.\/\:]+')); + $m->connect('main/push/hub', array('action' => 'pushhub')); $m->connect('main/push/callback/:feed', @@ -148,6 +161,28 @@ class OStatusPlugin extends Plugin return true; } + /** + * Add in an OStatus subscribe button + */ + function onStartProfilePageActionsElements($output, $profile) + { + $cur = common_current_user(); + + if (empty($cur)) { + // Add an OStatus subscribe + $output->elementStart('li', 'entity_subscribe'); + $url = common_local_url('ostatusinit', + array('nickname' => $profile->nickname)); + $output->element('a', array('href' => $url, + 'class' => 'entity_remote_subscribe'), + _('OStatus')); + + $output->elementEnd('li'); + } + } + + + function onCheckSchema() { // warning: the autoincrement doesn't seem to set. // alter table feedinfo change column id id int(11) not null auto_increment; @@ -155,5 +190,5 @@ class OStatusPlugin extends Plugin $schema->ensureTable('feedinfo', Feedinfo::schemaDef()); $schema->ensureTable('hubsub', HubSub::schemaDef()); return true; - } + } } diff --git a/plugins/OStatus/actions/hostmeta.php b/plugins/OStatus/actions/hostmeta.php new file mode 100644 index 000000000..850b8a0fe --- /dev/null +++ b/plugins/OStatus/actions/hostmeta.php @@ -0,0 +1,42 @@ +. + */ + +/** + * @package OStatusPlugin + * @maintainer James Walker + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class HostMetaAction extends Action +{ + + function handle() + { + parent::handle(); + + $w = new Webfinger(); + + + $domain = common_config('site', 'server'); + $url = common_local_url('webfinger'); + $url.= '?uri={uri}'; + print $w->getHostMeta($domain, $url); + } +} diff --git a/plugins/OStatus/actions/ostatusinit.php b/plugins/OStatus/actions/ostatusinit.php new file mode 100644 index 000000000..bac2c4d43 --- /dev/null +++ b/plugins/OStatus/actions/ostatusinit.php @@ -0,0 +1,128 @@ +. + */ + +/** + * @package OStatusPlugin + * @maintainer James Walker + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + + +class OStatusInitAction extends Action +{ + + var $nickname; + var $acct; + var $err; + + function prepare($args) + { + parent::prepare($args); + + if (common_logged_in()) { + $this->clientError(_('You can use the local subscription!')); + return false; + } + + $this->nickname = $this->trimmed('nickname'); + $this->acct = $this->trimmed('acct'); + + return true; + } + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + /* 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. '. + 'Try again, please.')); + return; + } + $this->ostatusConnect(); + } else { + $this->showForm(); + } + } + + function showForm($err = null) + { + $this->err = $err; + $this->showPage(); + + } + + function showContent() + { + $this->elementStart('form', array('id' => 'form_ostatus_connect', + 'method' => 'post', + 'class' => 'form_settings', + 'action' => common_local_url('ostatusinit'))); + $this->elementStart('fieldset'); + $this->element('legend', _('Subscribe to a remote user')); + $this->hidden('token', common_session_token()); + + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('User nickname'), $this->nickname, + _('Nickname of the user you want to follow')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->input('acct', _('Profile Account'), $this->acct, + _('Your account id (i.e. user@identi.ca)')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('submit', _('Subscribe')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function ostatusConnect() + { + $w = new Webfinger; + + $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! + + $user = User::staticGet('nickname', $this->nickname); + + $feed_url = common_local_url('ApiTimelineUser', + array('id' => $user->id, + 'format' => 'atom')); + $url = $w->applyTemplate($link['template'], $feed_url); + + common_redirect($url, 303); + } + + } + + } + + function title() + { + return _('OStatus Connect'); + } + +} \ No newline at end of file diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php new file mode 100644 index 000000000..ffc4ae8df --- /dev/null +++ b/plugins/OStatus/actions/ostatussub.php @@ -0,0 +1,226 @@ +. + */ + +/** + * @package OStatusPlugin + * @maintainer James Walker + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class OStatusSubAction extends Action +{ + + protected $feedurl; + + function title() + { + return _m("OStatus Subscribe"); + } + + function handle($args) + { + if ($this->validateFeed()) { + $this->showForm(); + } + + return true; + + } + + function showForm($err = null) + { + $this->err = $err; + $this->showPage(); + } + + + function showContent() + { + $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', + 'class' => 'form_settings', + 'action' => + common_local_url('feedsubsettings'))); + + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset', array('id' => 'settings_feeds')); + + $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->elementEnd('li'); + $this->elementEnd('ul'); + + $this->submit('subscribe', _m('Subscribe')); + + $this->elementEnd('fieldset'); + + $this->elementEnd('form'); + + $this->previewFeed(); + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + if ($this->arg('subscribe')) { + $this->saveFeed(); + } else { + $this->showForm(_('Unexpected form submission.')); + } + } + + + /** + * Set up and add a feed + * + * @return boolean true if feed successfully read + * Sends you back to input form if not. + */ + function validateFeed() + { + $feedurl = $this->trimmed('feed'); + + if ($feedurl == '') { + $this->showForm(_m('Empty feed URL!')); + return; + } + $this->feedurl = $feedurl; + + // Get the canonical feed URI and check it + try { + $discover = new FeedDiscovery(); + $uri = $discover->discoverFromURL($feedurl); + } catch (FeedSubBadURLException $e) { + $this->showForm(_m('Invalid URL or could not reach server.')); + return false; + } catch (FeedSubBadResponseException $e) { + $this->showForm(_m('Cannot read feed; server returned error.')); + return false; + } catch (FeedSubEmptyException $e) { + $this->showForm(_m('Cannot read feed; server returned an empty page.')); + return false; + } catch (FeedSubBadHTMLException $e) { + $this->showForm(_m('Bad HTML, could not find feed link.')); + return false; + } catch (FeedSubNoFeedException $e) { + $this->showForm(_m('Could not find a feed linked from this URL.')); + return false; + } catch (FeedSubUnrecognizedTypeException $e) { + $this->showForm(_m('Not a recognized feed type.')); + return false; + } catch (FeedSubException $e) { + // Any new ones we forgot about + $this->showForm(_m('Bad feed URL.')); + return false; + } + + $this->munger = $discover->feedMunger(); + $this->feedinfo = $this->munger->feedInfo(); + + if ($this->feedinfo->huburi == '') { + $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.')); + return false; + } + + return true; + } + + function saveFeed() + { + if ($this->validateFeed()) { + $this->preview = true; + $this->feedinfo = Feedinfo::ensureProfile($this->munger); + + // If not already in use, subscribe to updates via the hub + if ($this->feedinfo->sub_start) { + common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}"); + } else { + $ok = $this->feedinfo->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->feedinfo->getProfile(); + + if ($user->isSubscribed($profile)) { + $this->showForm(_m('Already subscribed!')); + } elseif ($user->subscribeTo($profile)) { + $this->showForm(_m('Feed subscribed!')); + } else { + $this->showForm(_m('Feed subscription failed!')); + } + } + } + + + function previewFeed() + { + $feedinfo = $this->munger->feedinfo(); + $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.'); + } + } + + +} \ No newline at end of file diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/webfinger.php new file mode 100644 index 000000000..ec2dddd53 --- /dev/null +++ b/plugins/OStatus/actions/webfinger.php @@ -0,0 +1,70 @@ +. + */ + +/** + * @package OStatusPlugin + * @maintainer James Walker + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class WebfingerAction extends Action +{ + + public $uri; + + function prepare($args) + { + parent::prepare($args); + + $this->uri = $this->trimmed('uri'); + + return true; + } + + function handle() + { + $acct = Webfinger::normalize($this->uri); + + $xrd = new XRD(); + + list($nick, $domain) = explode('@', urldecode($acct)); + $nick = common_canonical_nickname($nick); + + $this->user = User::staticGet('nickname', $nick); + if (!$this->user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $xrd->subject = $this->uri; + $xrd->alias[] = common_profile_url($nick); + $xrd->links[] = array('rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => common_profile_url($nick)); + // TODO - finalize where the redirect should go on the publisher + $url = common_local_url('ostatussub') . '?feed={uri}'; + $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe', + 'template' => $url ); + + header('Content-type: text/xml'); + print $xrd->toXML(); + } + +} diff --git a/plugins/OStatus/lib/Webfinger.php b/plugins/OStatus/lib/Webfinger.php new file mode 100644 index 000000000..7ab6b421b --- /dev/null +++ b/plugins/OStatus/lib/Webfinger.php @@ -0,0 +1,139 @@ +. + * + * @package StatusNet + * @author James Walker + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +define('WEBFINGER_SERVICE_REL_VALUE', 'lrdd'); + +/** + * Implement the webfinger protocol. + */ +class Webfinger +{ + /** + * Perform a webfinger lookup given an account. + */ + public function lookup($id) + { + $id = $this->normalize($id); + list($name, $domain) = explode('@', $id); + + $links = $this->getServiceLinks($domain); + if (!$links) { + return false; + } + + $services = array(); + foreach ($links as $link) { + if ($link['template']) { + return $this->getServiceDescription($link['template'], $id); + } + if ($link['href']) { + return $this->getServiceDescription($link['href'], $id); + } + } + } + + /** + * Normalize an account ID + */ + function normalize($id) + { + if (substr($id, 0, 7) == 'acct://') { + return substr($id, 7); + } else if (substr($id, 0, 5) == 'acct:') { + return substr($id, 5); + } + + return $id; + } + + function getServiceLinks($domain) + { + $url = 'http://'. $domain .'/.well-known/host-meta'; + $content = $this->fetchURL($url); + $result = XRD::parse($content); + + // Ensure that the host == domain (spec may include signing later) + if ($result->host != $domain) { + return false; + } + + $links = array(); + foreach ($result->links as $link) { + if ($link['rel'] == WEBFINGER_SERVICE_REL_VALUE) { + $links[] = $link; + } + + } + return $links; + } + + function getServiceDescription($template, $id) + { + $url = $this->applyTemplate($template, 'acct:' . $id); + + $content = $this->fetchURL($url); + + return XRD::parse($content); + } + + function fetchURL($url) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return $response->getBody(); + } + + function applyTemplate($template, $id) + { + $template = str_replace('{uri}', urlencode($id), $template); + + return $template; + } + + function getHostMeta($domain, $template) { + $xrd = new XRD(); + $xrd->host = $domain; + $xrd->links[] = array('rel' => 'lrdd', + 'template' => $template, + 'title' => array('Resource Descriptor')); + + return $xrd->toXML(); + } +} + + diff --git a/plugins/OStatus/lib/XRD.php b/plugins/OStatus/lib/XRD.php new file mode 100644 index 000000000..16d27f8eb --- /dev/null +++ b/plugins/OStatus/lib/XRD.php @@ -0,0 +1,183 @@ +. + * + * @package StatusNet + * @author James Walker + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + + +class XRD +{ + const XML_NS = 'http://www.w3.org/2000/xmlns/'; + + const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'; + + const HOST_META_NS = 'http://host-meta.net/xrd/1.0'; + + public $expires; + + public $subject; + + public $host; + + public $alias = array(); + + public $types = array(); + + public $links = array(); + + public static function parse($xml) + { + $xrd = new XRD(); + + $dom = new DOMDocument(); + $dom->loadXML($xml); + $xrd_element = $dom->getElementsByTagName('XRD')->item(0); + + // Check for host-meta host + $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; + if ($host) { + $xrd->host = $host; + } + + // Loop through other elements + foreach ($xrd_element->childNodes as $node) { + switch ($node->tagName) { + case 'Expires': + $xrd->expires = $node->nodeValue; + break; + case 'Subject': + $xrd->subject = $node->nodeValue; + break; + + case 'Alias': + $xrd->alias[] = $node->nodeValue; + break; + + case 'Link': + $xrd->links[] = $xrd->parseLink($node); + break; + + case 'Type': + $xrd->types[] = $xrd->parseType($node); + break; + + } + } + return $xrd; + } + + public function toXML() + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $xrd_dom = $dom->createElementNS(XRD::XRD_NS, 'XRD'); + $dom->appendChild($xrd_dom); + + if ($this->host) { + $host_dom = $dom->createElement('hm:Host', $this->host); + $xrd_dom->setAttributeNS(XRD::XML_NS, 'xmlns:hm', XRD::HOST_META_NS); + $xrd_dom->appendChild($host_dom); + } + + if ($this->expires) { + $expires_dom = $dom->createElement('Expires', $this->expires); + $xrd_dom->appendChild($expires_dom); + } + + if ($this->subject) { + $subject_dom = $dom->createElement('Subject', $this->subject); + $xrd_dom->appendChild($subject_dom); + } + + foreach ($this->alias as $alias) { + $alias_dom = $dom->createElement('Alias', $alias); + $xrd_dom->appendChild($alias_dom); + } + + foreach ($this->types as $type) { + $type_dom = $dom->createElement('Type', $type); + $xrd_dom->appendChild($type_dom); + } + + foreach ($this->links as $link) { + $link_dom = $this->saveLink($dom, $link); + $xrd_dom->appendChild($link_dom); + } + + return $dom->saveXML(); + } + + function parseType($element) + { + return array(); + } + + function parseLink($element) + { + $link = array(); + $link['rel'] = $element->getAttribute('rel'); + $link['type'] = $element->getAttribute('type'); + $link['href'] = $element->getAttribute('href'); + $link['template'] = $element->getAttribute('template'); + foreach ($element->childNodes as $node) { + switch($node->tagName) { + case 'Title': + $link['title'][] = $node->nodeValue; + } + } + + return $link; + } + + function saveLink($doc, $link) + { + $link_element = $doc->createElement('Link'); + if ($link['rel']) { + $link_element->setAttribute('rel', $link['rel']); + } + if ($link['type']) { + $link_element->setAttribute('type', $link['type']); + } + if ($link['href']) { + $link_element->setAttribute('href', $link['href']); + } + if ($link['template']) { + $link_element->setAttribute('template', $link['template']); + } + + if (is_array($link['title'])) { + foreach($link['title'] as $title) { + $title = $doc->createElement('Title', $title); + $link_element->appendChild($title); + } + } + + + return $link_element; + } +} + -- cgit v1.2.3-54-g00ecf From c2475f88539ba8053596acd17be39ad0aec98bb4 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Feb 2010 15:37:37 -0500 Subject: in progress Salmon responses --- plugins/OStatus/OStatusPlugin.php | 60 ++++++++++++++++++++++++++++++-- plugins/OStatus/actions/salmon.php | 49 +++++++++++++++++++++++++++ plugins/OStatus/actions/webfinger.php | 7 ++++ plugins/OStatus/lib/Salmon.php | 64 +++++++++++++++++++++++++++++++++++ plugins/OStatus/lib/Webfinger.php | 4 +++ 5 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 plugins/OStatus/actions/salmon.php create mode 100644 plugins/OStatus/lib/Salmon.php diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index ce33344d2..60a4e3827 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -53,6 +53,7 @@ class OStatusPlugin extends Plugin */ function onRouterInitialized($m) { + // Discovery actions $m->connect('.well-known/host-meta', array('action' => 'hostmeta')); $m->connect('main/webfinger', @@ -65,7 +66,8 @@ class OStatusPlugin extends Plugin array('action' => 'ostatussub')); $m->connect('main/ostatussub', array('action' => 'ostatussub'), array('feed' => '[A-Za-z0-9\.\/\:]+')); - + + // PuSH actions $m->connect('main/push/hub', array('action' => 'pushhub')); $m->connect('main/push/callback/:feed', @@ -73,6 +75,11 @@ class OStatusPlugin extends Plugin array('feed' => '[0-9]+')); $m->connect('settings/feedsub', array('action' => 'feedsubsettings')); + + // Salmon endpoint + $m->connect('salmon/user/:id', + array('action' => 'salmon'), + array('id' => '[0-9]+')); return true; } @@ -111,11 +118,15 @@ class OStatusPlugin extends Plugin // Updates will be handled for our internal PuSH hub. $action->element('link', array('rel' => 'hub', 'href' => common_local_url('pushhub'))); + + // Also, we'll add in the salmon link + $action->element('link', array('rel' => 'salmon', + 'href' => common_local_url('salmon'))); } } return true; } - + /** * Add the feed settings page to the Connect Settings menu * @@ -180,8 +191,51 @@ class OStatusPlugin extends Plugin $output->elementEnd('li'); } } - + /** + * Check if we've got some Salmon stuff to send + */ + function onEndNoticeSave($notice) + { + $count = preg_match_all('/(\w+\.)*\w+@(\w+\.)*\w+(\w+\-\w+)*\.\w+/', $notice->content, $matches); + if ($count) { + foreach ($matches[0] as $webfinger) { + // Check to see if we've got an actual webfinger + $w = new Webfinger; + + $endpoint_uri = ''; + + $result = $w->lookup($webfinger); + if (empty($result)) { + continue; + } + + foreach ($result->links as $link) { + if ($link['rel'] == 'salmon') { + $endpoint_uri = $link['href']; + } + } + + if (empty($endpoint_uri)) { + continue; + } + + $profile = $notice->getProfile(); + + $acct = $profile->nickname .'@'. common_config('site', 'server'); + + $xml = ''; + $xml .= $notice->asAtomEntry(); + // TODO : need to set author/uri to webfinger acct. more cleanly + $xml = preg_replace('/([^<])*<\/uri>/i', 'acct:'.$acct.'', $xml); + + + $salmon = new Salmon(); + $salmon->post($endpoint_uri, $xml); + } + } + } + function onCheckSchema() { // warning: the autoincrement doesn't seem to set. diff --git a/plugins/OStatus/actions/salmon.php b/plugins/OStatus/actions/salmon.php new file mode 100644 index 000000000..012869cf7 --- /dev/null +++ b/plugins/OStatus/actions/salmon.php @@ -0,0 +1,49 @@ +. + */ + +/** + * @package OStatusPlugin + * @author James Walker + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class SalmonAction extends Action +{ + + function handle() + { + parent::handle(); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost(); + } + } + + + function handlePost() + { + $user_id = $this->arg('id'); + common_log(LOG_DEBUG, 'Salmon: incoming post for user: '. $user_id); + + $xml = file_get_contents('php://input'); + + // TODO : Insert new $xml -> notice code + + } +} diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/webfinger.php index ec2dddd53..75ba16638 100644 --- a/plugins/OStatus/actions/webfinger.php +++ b/plugins/OStatus/actions/webfinger.php @@ -58,6 +58,13 @@ class WebfingerAction extends Action $xrd->links[] = array('rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => common_profile_url($nick)); + + $salmon_url = common_local_url('salmon', + array('id' => $this->user->id)); + + $xrd->links[] = array('rel' => 'salmon', + 'href' => $salmon_url); + // TODO - finalize where the redirect should go on the publisher $url = common_local_url('ostatussub') . '?feed={uri}'; $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe', diff --git a/plugins/OStatus/lib/Salmon.php b/plugins/OStatus/lib/Salmon.php new file mode 100644 index 000000000..8c77222a6 --- /dev/null +++ b/plugins/OStatus/lib/Salmon.php @@ -0,0 +1,64 @@ +. + * + * @package StatusNet + * @author James Walker + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class Salmon +{ + public function post($endpoint_uri, $xml) + { + if (empty($endpoint_uri)) { + return FALSE; + } + + $headers = array('Content-type: application/atom+xml'); + + try { + $client = new HTTPClient(); + $client->setBody($xml); + $response = $client->post($endpoint_uri, $headers); + } catch (HTTP_Request2_Exception $e) { + return false; + } + if ($response->getStatus() != 200) { + return false; + } + + } + + public function createMagicEnv($text, $userid) + { + + + } + + + public function verifyMagicEnv($env) + { + + + } +} diff --git a/plugins/OStatus/lib/Webfinger.php b/plugins/OStatus/lib/Webfinger.php index 7ab6b421b..417d54904 100644 --- a/plugins/OStatus/lib/Webfinger.php +++ b/plugins/OStatus/lib/Webfinger.php @@ -76,6 +76,10 @@ class Webfinger { $url = 'http://'. $domain .'/.well-known/host-meta'; $content = $this->fetchURL($url); + if (empty($content)) { + common_log(LOG_DEBUG, 'Error fetching host-meta'); + return false; + } $result = XRD::parse($content); // Ensure that the host == domain (spec may include signing later) -- cgit v1.2.3-54-g00ecf From cd0f288fa725aebaf6cb9ae240c2b085000f6707 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Feb 2010 12:39:31 -0800 Subject: Configurable delay between queuedaemon.php spawns/respawns to help stagger out startups and subscriptions. Defaults to 1 second. $config['queue']['spawndelay'] = 1; --- lib/default.php | 1 + lib/spawningdaemon.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/default.php b/lib/default.php index 485a08ba4..bf4b83718 100644 --- a/lib/default.php +++ b/lib/default.php @@ -88,6 +88,7 @@ $default = 'stomp_manual_failover' => true, // if multiple servers are listed, treat them as separate (enqueue on one randomly, listen on all) 'monitor' => null, // URL to monitor ping endpoint (work in progress) 'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully + 'spawndelay' => 1, // Wait at least N seconds between (re)spawns of child processes to avoid slamming the queue server with subscription startup 'debug_memory' => false, // true to spit memory usage to log 'inboxes' => true, // true to do inbox distribution & output queueing from in background via 'distrib' queue ), diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php index b1961d688..862cbb4fa 100644 --- a/lib/spawningdaemon.php +++ b/lib/spawningdaemon.php @@ -83,6 +83,7 @@ abstract class SpawningDaemon extends Daemon $this->log(LOG_INFO, "Spawned thread $i as pid $pid"); $children[$i] = $pid; } + sleep(common_config('queue', 'spawndelay')); } $this->log(LOG_INFO, "Waiting for children to complete."); @@ -111,6 +112,7 @@ abstract class SpawningDaemon extends Daemon $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); $children[$i] = $pid; } + sleep(common_config('queue', 'spawndelay')); } else { $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread."); } -- cgit v1.2.3-54-g00ecf From e856af34c3ac560a21286ca89019c2249994c080 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Feb 2010 12:39:31 -0800 Subject: Configurable delay between queuedaemon.php spawns/respawns to help stagger out startups and subscriptions. Defaults to 1 second. $config['queue']['spawndelay'] = 1; --- lib/default.php | 1 + lib/spawningdaemon.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/default.php b/lib/default.php index 485a08ba4..bf4b83718 100644 --- a/lib/default.php +++ b/lib/default.php @@ -88,6 +88,7 @@ $default = 'stomp_manual_failover' => true, // if multiple servers are listed, treat them as separate (enqueue on one randomly, listen on all) 'monitor' => null, // URL to monitor ping endpoint (work in progress) 'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully + 'spawndelay' => 1, // Wait at least N seconds between (re)spawns of child processes to avoid slamming the queue server with subscription startup 'debug_memory' => false, // true to spit memory usage to log 'inboxes' => true, // true to do inbox distribution & output queueing from in background via 'distrib' queue ), diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php index b1961d688..862cbb4fa 100644 --- a/lib/spawningdaemon.php +++ b/lib/spawningdaemon.php @@ -83,6 +83,7 @@ abstract class SpawningDaemon extends Daemon $this->log(LOG_INFO, "Spawned thread $i as pid $pid"); $children[$i] = $pid; } + sleep(common_config('queue', 'spawndelay')); } $this->log(LOG_INFO, "Waiting for children to complete."); @@ -111,6 +112,7 @@ abstract class SpawningDaemon extends Daemon $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); $children[$i] = $pid; } + sleep(common_config('queue', 'spawndelay')); } else { $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread."); } -- cgit v1.2.3-54-g00ecf From 8449256817f5a2bd7a7cac6bc04e4cb477d7dc49 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Feb 2010 18:32:52 -0800 Subject: OStatus partial support for group subscriptions: * detection of group feeds is currently a nasty hack based on presence of '/groups/' in URL -- should use some property on the feed? * listing for the remote group is kinda cruddy; needs to be named more cleanly * still need to establish per-author profiles (easier once we have the updated Atom code in) * group delivery probably not right yet * saving of group messages still triggering some weird behavior Added support for since_id and max_id on group timeline feeds as a free extra. Enjoy! --- actions/apitimelinegroup.php | 2 +- actions/showgroup.php | 4 +- classes/Notice.php | 4 +- classes/User_group.php | 4 +- lib/util.php | 2 +- plugins/OStatus/OStatusPlugin.php | 4 +- plugins/OStatus/actions/feedsubsettings.php | 22 +++++--- plugins/OStatus/classes/Feedinfo.php | 55 ++++++++++++++++++-- plugins/OStatus/lib/feedmunger.php | 4 +- plugins/OStatus/lib/hubdistribqueuehandler.php | 70 +++++++++++++++++++++++--- 10 files changed, 140 insertions(+), 31 deletions(-) diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index af414c680..fd2ed9ff9 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -130,7 +130,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction case 'atom': $selfuri = common_root_url() . 'api/statusnet/groups/timeline/' . - $this->group->nickname . '.atom'; + $this->group->id . '.atom'; $this->showAtomTimeline( $this->notices, $title, diff --git a/actions/showgroup.php b/actions/showgroup.php index 8042a4951..eb1238902 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -330,13 +330,13 @@ class ShowgroupAction extends GroupDesignAction new Feed(Feed::RSS2, common_local_url('ApiTimelineGroup', array('format' => 'rss', - 'id' => $this->group->nickname)), + 'id' => $this->group->id)), sprintf(_('Notice feed for %s group (RSS 2.0)'), $this->group->nickname)), new Feed(Feed::ATOM, common_local_url('ApiTimelineGroup', array('format' => 'atom', - 'id' => $this->group->nickname)), + 'id' => $this->group->id)), sprintf(_('Notice feed for %s group (Atom)'), $this->group->nickname)), new Feed(Feed::FOAF, diff --git a/classes/Notice.php b/classes/Notice.php index fca1c599c..247440f29 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -783,7 +783,7 @@ class Notice extends Memcached_DataObject $result = $gi->insert(); - if (!result) { + if (!$result) { common_log_db_error($gi, 'INSERT', __FILE__); throw new ServerException(_('Problem saving group inbox.')); } @@ -917,7 +917,7 @@ class Notice extends Memcached_DataObject /** * Same calculation as saveGroups but without the saving * @fixme merge the functions - * @return array of Group objects + * @return array of Group_inbox objects */ function getGroups() { diff --git a/classes/User_group.php b/classes/User_group.php index c86eadf8f..1fbb50a6e 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -49,12 +49,12 @@ class User_group extends Memcached_DataObject array('id' => $this->id)); } - function getNotices($offset, $limit) + function getNotices($offset, $limit, $since_id=null, $max_id=null) { $ids = Notice::stream(array($this, '_streamDirect'), array(), 'user_group:notice_ids:' . $this->id, - $offset, $limit); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } diff --git a/lib/util.php b/lib/util.php index 00c21aeb2..a07fe49e3 100644 --- a/lib/util.php +++ b/lib/util.php @@ -697,7 +697,7 @@ function common_group_link($sender_id, $nickname) { $sender = Profile::staticGet($sender_id); $group = User_group::getForNickname($nickname); - if ($group && $sender->isMember($group)) { + if ($sender && $group && $sender->isMember($group)) { $attrs = array('href' => $group->permalink(), 'class' => 'url'); if (!empty($group->fullname)) { diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 60a4e3827..89b5c4caa 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -107,11 +107,11 @@ class OStatusPlugin extends Plugin /** * Set up a PuSH hub link to our internal link for canonical timeline - * Atom feeds for users. + * Atom feeds for users and groups. */ function onStartApiAtom(Action $action) { - if ($action instanceof ApiTimelineUserAction) { + if ($action instanceof ApiTimelineUserAction || $action instanceof ApiTimelineGroupAction) { $id = $action->arg('id'); if (strval(intval($id)) === strval($id)) { // Canonical form of id in URL? diff --git a/plugins/OStatus/actions/feedsubsettings.php b/plugins/OStatus/actions/feedsubsettings.php index 4d5b7b60f..6f592bf5b 100644 --- a/plugins/OStatus/actions/feedsubsettings.php +++ b/plugins/OStatus/actions/feedsubsettings.php @@ -209,7 +209,7 @@ class FeedSubSettingsAction extends ConnectSettingsAction return; } } - + // And subscribe the current user to the local profile $user = common_current_user(); $profile = $this->feedinfo->getProfile(); @@ -217,12 +217,22 @@ class FeedSubSettingsAction extends ConnectSettingsAction throw new ServerException("Feed profile was not saved properly."); } - if ($user->isSubscribed($profile)) { - $this->showForm(_m('Already subscribed!')); - } elseif ($user->subscribeTo($profile)) { - $this->showForm(_m('Feed subscribed!')); + if ($this->feedinfo->isGroup()) { + if ($user->isMember($profile)) { + $this->showForm(_m('Already a member!')); + } elseif (Group_member::join($this->feedinfo->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!')); + if ($user->isSubscribed($profile)) { + $this->showForm(_m('Already subscribed!')); + } elseif ($user->subscribeTo($profile)) { + $this->showForm(_m('Feed subscribed!')); + } else { + $this->showForm(_m('Feed subscription failed!')); + } } } } diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index 107faf012..792ea6034 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -89,7 +89,8 @@ class Feedinfo extends Memcached_DataObject function table() { return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'profile_id' => DB_DATAOBJECT_INT + 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 + DB_DATAOBJECT_NOTNULL, @@ -111,7 +112,9 @@ class Feedinfo extends Memcached_DataObject /*extra*/ null, /*auto_increment*/ true), new ColumnDef('profile_id', 'integer', - null, false), + null, true), + new ColumnDef('group_id', 'integer', + null, true), new ColumnDef('feeduri', 'varchar', 255, false, 'UNI'), new ColumnDef('homeuri', 'varchar', @@ -176,6 +179,7 @@ class Feedinfo extends Memcached_DataObject /** * @param FeedMunger $munger + * @param boolean $isGroup is this a group record? * @return Feedinfo */ public static function ensureProfile($munger) @@ -217,6 +221,22 @@ class Feedinfo extends Memcached_DataObject } $feedinfo->profile_id = $profile->id; + if ($feedinfo->isGroup()) { + $group = new User_group(); + $group->nickname = $profile->nickname . '@remote'; // @fixme + $group->fullname = $profile->fullname; + $group->homepage = $profile->homepage; + $group->location = $profile->location; + $group->created = $profile->created; + $group->insert(); + + if ($avatar) { + $group->setOriginal($filename); + } + + $feedinfo->group_id = $group->id; + } + $result = $feedinfo->insert(); if (empty($result)) { throw new FeedDBException($feedinfo); @@ -231,6 +251,14 @@ class Feedinfo extends Memcached_DataObject return $feedinfo; } + /** + * Damn dirty hack! + */ + function isGroup() + { + return (strpos($this->feeduri, '/groups/') !== false); + } + /** * Send a subscription request to the hub for this feed. * The hub will later send us a confirmation POST to /feedsub/callback. @@ -325,17 +353,34 @@ class Feedinfo extends Memcached_DataObject $dupe = new Notice(); $dupe->uri = $notice->uri; if ($dupe->find(true)) { + // @fixme we might have to do individual and group delivery separately! common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); continue; } - + if (Event::handle('StartNoticeSave', array(&$notice))) { $id = $notice->insert(); Event::handle('EndNoticeSave', array($notice)); } - $notice->addToInboxes(); - common_log(LOG_INFO, __METHOD__ . ": saved notice {$notice->id} for entry $index of update to \"{$this->feeduri}\""); + + common_log(LOG_DEBUG, "going to check group delivery..."); + if ($this->group_id) { + $group = User_group::staticGet($this->group_id); + if ($group) { + common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname"); + $groups = array($group); + } else { + common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?"); + } + } else { + common_log(LOG_INFO, __METHOD__ . ": no local shadow groups"); + $groups = array(); + } + common_log(LOG_DEBUG, "going to add to inboxes..."); + $notice->addToInboxes($groups, array()); + common_log(LOG_DEBUG, "added to inboxes."); + $hits++; } if ($hits == 0) { diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index cbaec6775..5dce95342 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -203,7 +203,7 @@ class FeedMunger if (!$entry) { return null; } - + if ($preview) { $notice = new FeedSubPreviewNotice($this->profile(true)); $notice->id = -1; @@ -221,7 +221,7 @@ class FeedMunger $notice->uri = $link; $notice->url = $link; $notice->content = $this->noticeFromEntry($entry); - $notice->rendered = common_render_content($notice->content, $notice); + $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'; diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/hubdistribqueuehandler.php index 126f1355f..189ccbedf 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/hubdistribqueuehandler.php @@ -34,6 +34,14 @@ class HubDistribQueueHandler extends QueueHandler { assert($notice instanceof Notice); + $this->pushUser($notice); + foreach ($notice->getGroups() as $group) { + $this->pushGroup($notice, $group->group_id); + } + } + + function pushUser($notice) + { // See if there's any PuSH subscriptions, including OStatus clients. // @fixme handle group subscriptions as well // http://identi.ca/api/statuses/user_timeline/1.atom @@ -43,20 +51,42 @@ class HubDistribQueueHandler extends QueueHandler $sub = new HubSub(); $sub->topic = $feed; if ($sub->find()) { - common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $feed"); - $qm = QueueManager::get(); $atom = $this->userFeedForNotice($notice); - while ($sub->fetch()) { - common_log(LOG_INFO, "Prepping PuSH distribution to $sub->callback for $feed"); - $data = array('sub' => clone($sub), - 'atom' => $atom); - $qm->enqueue($data, 'hubout'); - } + $this->pushFeeds($atom, $sub); } else { common_log(LOG_INFO, "No PuSH subscribers for $feed"); } } + function pushGroup($notice, $group_id) + { + $feed = common_local_url('ApiTimelineGroup', + array('id' => $group_id, + 'format' => 'atom')); + $sub = new HubSub(); + $sub->topic = $feed; + if ($sub->find()) { + common_log(LOG_INFO, "Building PuSH feed for $feed"); + $atom = $this->groupFeedForNotice($group_id, $notice); + $this->pushFeeds($atom, $sub); + } else { + common_log(LOG_INFO, "No PuSH subscribers for $feed"); + } + } + + + function pushFeeds($atom, $sub) + { + common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic"); + $qm = QueueManager::get(); + while ($sub->fetch()) { + common_log(LOG_INFO, "Prepping PuSH distribution to $sub->callback for $sub->topic"); + $data = array('sub' => clone($sub), + 'atom' => $atom); + $qm->enqueue($data, 'hubout'); + } + } + /** * Build a single-item version of the sending user's Atom feed. * @param Notice $notice @@ -83,5 +113,29 @@ class HubDistribQueueHandler extends QueueHandler common_log(LOG_DEBUG, $feed); return $feed; } + + function groupFeedForNotice($group_id, $notice) + { + // @fixme this feels VERY hacky... + // should probably be a cleaner way to do it + + ob_start(); + $api = new ApiTimelineGroupAction(); + $args = array('id' => $group_id, + 'format' => 'atom', + 'max_id' => $notice->id, + 'since_id' => $notice->id - 1); + $api->prepare($args); + $api->handle($args); + $feed = ob_get_clean(); + + // ...and override the content-type back to something normal... eww! + // hope there's no other headers that got set while we weren't looking. + header('Content-Type: text/html; charset=utf-8'); + + common_log(LOG_DEBUG, $feed); + return $feed; + } + } -- cgit v1.2.3-54-g00ecf From 46f90f7b08381eec80c2e895d061af15b5e1c729 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Feb 2010 16:53:51 -0500 Subject: moving salmon endpoint under 'main/' --- plugins/OStatus/OStatusPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 89b5c4caa..f7fed1f0d 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -77,7 +77,7 @@ class OStatusPlugin extends Plugin array('action' => 'feedsubsettings')); // Salmon endpoint - $m->connect('salmon/user/:id', + $m->connect('main/salmon/user/:id', array('action' => 'salmon'), array('id' => '[0-9]+')); return true; -- cgit v1.2.3-54-g00ecf From f4ebac503665928fb3102bb957a5c0a0259d5ef9 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Feb 2010 21:50:51 -0500 Subject: removing the webfinger hack for Notice::asAtomEntry since salmon can use a profile URL --- plugins/OStatus/OStatusPlugin.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index f7fed1f0d..62ecaf631 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -220,15 +220,8 @@ class OStatusPlugin extends Plugin continue; } - $profile = $notice->getProfile(); - - $acct = $profile->nickname .'@'. common_config('site', 'server'); - $xml = ''; $xml .= $notice->asAtomEntry(); - // TODO : need to set author/uri to webfinger acct. more cleanly - $xml = preg_replace('/([^<])*<\/uri>/i', 'acct:'.$acct.'', $xml); - $salmon = new Salmon(); $salmon->post($endpoint_uri, $xml); -- cgit v1.2.3-54-g00ecf From dcd9b2a405eba8861459d1aa5e645c84688e8837 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 10 Feb 2010 11:16:27 +0100 Subject: Refactored repeat confirmation dialog. Also fixes dialog skipping. --- js/util.js | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/js/util.js b/js/util.js index c6a9682de..639049668 100644 --- a/js/util.js +++ b/js/util.js @@ -356,42 +356,44 @@ var SN = { // StatusNet }, NoticeRepeat: function() { - $('.form_repeat').live('click', function() { - SN.U.FormXHR($(this)); + $('.form_repeat').live('click', function(e) { + e.preventDefault(); + SN.U.NoticeRepeatConfirmation($(this)); return false; }); }, NoticeRepeatConfirmation: function(form) { - function NRC() { - form.closest('.notice-options').addClass('opaque'); - form.addClass('dialogbox'); + var submit_i = form.find('.submit'); - form.append(''); - form.find('button.close').click(function(){ - $(this).remove(); + var submit = submit_i.clone(); + submit + .addClass('submit_dialogbox') + .removeClass('submit'); + form.append(submit); + submit.bind('click', function() { SN.U.FormXHR(form); return false; }); - form.closest('.notice-options').removeClass('opaque'); - form.removeClass('dialogbox'); - form.find('.submit_dialogbox').remove(); - form.find('.submit').show(); + submit_i.hide(); - return false; - }); - }; + form + .addClass('dialogbox') + .append('') + .closest('.notice-options') + .addClass('opaque'); - form.find('.submit').bind('click', function(e) { - e.preventDefault(); + form.find('button.close').click(function(){ + $(this).remove(); - var submit = form.find('.submit').clone(); - submit.addClass('submit_dialogbox'); - submit.removeClass('submit'); - form.append(submit); + form + .removeClass('dialogbox') + .closest('.notice-options') + .removeClass('opaque'); - $(this).hide(); + form.find('.submit_dialogbox').remove(); + form.find('.submit').show(); - NRC(); + return false; }); }, -- cgit v1.2.3-54-g00ecf From 6b10c269b54d452b0a5f55ab5722297f4b889653 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 10 Feb 2010 10:47:46 +0000 Subject: Fix to Realtime's repeat notice form legend and notice id --- plugins/Realtime/realtimeupdate.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 7adea45a0..2e5851ae5 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -209,10 +209,10 @@ RealtimeUpdate = { var rf; rf = "
"+ "
"+ - "Favor this notice"+ + "Repeat this notice?"+ ""+ - ""+ - ""+ + ""+ + ""+ "
"+ "
"; -- cgit v1.2.3-54-g00ecf From f37063cd63a30fdcc0948d4710c088ba5e5d0990 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Feb 2010 10:18:47 -0800 Subject: Filename case fix --- plugins/OStatus/lib/Salmon.php | 64 ------------- plugins/OStatus/lib/Webfinger.php | 143 ----------------------------- plugins/OStatus/lib/XRD.php | 183 -------------------------------------- plugins/OStatus/lib/salmon.php | 64 +++++++++++++ plugins/OStatus/lib/webfinger.php | 143 +++++++++++++++++++++++++++++ plugins/OStatus/lib/xrd.php | 183 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 390 insertions(+), 390 deletions(-) delete mode 100644 plugins/OStatus/lib/Salmon.php delete mode 100644 plugins/OStatus/lib/Webfinger.php delete mode 100644 plugins/OStatus/lib/XRD.php create mode 100644 plugins/OStatus/lib/salmon.php create mode 100644 plugins/OStatus/lib/webfinger.php create mode 100644 plugins/OStatus/lib/xrd.php diff --git a/plugins/OStatus/lib/Salmon.php b/plugins/OStatus/lib/Salmon.php deleted file mode 100644 index 8c77222a6..000000000 --- a/plugins/OStatus/lib/Salmon.php +++ /dev/null @@ -1,64 +0,0 @@ -. - * - * @package StatusNet - * @author James Walker - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ -class Salmon -{ - public function post($endpoint_uri, $xml) - { - if (empty($endpoint_uri)) { - return FALSE; - } - - $headers = array('Content-type: application/atom+xml'); - - try { - $client = new HTTPClient(); - $client->setBody($xml); - $response = $client->post($endpoint_uri, $headers); - } catch (HTTP_Request2_Exception $e) { - return false; - } - if ($response->getStatus() != 200) { - return false; - } - - } - - public function createMagicEnv($text, $userid) - { - - - } - - - public function verifyMagicEnv($env) - { - - - } -} diff --git a/plugins/OStatus/lib/Webfinger.php b/plugins/OStatus/lib/Webfinger.php deleted file mode 100644 index 417d54904..000000000 --- a/plugins/OStatus/lib/Webfinger.php +++ /dev/null @@ -1,143 +0,0 @@ -. - * - * @package StatusNet - * @author James Walker - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -define('WEBFINGER_SERVICE_REL_VALUE', 'lrdd'); - -/** - * Implement the webfinger protocol. - */ -class Webfinger -{ - /** - * Perform a webfinger lookup given an account. - */ - public function lookup($id) - { - $id = $this->normalize($id); - list($name, $domain) = explode('@', $id); - - $links = $this->getServiceLinks($domain); - if (!$links) { - return false; - } - - $services = array(); - foreach ($links as $link) { - if ($link['template']) { - return $this->getServiceDescription($link['template'], $id); - } - if ($link['href']) { - return $this->getServiceDescription($link['href'], $id); - } - } - } - - /** - * Normalize an account ID - */ - function normalize($id) - { - if (substr($id, 0, 7) == 'acct://') { - return substr($id, 7); - } else if (substr($id, 0, 5) == 'acct:') { - return substr($id, 5); - } - - return $id; - } - - function getServiceLinks($domain) - { - $url = 'http://'. $domain .'/.well-known/host-meta'; - $content = $this->fetchURL($url); - if (empty($content)) { - common_log(LOG_DEBUG, 'Error fetching host-meta'); - return false; - } - $result = XRD::parse($content); - - // Ensure that the host == domain (spec may include signing later) - if ($result->host != $domain) { - return false; - } - - $links = array(); - foreach ($result->links as $link) { - if ($link['rel'] == WEBFINGER_SERVICE_REL_VALUE) { - $links[] = $link; - } - - } - return $links; - } - - function getServiceDescription($template, $id) - { - $url = $this->applyTemplate($template, 'acct:' . $id); - - $content = $this->fetchURL($url); - - return XRD::parse($content); - } - - function fetchURL($url) - { - try { - $client = new HTTPClient(); - $response = $client->get($url); - } catch (HTTP_Request2_Exception $e) { - return false; - } - - if ($response->getStatus() != 200) { - return false; - } - - return $response->getBody(); - } - - function applyTemplate($template, $id) - { - $template = str_replace('{uri}', urlencode($id), $template); - - return $template; - } - - function getHostMeta($domain, $template) { - $xrd = new XRD(); - $xrd->host = $domain; - $xrd->links[] = array('rel' => 'lrdd', - 'template' => $template, - 'title' => array('Resource Descriptor')); - - return $xrd->toXML(); - } -} - - diff --git a/plugins/OStatus/lib/XRD.php b/plugins/OStatus/lib/XRD.php deleted file mode 100644 index 16d27f8eb..000000000 --- a/plugins/OStatus/lib/XRD.php +++ /dev/null @@ -1,183 +0,0 @@ -. - * - * @package StatusNet - * @author James Walker - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - - -class XRD -{ - const XML_NS = 'http://www.w3.org/2000/xmlns/'; - - const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'; - - const HOST_META_NS = 'http://host-meta.net/xrd/1.0'; - - public $expires; - - public $subject; - - public $host; - - public $alias = array(); - - public $types = array(); - - public $links = array(); - - public static function parse($xml) - { - $xrd = new XRD(); - - $dom = new DOMDocument(); - $dom->loadXML($xml); - $xrd_element = $dom->getElementsByTagName('XRD')->item(0); - - // Check for host-meta host - $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; - if ($host) { - $xrd->host = $host; - } - - // Loop through other elements - foreach ($xrd_element->childNodes as $node) { - switch ($node->tagName) { - case 'Expires': - $xrd->expires = $node->nodeValue; - break; - case 'Subject': - $xrd->subject = $node->nodeValue; - break; - - case 'Alias': - $xrd->alias[] = $node->nodeValue; - break; - - case 'Link': - $xrd->links[] = $xrd->parseLink($node); - break; - - case 'Type': - $xrd->types[] = $xrd->parseType($node); - break; - - } - } - return $xrd; - } - - public function toXML() - { - $dom = new DOMDocument('1.0', 'UTF-8'); - $dom->formatOutput = true; - - $xrd_dom = $dom->createElementNS(XRD::XRD_NS, 'XRD'); - $dom->appendChild($xrd_dom); - - if ($this->host) { - $host_dom = $dom->createElement('hm:Host', $this->host); - $xrd_dom->setAttributeNS(XRD::XML_NS, 'xmlns:hm', XRD::HOST_META_NS); - $xrd_dom->appendChild($host_dom); - } - - if ($this->expires) { - $expires_dom = $dom->createElement('Expires', $this->expires); - $xrd_dom->appendChild($expires_dom); - } - - if ($this->subject) { - $subject_dom = $dom->createElement('Subject', $this->subject); - $xrd_dom->appendChild($subject_dom); - } - - foreach ($this->alias as $alias) { - $alias_dom = $dom->createElement('Alias', $alias); - $xrd_dom->appendChild($alias_dom); - } - - foreach ($this->types as $type) { - $type_dom = $dom->createElement('Type', $type); - $xrd_dom->appendChild($type_dom); - } - - foreach ($this->links as $link) { - $link_dom = $this->saveLink($dom, $link); - $xrd_dom->appendChild($link_dom); - } - - return $dom->saveXML(); - } - - function parseType($element) - { - return array(); - } - - function parseLink($element) - { - $link = array(); - $link['rel'] = $element->getAttribute('rel'); - $link['type'] = $element->getAttribute('type'); - $link['href'] = $element->getAttribute('href'); - $link['template'] = $element->getAttribute('template'); - foreach ($element->childNodes as $node) { - switch($node->tagName) { - case 'Title': - $link['title'][] = $node->nodeValue; - } - } - - return $link; - } - - function saveLink($doc, $link) - { - $link_element = $doc->createElement('Link'); - if ($link['rel']) { - $link_element->setAttribute('rel', $link['rel']); - } - if ($link['type']) { - $link_element->setAttribute('type', $link['type']); - } - if ($link['href']) { - $link_element->setAttribute('href', $link['href']); - } - if ($link['template']) { - $link_element->setAttribute('template', $link['template']); - } - - if (is_array($link['title'])) { - foreach($link['title'] as $title) { - $title = $doc->createElement('Title', $title); - $link_element->appendChild($title); - } - } - - - return $link_element; - } -} - diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php new file mode 100644 index 000000000..8c77222a6 --- /dev/null +++ b/plugins/OStatus/lib/salmon.php @@ -0,0 +1,64 @@ +. + * + * @package StatusNet + * @author James Walker + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class Salmon +{ + public function post($endpoint_uri, $xml) + { + if (empty($endpoint_uri)) { + return FALSE; + } + + $headers = array('Content-type: application/atom+xml'); + + try { + $client = new HTTPClient(); + $client->setBody($xml); + $response = $client->post($endpoint_uri, $headers); + } catch (HTTP_Request2_Exception $e) { + return false; + } + if ($response->getStatus() != 200) { + return false; + } + + } + + public function createMagicEnv($text, $userid) + { + + + } + + + public function verifyMagicEnv($env) + { + + + } +} diff --git a/plugins/OStatus/lib/webfinger.php b/plugins/OStatus/lib/webfinger.php new file mode 100644 index 000000000..417d54904 --- /dev/null +++ b/plugins/OStatus/lib/webfinger.php @@ -0,0 +1,143 @@ +. + * + * @package StatusNet + * @author James Walker + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +define('WEBFINGER_SERVICE_REL_VALUE', 'lrdd'); + +/** + * Implement the webfinger protocol. + */ +class Webfinger +{ + /** + * Perform a webfinger lookup given an account. + */ + public function lookup($id) + { + $id = $this->normalize($id); + list($name, $domain) = explode('@', $id); + + $links = $this->getServiceLinks($domain); + if (!$links) { + return false; + } + + $services = array(); + foreach ($links as $link) { + if ($link['template']) { + return $this->getServiceDescription($link['template'], $id); + } + if ($link['href']) { + return $this->getServiceDescription($link['href'], $id); + } + } + } + + /** + * Normalize an account ID + */ + function normalize($id) + { + if (substr($id, 0, 7) == 'acct://') { + return substr($id, 7); + } else if (substr($id, 0, 5) == 'acct:') { + return substr($id, 5); + } + + return $id; + } + + function getServiceLinks($domain) + { + $url = 'http://'. $domain .'/.well-known/host-meta'; + $content = $this->fetchURL($url); + if (empty($content)) { + common_log(LOG_DEBUG, 'Error fetching host-meta'); + return false; + } + $result = XRD::parse($content); + + // Ensure that the host == domain (spec may include signing later) + if ($result->host != $domain) { + return false; + } + + $links = array(); + foreach ($result->links as $link) { + if ($link['rel'] == WEBFINGER_SERVICE_REL_VALUE) { + $links[] = $link; + } + + } + return $links; + } + + function getServiceDescription($template, $id) + { + $url = $this->applyTemplate($template, 'acct:' . $id); + + $content = $this->fetchURL($url); + + return XRD::parse($content); + } + + function fetchURL($url) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return $response->getBody(); + } + + function applyTemplate($template, $id) + { + $template = str_replace('{uri}', urlencode($id), $template); + + return $template; + } + + function getHostMeta($domain, $template) { + $xrd = new XRD(); + $xrd->host = $domain; + $xrd->links[] = array('rel' => 'lrdd', + 'template' => $template, + 'title' => array('Resource Descriptor')); + + return $xrd->toXML(); + } +} + + diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php new file mode 100644 index 000000000..16d27f8eb --- /dev/null +++ b/plugins/OStatus/lib/xrd.php @@ -0,0 +1,183 @@ +. + * + * @package StatusNet + * @author James Walker + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + + +class XRD +{ + const XML_NS = 'http://www.w3.org/2000/xmlns/'; + + const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'; + + const HOST_META_NS = 'http://host-meta.net/xrd/1.0'; + + public $expires; + + public $subject; + + public $host; + + public $alias = array(); + + public $types = array(); + + public $links = array(); + + public static function parse($xml) + { + $xrd = new XRD(); + + $dom = new DOMDocument(); + $dom->loadXML($xml); + $xrd_element = $dom->getElementsByTagName('XRD')->item(0); + + // Check for host-meta host + $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; + if ($host) { + $xrd->host = $host; + } + + // Loop through other elements + foreach ($xrd_element->childNodes as $node) { + switch ($node->tagName) { + case 'Expires': + $xrd->expires = $node->nodeValue; + break; + case 'Subject': + $xrd->subject = $node->nodeValue; + break; + + case 'Alias': + $xrd->alias[] = $node->nodeValue; + break; + + case 'Link': + $xrd->links[] = $xrd->parseLink($node); + break; + + case 'Type': + $xrd->types[] = $xrd->parseType($node); + break; + + } + } + return $xrd; + } + + public function toXML() + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $xrd_dom = $dom->createElementNS(XRD::XRD_NS, 'XRD'); + $dom->appendChild($xrd_dom); + + if ($this->host) { + $host_dom = $dom->createElement('hm:Host', $this->host); + $xrd_dom->setAttributeNS(XRD::XML_NS, 'xmlns:hm', XRD::HOST_META_NS); + $xrd_dom->appendChild($host_dom); + } + + if ($this->expires) { + $expires_dom = $dom->createElement('Expires', $this->expires); + $xrd_dom->appendChild($expires_dom); + } + + if ($this->subject) { + $subject_dom = $dom->createElement('Subject', $this->subject); + $xrd_dom->appendChild($subject_dom); + } + + foreach ($this->alias as $alias) { + $alias_dom = $dom->createElement('Alias', $alias); + $xrd_dom->appendChild($alias_dom); + } + + foreach ($this->types as $type) { + $type_dom = $dom->createElement('Type', $type); + $xrd_dom->appendChild($type_dom); + } + + foreach ($this->links as $link) { + $link_dom = $this->saveLink($dom, $link); + $xrd_dom->appendChild($link_dom); + } + + return $dom->saveXML(); + } + + function parseType($element) + { + return array(); + } + + function parseLink($element) + { + $link = array(); + $link['rel'] = $element->getAttribute('rel'); + $link['type'] = $element->getAttribute('type'); + $link['href'] = $element->getAttribute('href'); + $link['template'] = $element->getAttribute('template'); + foreach ($element->childNodes as $node) { + switch($node->tagName) { + case 'Title': + $link['title'][] = $node->nodeValue; + } + } + + return $link; + } + + function saveLink($doc, $link) + { + $link_element = $doc->createElement('Link'); + if ($link['rel']) { + $link_element->setAttribute('rel', $link['rel']); + } + if ($link['type']) { + $link_element->setAttribute('type', $link['type']); + } + if ($link['href']) { + $link_element->setAttribute('href', $link['href']); + } + if ($link['template']) { + $link_element->setAttribute('template', $link['template']); + } + + if (is_array($link['title'])) { + foreach($link['title'] as $title) { + $title = $doc->createElement('Title', $title); + $link_element->appendChild($title); + } + } + + + return $link_element; + } +} + -- cgit v1.2.3-54-g00ecf From d9c9b2a12fe6cbb800440eeb0174d375760e0103 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Feb 2010 10:59:30 -0800 Subject: Queue daemon fixes: * skip unnecessary unsubscribes on graceful shutdown -- takes a long time for many queues, slows down our restarts when hitting graceful mem limit * fix control channel (was broken when we switched to support multiple queue servers) --- lib/stompqueuemanager.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 6730cd213..cc4c817d8 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -107,9 +107,10 @@ class StompQueueManager extends QueueManager $message .= ':' . $param; } $this->_connect(); - $result = $this->_send($this->control, - $message, - array ('created' => common_sql_now())); + $con = $this->cons[$this->defaultIdx]; + $result = $con->send($this->control, + $message, + array ('created' => common_sql_now())); if ($result) { $this->_log(LOG_INFO, "Sent control ping to queue daemons: $message"); return true; @@ -368,17 +369,10 @@ class StompQueueManager extends QueueManager foreach ($this->cons as $i => $con) { if ($con) { $this->rollback($i); - $con->unsubscribe($this->control); + $con->disconnect(); + $this->cons[$i] = null; } } - if ($this->sites) { - foreach ($this->sites as $server) { - StatusNet::init($server); - $this->doUnsubscribe(); - } - } else { - $this->doUnsubscribe(); - } return true; } -- cgit v1.2.3-54-g00ecf From 045797331c82b86e03c61f00f4db68a085688520 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Feb 2010 16:43:37 -0800 Subject: fix up hub queueing to work w/ stomp queues --- lib/queuemanager.php | 36 ++++++++++++++++++-------- lib/stompqueuemanager.php | 26 +++++-------------- plugins/OStatus/lib/hubdistribqueuehandler.php | 1 + plugins/OStatus/lib/huboutqueuehandler.php | 2 +- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/lib/queuemanager.php b/lib/queuemanager.php index afe710e88..149617eb5 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -155,26 +155,26 @@ abstract class QueueManager extends IoManager } /** - * Encode an object for queued storage. - * Next gen may use serialization. + * Encode an object or variable for queued storage. + * Notice objects are currently stored as an id reference; + * other items are serialized. * - * @param mixed $object + * @param mixed $item * @return string */ - protected function encode($object) + protected function encode($item) { - if ($object instanceof Notice) { - return $object->id; - } else if (is_string($object)) { - return $object; + if ($item instanceof Notice) { + // Backwards compat + return $item->id; } else { - throw new ServerException("Can't queue this type", 500); + return serialize($item); } } /** * Decode an object from queued storage. - * Accepts back-compat notice reference entries and strings for now. + * Accepts notice reference entries and serialized items. * * @param string * @return mixed @@ -182,9 +182,23 @@ abstract class QueueManager extends IoManager protected function decode($frame) { if (is_numeric($frame)) { + // Back-compat for notices... return Notice::staticGet(intval($frame)); - } else { + } elseif (substr($frame, 0, 1) == '<') { + // Back-compat for XML source return $frame; + } else { + // Deserialize! + #$old = error_reporting(); + #error_reporting($old & ~E_NOTICE); + $out = unserialize($frame); + #error_reporting($old); + + if ($out === false && $frame !== 'b:0;') { + common_log(LOG_ERR, "Couldn't unserialize queued frame: $frame"); + return false; + } + return $out; } } diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index cc4c817d8..cd62c25bd 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -549,26 +549,14 @@ class StompQueueManager extends QueueManager } $host = $this->cons[$idx]->getServer(); - if (is_numeric($frame->body)) { - $id = intval($frame->body); - $info = "notice $id posted at {$frame->headers['created']} in queue $queue from $host"; - - $notice = Notice::staticGet('id', $id); - if (empty($notice)) { - $this->_log(LOG_WARNING, "Skipping missing $info"); - $this->ack($idx, $frame); - $this->commit($idx); - $this->begin($idx); - $this->stats('badnotice', $queue); - return false; - } - - $item = $notice; - } else { - // @fixme should we serialize, or json, or what here? - $info = "string posted at {$frame->headers['created']} in queue $queue from $host"; - $item = $frame->body; + $item = $this->decode($frame->body); + if (empty($item)) { + $this->_log(LOG_ERR, "Skipping empty or deleted item in queue $queue from $host"); + return true; } + $info = $this->logrep($item) . " posted at " . + $frame->headers['created'] . " in queue $queue from $host"; + $this->_log(LOG_DEBUG, "Dequeued $info"); $handler = $this->getHandler($queue); if (!$handler) { diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/hubdistribqueuehandler.php index 189ccbedf..de3a81385 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/hubdistribqueuehandler.php @@ -56,6 +56,7 @@ class HubDistribQueueHandler extends QueueHandler } else { common_log(LOG_INFO, "No PuSH subscribers for $feed"); } + return true; } function pushGroup($notice, $group_id) diff --git a/plugins/OStatus/lib/huboutqueuehandler.php b/plugins/OStatus/lib/huboutqueuehandler.php index cb44ad2c4..0791c7e5d 100644 --- a/plugins/OStatus/lib/huboutqueuehandler.php +++ b/plugins/OStatus/lib/huboutqueuehandler.php @@ -43,7 +43,7 @@ class HubOutQueueHandler extends QueueHandler common_log(LOG_ERR, "Failed PuSH to $sub->callback for $sub->topic: " . $e->getMessage()); // @fixme Reschedule a later delivery? - // Currently we have no way to do this other than 'send NOW' + return true; } return true; -- cgit v1.2.3-54-g00ecf From 7752612ef6c6d00ad3adee178998d997f956f4b5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Feb 2010 20:47:42 +0000 Subject: fix hubdistrib --- plugins/OStatus/lib/hubdistribqueuehandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/hubdistribqueuehandler.php index de3a81385..a35b8874c 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/hubdistribqueuehandler.php @@ -38,6 +38,7 @@ class HubDistribQueueHandler extends QueueHandler foreach ($notice->getGroups() as $group) { $this->pushGroup($notice, $group->group_id); } + return true; } function pushUser($notice) -- cgit v1.2.3-54-g00ecf From 162868afdb1181a3d6e973a3de9d0abbb5e1c168 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Feb 2010 21:18:53 +0000 Subject: OStatus update: now using standard save/delivery for incoming ostatus messages -- they get reflected to realtime and everything! woooo Group delivery may still need some munging --- plugins/OStatus/classes/Feedinfo.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index 792ea6034..b4e55c364 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -344,26 +344,31 @@ class Feedinfo extends Memcached_DataObject $hits = 0; foreach ($feed as $index => $entry) { // @fixme this might sort in wrong order if we get multiple updates - + $notice = $munger->notice($index); $notice->profile_id = $this->profile_id; - + // Double-check for oldies // @fixme this could explode horribly for multiple feeds on a blog. sigh $dupe = new Notice(); $dupe->uri = $notice->uri; if ($dupe->find(true)) { - // @fixme we might have to do individual and group delivery separately! common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); continue; } - if (Event::handle('StartNoticeSave', array(&$notice))) { - $id = $notice->insert(); - Event::handle('EndNoticeSave', array($notice)); - } - common_log(LOG_INFO, __METHOD__ . ": saved notice {$notice->id} for entry $index of update to \"{$this->feeduri}\""); - + // @fixme need to ensure that groups get handled correctly + $saved = Notice::saveNew($this->profile_id, + $notice->content, + 'ostatus', + array('is_local' => Notice::REMOTE_OMB, + 'uri' => $notice->uri, + 'lat' => $notice->lat, + 'lon' => $notice->lon, + 'location_ns' => $notice->location_ns, + 'location_id' => $notice->location_id)); + + /* common_log(LOG_DEBUG, "going to check group delivery..."); if ($this->group_id) { $group = User_group::staticGet($this->group_id); @@ -380,6 +385,7 @@ class Feedinfo extends Memcached_DataObject common_log(LOG_DEBUG, "going to add to inboxes..."); $notice->addToInboxes($groups, array()); common_log(LOG_DEBUG, "added to inboxes."); + */ $hits++; } -- cgit v1.2.3-54-g00ecf From 4ae760cb62657e68b6b2313e64d2bb59fe264df4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Feb 2010 22:58:39 +0000 Subject: OStatus PuSH fixes: * HMAC now calculated correctly - confirmed interop with Google's public hub * Can optionally use an external PuSH hub, set URL in $config['ostatus']['hub'] (may have issues in replication environment, and will ping the hub for every update rather than just those with subscribers) Internal hub will still function when this is set, but won't be advertised. Warning: setting this, then turning it off later will break subscriptions as that hub will no longer receive pings. --- plugins/OStatus/OStatusPlugin.php | 11 ++-- plugins/OStatus/classes/Feedinfo.php | 4 +- plugins/OStatus/classes/HubSub.php | 2 +- plugins/OStatus/lib/hubdistribqueuehandler.php | 70 ++++++++++++++++++++------ 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 62ecaf631..4b9b4d2c3 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -114,10 +114,15 @@ class OStatusPlugin extends Plugin if ($action instanceof ApiTimelineUserAction || $action instanceof ApiTimelineGroupAction) { $id = $action->arg('id'); if (strval(intval($id)) === strval($id)) { - // Canonical form of id in URL? - // Updates will be handled for our internal PuSH hub. + // Canonical form of id in URL? These are used for OStatus syndication. + + $hub = common_config('ostatus', 'hub'); + if (empty($hub)) { + // Updates will be handled through our internal PuSH hub. + $hub = common_local_url('pushhub'); + } $action->element('link', array('rel' => 'hub', - 'href' => common_local_url('pushhub'))); + 'href' => $hub)); // Also, we'll add in the salmon link $action->element('link', array('rel' => 'salmon', diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index b4e55c364..2344a4a0e 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -160,7 +160,7 @@ class Feedinfo extends Memcached_DataObject function keyTypes() { - return array('id' => 'K'); // @fixme we'll need a profile_id key at least + return array('id' => 'K', 'feeduri' => 'U'); // @fixme we'll need a profile_id key at least } function sequenceKey() @@ -323,7 +323,7 @@ class Feedinfo extends Memcached_DataObject if ($this->secret) { if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { $their_hmac = strtolower($matches[1]); - $our_hmac = sha1($xml . $this->secret); + $our_hmac = hash_hmac('sha1', $xml, $this->secret); if ($their_hmac !== $our_hmac) { common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac"); return; diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php index 1769f6c94..7071ee5b4 100644 --- a/plugins/OStatus/classes/HubSub.php +++ b/plugins/OStatus/classes/HubSub.php @@ -242,7 +242,7 @@ class HubSub extends Memcached_DataObject { $headers = array('Content-Type: application/atom+xml'); if ($this->secret) { - $hmac = sha1($atom . $this->secret); + $hmac = hash_hmac('sha1', $atom, $this->secret); $headers[] = "X-Hub-Signature: sha1=$hmac"; } else { $hmac = '(none)'; diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/hubdistribqueuehandler.php index a35b8874c..245a57f72 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/hubdistribqueuehandler.php @@ -49,15 +49,7 @@ class HubDistribQueueHandler extends QueueHandler $feed = common_local_url('ApiTimelineUser', array('id' => $notice->profile_id, 'format' => 'atom')); - $sub = new HubSub(); - $sub->topic = $feed; - if ($sub->find()) { - $atom = $this->userFeedForNotice($notice); - $this->pushFeeds($atom, $sub); - } else { - common_log(LOG_INFO, "No PuSH subscribers for $feed"); - } - return true; + $this->pushFeed($feed, array($this, 'userFeedForNotice'), $notice); } function pushGroup($notice, $group_id) @@ -65,19 +57,69 @@ class HubDistribQueueHandler extends QueueHandler $feed = common_local_url('ApiTimelineGroup', array('id' => $group_id, 'format' => 'atom')); + $this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id, $notice); + } + + /** + * @param string $feed URI to the feed + * @param callable $callback function to generate Atom feed update if needed + * any additional params are passed to the callback. + */ + function pushFeed($feed, $callback) + { + $hub = common_config('ostatus', 'hub'); + if ($hub) { + $this->pushFeedExternal($feed, $hub); + } + $sub = new HubSub(); $sub->topic = $feed; if ($sub->find()) { - common_log(LOG_INFO, "Building PuSH feed for $feed"); - $atom = $this->groupFeedForNotice($group_id, $notice); - $this->pushFeeds($atom, $sub); + $args = array_slice(func_get_args(), 2); + $atom = call_user_func_array($callback, $args); + $this->pushFeedInternal($atom, $sub); } else { common_log(LOG_INFO, "No PuSH subscribers for $feed"); } + return true; } - - function pushFeeds($atom, $sub) + /** + * Ping external hub about this update. + * The hub will pull the feed and check for new items later. + * Not guaranteed safe in an environment with database replication. + * + * @param string $feed feed topic URI + * @param string $hub PuSH hub URI + * @fixme can consolidate pings for user & group posts + */ + function pushFeedExternal($feed, $hub) + { + $client = new HTTPClient(); + try { + $data = array('hub.mode' => 'publish', + 'hub.url' => $feed); + $response = $client->post($hub, array(), $data); + if ($response->getStatus() == 204) { + common_log(LOG_INFO, "PuSH ping to hub $hub for $feed ok"); + return true; + } else { + common_log(LOG_ERR, "PuSH ping to hub $hub for $feed failed with HTTP " . + $response->getStatus() . ': ' . + $response->getBody()); + } + } catch (Exception $e) { + common_log(LOG_ERR, "PuSH ping to hub $hub for $feed failed: " . $e->getMessage()); + return false; + } + } + + /** + * Queue up direct feed update pushes to subscribers on our internal hub. + * @param string $atom update feed, containing only new/changed items + * @param HubSub $sub open query of subscribers + */ + function pushFeedInternal($atom, $sub) { common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic"); $qm = QueueManager::get(); -- cgit v1.2.3-54-g00ecf From 71151b2583d81e28c5f5d42a690c649f4e84f3bf Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 11 Feb 2010 00:09:20 +0000 Subject: OStatus: garbage collect unused PuSH subscriptions when the last local subscriber unsubs --- plugins/OStatus/OStatusPlugin.php | 23 ++++++++++++++++++++--- plugins/OStatus/actions/pushcallback.php | 19 ++++++++++++------- plugins/OStatus/classes/Feedinfo.php | 22 ++++++++++++++++------ 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 4b9b4d2c3..ce02393e4 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -234,13 +234,30 @@ class OStatusPlugin extends Plugin } } + + /** + * Garbage collect unused feeds on unsubscribe + */ + function onEndUnsubscribe($user, $other) + { + $feed = Feedinfo::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"); + $feed->unsubscribe(); + } + } + return true; + } + function onCheckSchema() { - // warning: the autoincrement doesn't seem to set. - // alter table feedinfo change column id id int(11) not null auto_increment; $schema = Schema::get(); $schema->ensureTable('feedinfo', Feedinfo::schemaDef()); $schema->ensureTable('hubsub', HubSub::schemaDef()); return true; - } + } } diff --git a/plugins/OStatus/actions/pushcallback.php b/plugins/OStatus/actions/pushcallback.php index a5e02e08f..471d079ab 100644 --- a/plugins/OStatus/actions/pushcallback.php +++ b/plugins/OStatus/actions/pushcallback.php @@ -91,15 +91,20 @@ class PushCallbackAction extends Action #} // OK! - common_log(LOG_INFO, __METHOD__ . ': sub confirmed'); - $feedinfo->sub_start = common_sql_date(time()); - if ($lease_seconds > 0) { - $feedinfo->sub_end = common_sql_date(time() + $lease_seconds); + if ($mode == 'subscribe') { + common_log(LOG_INFO, __METHOD__ . ': sub confirmed'); + $feedinfo->sub_start = common_sql_date(time()); + if ($lease_seconds > 0) { + $feedinfo->sub_end = common_sql_date(time() + $lease_seconds); + } else { + $feedinfo->sub_end = null; + } + $feedinfo->update(); } else { - $feedinfo->sub_end = null; + common_log(LOG_INFO, __METHOD__ . ": unsub confirmed; deleting sub record for $topic"); + $feedinfo->delete(); } - $feedinfo->update(); - + print $challenge; } } diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index 2344a4a0e..d3cccd42f 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -112,9 +112,9 @@ class Feedinfo extends Memcached_DataObject /*extra*/ null, /*auto_increment*/ true), new ColumnDef('profile_id', 'integer', - null, true), + null, true, 'UNI'), new ColumnDef('group_id', 'integer', - null, true), + null, true, 'UNI'), new ColumnDef('feeduri', 'varchar', 255, false, 'UNI'), new ColumnDef('homeuri', 'varchar', @@ -160,7 +160,7 @@ class Feedinfo extends Memcached_DataObject function keyTypes() { - return array('id' => 'K', 'feeduri' => 'U'); // @fixme we'll need a profile_id key at least + return array('id' => 'K', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U'); } function sequenceKey() @@ -261,11 +261,11 @@ class Feedinfo extends Memcached_DataObject /** * Send a subscription request to the hub for this feed. - * The hub will later send us a confirmation POST to /feedsub/callback. + * The hub will later send us a confirmation POST to /main/push/callback. * * @return bool true on success, false on failure */ - public function subscribe() + public function subscribe($mode='subscribe') { if (common_config('feedsub', 'nohub')) { // Fake it! We're just testing remote feeds w/o hubs. @@ -278,7 +278,7 @@ class Feedinfo extends Memcached_DataObject try { $callback = common_local_url('pushcallback', array('feed' => $this->id)); $headers = array('Content-Type: application/x-www-form-urlencoded'); - $post = array('hub.mode' => 'subscribe', + $post = array('hub.mode' => $mode, 'hub.callback' => $callback, 'hub.verify' => 'async', 'hub.verify_token' => $this->verify_token, @@ -308,6 +308,16 @@ class Feedinfo extends Memcached_DataObject } } + /** + * Send an 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 + */ + public function unsubscribe() { + return $this->subscribe('unsubscribe'); + } + /** * Read and post notices for updates from the feed. * Currently assumes that all items in the feed are new, -- cgit v1.2.3-54-g00ecf From 20714d1f35305cc29c2b657310c2e0db290fbb48 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 11 Feb 2010 19:44:03 +0000 Subject: OStatus fix: include feed profile at notice text processing time, fixes replies --- plugins/OStatus/classes/Feedinfo.php | 3 +-- plugins/OStatus/lib/feedmunger.php | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index d3cccd42f..e71b0cfa0 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -356,7 +356,6 @@ class Feedinfo extends Memcached_DataObject // @fixme this might sort in wrong order if we get multiple updates $notice = $munger->notice($index); - $notice->profile_id = $this->profile_id; // Double-check for oldies // @fixme this could explode horribly for multiple feeds on a blog. sigh @@ -368,7 +367,7 @@ class Feedinfo extends Memcached_DataObject } // @fixme need to ensure that groups get handled correctly - $saved = Notice::saveNew($this->profile_id, + $saved = Notice::saveNew($notice->profile_id, $notice->content, 'ostatus', array('is_local' => Notice::REMOTE_OMB, diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index 5dce95342..7f223cb20 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -154,6 +154,11 @@ class FeedMunger { return $this->getAtomLink($this->feed, array('rel' => 'hub')); } + + function getSelfLink() + { + return $this->getAtomLink($this->feed, array('rel' => 'self')); + } /** * Get an appropriate avatar image source URL, if available. @@ -209,6 +214,7 @@ class FeedMunger $notice->id = -1; } else { $notice = new Notice(); + $notice->profile_id = $this->profileIdForEntry($index); } $link = $this->getAltLink($entry); @@ -239,6 +245,20 @@ class FeedMunger return $notice; } + function profileIdForEntry($index=1) + { + // hack hack hack + // should get profile for this entry's author... + $feed = new Feedinfo(); + $feed->feeduri = $self; + $feed = Feedinfo::staticGet('feeduri', $this->getSelfLink()); + if ($feed) { + return $feed->profile_id; + } else { + throw new Exception("Can't find feed profile"); + } + } + /** * @param feed item $entry * @return mixed Location or false -- cgit v1.2.3-54-g00ecf From 21bfbc43ad026afdab4040713805913000fef626 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 11 Feb 2010 20:02:17 +0000 Subject: OStatus: fix salmon link on Atom feeds; add a url spec for group feeds as well (endpoint needs impl) --- plugins/OStatus/OStatusPlugin.php | 51 ++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index ce02393e4..c0f9dadc4 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -80,6 +80,9 @@ class OStatusPlugin extends Plugin $m->connect('main/salmon/user/:id', array('action' => 'salmon'), array('id' => '[0-9]+')); + $m->connect('main/salmon/group/:id', + array('action' => 'salmongroup'), + array('id' => '[0-9]+')); return true; } @@ -111,25 +114,31 @@ class OStatusPlugin extends Plugin */ function onStartApiAtom(Action $action) { - if ($action instanceof ApiTimelineUserAction || $action instanceof ApiTimelineGroupAction) { - $id = $action->arg('id'); - if (strval(intval($id)) === strval($id)) { - // Canonical form of id in URL? These are used for OStatus syndication. + if ($action instanceof ApiTimelineUserAction) { + $salmonAction = 'salmon'; + } else if ($action instanceof ApiTimelineGroupAction) { + $salmonAction = 'salmongroup'; + } else { + return; + } - $hub = common_config('ostatus', 'hub'); - if (empty($hub)) { - // Updates will be handled through our internal PuSH hub. - $hub = common_local_url('pushhub'); - } - $action->element('link', array('rel' => 'hub', - 'href' => $hub)); + $id = $action->arg('id'); + if (strval(intval($id)) === strval($id)) { + // Canonical form of id in URL? These are used for OStatus syndication. - // Also, we'll add in the salmon link - $action->element('link', array('rel' => 'salmon', - 'href' => common_local_url('salmon'))); + $hub = common_config('ostatus', 'hub'); + if (empty($hub)) { + // Updates will be handled through our internal PuSH hub. + $hub = common_local_url('pushhub'); } + $action->element('link', array('rel' => 'hub', + 'href' => $hub)); + + // Also, we'll add in the salmon link + $salmon = common_local_url($salmonAction, array('id' => $id)); + $action->element('link', array('rel' => 'salmon', + 'href' => $salmon)); } - return true; } /** @@ -191,14 +200,17 @@ class OStatusPlugin extends Plugin array('nickname' => $profile->nickname)); $output->element('a', array('href' => $url, 'class' => 'entity_remote_subscribe'), - _('OStatus')); + _m('OStatus')); $output->elementEnd('li'); } } /** - * Check if we've got some Salmon stuff to send + * Check if we've got remote replies to send via Salmon. + * + * @fixme push webfinger lookup & sending to a background queue + * @fixme also detect short-form name for remote subscribees where not ambiguous */ function onEndNoticeSave($notice) { @@ -233,7 +245,6 @@ class OStatusPlugin extends Plugin } } } - /** * Garbage collect unused feeds on unsubscribe @@ -253,7 +264,9 @@ class OStatusPlugin extends Plugin return true; } - + /** + * Make sure necessary tables are filled out. + */ function onCheckSchema() { $schema = Schema::get(); $schema->ensureTable('feedinfo', Feedinfo::schemaDef()); -- cgit v1.2.3-54-g00ecf From 1773d12a24d2720cdb6c1b517999cac1f708b355 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 11 Feb 2010 20:12:48 +0000 Subject: OStatus: save Salmon postback URI in feed subscription info, if provided. Will need it for sub/unsub postbacks and other notifications. --- plugins/OStatus/classes/Feedinfo.php | 9 ++++++--- plugins/OStatus/lib/feedmunger.php | 11 ++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php index e71b0cfa0..5b8a9039a 100644 --- a/plugins/OStatus/classes/Feedinfo.php +++ b/plugins/OStatus/classes/Feedinfo.php @@ -93,11 +93,12 @@ class Feedinfo extends Memcached_DataObject 'group_id' => DB_DATAOBJECT_INT, 'feeduri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'homeuri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, - 'huburi' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, + 'huburi' => DB_DATAOBJECT_STR, 'secret' => DB_DATAOBJECT_STR, 'verify_token' => 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, + '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); } @@ -119,8 +120,8 @@ class Feedinfo extends Memcached_DataObject 255, false, 'UNI'), new ColumnDef('homeuri', 'varchar', 255, false), - new ColumnDef('huburi', 'varchar', - 255, false), + new ColumnDef('huburi', 'text', + null, true), new ColumnDef('verify_token', 'varchar', 32, true), new ColumnDef('secret', 'varchar', @@ -129,6 +130,8 @@ class Feedinfo extends Memcached_DataObject null, true), new ColumnDef('sub_end', 'datetime', null, true), + new ColumnDef('salmonuri', 'text', + null, true), new ColumnDef('created', 'datetime', null, false), new ColumnDef('lastupdate', 'datetime', diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index 7f223cb20..25b0a0931 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -89,6 +89,10 @@ class FeedMunger $feedinfo->feeduri = $this->url; $feedinfo->homeuri = $this->feed->link; $feedinfo->huburi = $this->getHubLink(); + $salmon = $this->getSalmonLink(); + if ($salmon) { + $feedinfo->salmonuri = $salmon; + } return $feedinfo; } @@ -154,7 +158,12 @@ class FeedMunger { 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')); -- cgit v1.2.3-54-g00ecf From ce3c3be1bf971329f82bedbf3aae636e3c8ecbf9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 10 Feb 2010 14:24:16 -0800 Subject: Utility classes for atom feeds --- actions/apitimelineuser.php | 39 +++++++++- lib/atom10entry.php | 58 +++++++++++++++ lib/atom10feed.php | 177 ++++++++++++++++++++++++++++++++++++++++++++ lib/atomnoticefeed.php | 34 +++++++++ 4 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 lib/atom10entry.php create mode 100644 lib/atom10feed.php create mode 100644 lib/atomnoticefeed.php diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index ed9104905..bcc48f59c 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -145,7 +145,26 @@ class ApiTimelineUserAction extends ApiBareAuthAction ); break; case 'atom': + + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed(); + + $atom->addLink( + common_local_url( + 'showstream', + array('nickname' => $this->user->nickname) + ) + ); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + $id = $this->arg('id'); + if ($id) { $selfuri = common_root_url() . 'api/statuses/user_timeline/' . @@ -154,10 +173,24 @@ class ApiTimelineUserAction extends ApiBareAuthAction $selfuri = common_root_url() . 'api/statuses/user_timeline.atom'; } - $this->showAtomTimeline( - $this->notices, $title, $id, $link, - $subtitle, $suplink, $selfuri, $logo + + $atom->addLink( + $selfuri, + array('rel' => 'self', 'type' => 'application/atom+xml') + ); + + $atom->addLink( + $suplink, + array( + 'rel' => 'http://api.friendfeed.com/2008/03#sup', + 'type' => 'application/json' + ) ); + + $atom->addEntryFromNotices($this->notices); + + print $atom->getString(); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/lib/atom10entry.php b/lib/atom10entry.php new file mode 100644 index 000000000..1b79ce7ad --- /dev/null +++ b/lib/atom10entry.php @@ -0,0 +1,58 @@ +namespaces = array(); + } + + function addNamespace($namespace, $uri) + { + $ns = array($namespace => $uri); + $this->namespaces = array_merge($this->namespaces, $ns); + } + + function initEntry() + { + + } + + function endEntry() + { + + } + + function validate + { + + } + + function getString() + { + $this->validate(); + + $this->initEntry(); + $this->renderEntries(); + $this->endEntry(); + + return $this->xw->outputMemory(); + } + +} \ No newline at end of file diff --git a/lib/atom10feed.php b/lib/atom10feed.php new file mode 100644 index 000000000..9dd8ebc9b --- /dev/null +++ b/lib/atom10feed.php @@ -0,0 +1,177 @@ +namespaces = array(); + $this->links = array(); + $this->entries = array(); + $this->addNamespace('xmlns', 'http://www.w3.org/2005/Atom'); + } + + function addNamespace($namespace, $uri) + { + $ns = array($namespace => $uri); + $this->namespaces = array_merge($this->namespaces, $ns); + } + + function getNamespaces() + { + return $this->namespaces; + } + + function initFeed() + { + $this->xw->startDocument('1.0', 'UTF-8'); + $commonAttrs = array('xml:lang' => 'en-US'); + $commonAttrs = array_merge($commonAttrs, $this->namespaces); + $this->elementStart('feed', $commonAttrs); + + $this->element('id', null, $this->id); + $this->element('title', null, $this->title); + $this->element('subtitle', null, $this->subtitle); + $this->element('logo', null, $this->logo); + $this->element('updated', null, $this->updated); + + $this->renderLinks(); + } + + /** + * Check that all required elements have been set, etc. + * Throws an Atom10FeedException if something's missing. + * + * @return void + */ + function validate() + { + } + + function renderLinks() + { + foreach ($this->links as $attrs) + { + $this->element('link', $attrs, null); + } + } + + function addEntryRaw($entry) + { + array_push($this->entries, $entry); + } + + function addEntry($entry) + { + array_push($this->entries, $entry->getString()); + } + + function renderEntries() + { + foreach ($this->entries as $entry) { + $this->raw($entry); + } + } + + function endFeed() + { + $this->elementEnd('feed'); + $this->xw->endDocument(); + } + + function getString() + { + $this->validate(); + + $this->initFeed(); + $this->renderEntries(); + $this->endFeed(); + + return $this->xw->outputMemory(); + } + + function setId($id) + { + $this->id = $id; + } + + function setTitle($title) + { + $this->title = $title; + } + + function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } + + function setLogo($logo) + { + $this->logo = $logo; + } + + function setUpdated($dt) + { + $this->updated = common_date_iso8601($dt); + } + + function setPublished($dt) + { + $this->published = common_date_iso8601($dt); + } + + /** + * Adds a link element into the Atom document + * + * Assumes you want rel="alternate" and type="text/html" unless + * you send in $otherAttrs. + * + * @param string $uri the uri the href need to point to + * @param array $otherAttrs other attributes to stick in + * + * @return void + */ + function addLink($uri, $otherAttrs = null) { + $attrs = array('href' => $uri); + + if (is_null($otherAttrs)) { + $attrs['rel'] = 'alternate'; + $attrs['type'] = 'text/html'; + } else { + $attrs = array_merge($attrs, $otherAttrs); + } + + array_push($this->links, $attrs); + } + +} + + + diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php new file mode 100644 index 000000000..a28c9cda7 --- /dev/null +++ b/lib/atomnoticefeed.php @@ -0,0 +1,34 @@ +addNamespace( + 'xmlns:thr', + 'http://purl.org/syndication/thread/1.0' + ); + } + + function addEntryFromNotices($notices) + { + if (is_array($notices)) { + foreach ($notices as $notice) { + $this->addEntryFromNotice($notice); + } + } else { + while ($notices->fetch()) { + $this->addEntryFromNotice($notice); + } + } + } + + function addEntryFromNotice($notice) + { + $this->addEntryRaw($notice->asAtomEntry()); + } + +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From e2c0f59414dd7e9a33ffbae7307b81a85c2c168b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 10 Feb 2010 18:55:14 -0800 Subject: Some upgrades to Atom output for OStatus --- actions/apitimelineuser.php | 2 +- classes/Notice.php | 38 +++++++++++++++++++++++++++-------- classes/Profile.php | 49 +++++++++++++++++++++++++++++++++++++++++++++ lib/atom10feed.php | 2 +- lib/atomnoticefeed.php | 18 ++++++++++++++++- 5 files changed, 98 insertions(+), 11 deletions(-) diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index bcc48f59c..cb8213619 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -189,7 +189,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction $atom->addEntryFromNotices($this->notices); - print $atom->getString(); + $this->raw($atom->getString()); break; case 'json': diff --git a/classes/Notice.php b/classes/Notice.php index 247440f29..091f2dc7b 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -957,7 +957,10 @@ class Notice extends Memcached_DataObject if ($namespace) { $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom', - 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'); + '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'); } else { $attrs = array(); } @@ -983,11 +986,6 @@ class Notice extends Memcached_DataObject $xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE)); } - $xs->elementStart('author'); - $xs->element('name', null, $profile->nickname); - $xs->element('uri', null, $profile->profileurl); - $xs->elementEnd('author'); - if ($source) { $xs->elementEnd('source'); } @@ -995,6 +993,9 @@ class Notice extends Memcached_DataObject $xs->element('title', null, $this->content); $xs->element('summary', null, $this->content); + $xs->raw($profile->asAtomAuthor()); + $xs->raw($profile->asActivityActor($namespace)); + $xs->element('link', array('rel' => 'alternate', 'href' => $this->bestUrl())); @@ -1014,6 +1015,29 @@ class Notice extends Memcached_DataObject } } + if (!empty($this->conversation) + && $this->conversation != $this->notice->id) { + $xs->element( + 'link', array( + 'rel' => 'osatus:conversation', + 'href' => common_local_url( + 'conversation', + array('id' => $this->conversation) + ) + ) + ); + } + + if (!empty($this->repeat_of)) { + $repeat = Notice::staticGet('id', $this->repeat_of); + if (!empty($repeat)) { + $xs->element( + 'ostatus:forward', + array('ref' => $repeat->uri, 'href' => $repeat->bestUrl()) + ); + } + } + $xs->element('content', array('type' => 'html'), $this->rendered); $tag = new Notice_tag(); @@ -1041,9 +1065,7 @@ class Notice extends Memcached_DataObject } if (!empty($this->lat) && !empty($this->lon)) { - $xs->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss')); $xs->element('georss:point', null, $this->lat . ' ' . $this->lon); - $xs->elementEnd('geo'); } $xs->elementEnd('entry'); diff --git a/classes/Profile.php b/classes/Profile.php index feabc2508..664c45f64 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -754,4 +754,53 @@ class Profile extends Memcached_DataObject return !empty($notice); } + + function asAtomAuthor() + { + $xs = new XMLStringer(true); + + $xs->elementStart('author'); + $xs->element('name', null, $this->nickname); + $xs->element('uri', null, $this->profileurl); + $xs->elementEnd('author'); + + return $xs->getString(); + } + + function asActivityActor() + { + $xs = new XMLStringer(true); + + $xs->elementStart('activity:actor'); + $xs->element( + 'activity:object-type', + null, + 'http://activitystrea.ms/schema/1.0/person' + ); + $xs->element( + 'id', + null, + common_local_url( + 'userbyid', + array('id' => $this->id) + ) + ); + $xs->element('title', null, $this->getBestName()); + + $avatar = $this->getAvatar(AVATAR_PROFILE_SIZE); + + $xs->element( + 'link', array( + 'type' => empty($avatar) ? 'image/png' : $avatar->mediatype, + 'href' => empty($avatar) + ? Avatar::defaultImage(AVATAR_PROFILE_SIZE) + : $avatar->displayUrl() + ), + '' + ); + + $xs->elementEnd('activity:actor'); + + return $xs->getString(); + } } diff --git a/lib/atom10feed.php b/lib/atom10feed.php index 9dd8ebc9b..01fc69072 100644 --- a/lib/atom10feed.php +++ b/lib/atom10feed.php @@ -153,7 +153,7 @@ class Atom10Feed extends XMLStringer * Assumes you want rel="alternate" and type="text/html" unless * you send in $otherAttrs. * - * @param string $uri the uri the href need to point to + * @param string $uri the uri the href needs to point to * @param array $otherAttrs other attributes to stick in * * @return void diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index a28c9cda7..ce87ec9e6 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -5,12 +5,28 @@ class AtomNoticeFeed extends Atom10Feed function __construct($indent = true) { parent::__construct($indent); - // Feeds containing notice info use the Atom Threading Extensions + // Feeds containing notice info use these namespaces $this->addNamespace( 'xmlns:thr', 'http://purl.org/syndication/thread/1.0' ); + + $this->addNamespace( + 'xmlns:georss', + 'http://www.georss.org/georss' + ); + + $this->addNamespace( + 'xmlns:activity', + 'http://activitystrea.ms/spec/1.0/' + ); + + // XXX: What should the uri be? + $this->addNamespace( + 'xmlns:ostatus', + 'http://ostatus.org/schema/1.0' + ); } function addEntryFromNotices($notices) -- cgit v1.2.3-54-g00ecf From c8d5c8442fe6ce54f7f65d1d0eb4203b06c09583 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 10 Feb 2010 21:21:42 -0800 Subject: Added some boilerplate class comments, etc. --- lib/atom10entry.php | 48 +++++++++++++++++++++++++++++++++++++++++ lib/atom10feed.php | 58 ++++++++++++++++++++++++++++++++++++++++++++------ lib/atomnoticefeed.php | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 7 deletions(-) diff --git a/lib/atom10entry.php b/lib/atom10entry.php index 1b79ce7ad..5710c80fc 100644 --- a/lib/atom10entry.php +++ b/lib/atom10entry.php @@ -1,9 +1,51 @@ . + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') +{ + exit(1); +} class Atom10EntryException extends Exception { } +/** + * Class for manipulating an Atom entry in memory. Get the entry as an XML + * string with Atom10Entry::getString(). + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ class Atom10Entry extends XMLStringer { private $namespaces; @@ -39,6 +81,12 @@ class Atom10Entry extends XMLStringer } + /** + * Check that all required elements have been set, etc. + * Throws an Atom10EntryException if something's missing. + * + * @return void + */ function validate { diff --git a/lib/atom10feed.php b/lib/atom10feed.php index 01fc69072..a37f6521a 100644 --- a/lib/atom10feed.php +++ b/lib/atom10feed.php @@ -1,9 +1,51 @@ . + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') +{ + exit(1); +} class Atom10FeedException extends Exception { } +/** + * Class for building an Atom feed in memory. Get the finished doc + * as a string with Atom10Feed::getString(). + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ class Atom10Feed extends XMLStringer { public $xw; @@ -23,12 +65,11 @@ class Atom10Feed extends XMLStringer private $entries; /** - * undocumented function + * Constructor * - * @param array $entries an array of FeedItems + * @param boolean $indent flag to turn indenting on or off * * @return void - * */ function __construct($indent = true) { parent::__construct($indent); @@ -38,6 +79,14 @@ class Atom10Feed extends XMLStringer $this->addNamespace('xmlns', 'http://www.w3.org/2005/Atom'); } + /** + * Add another namespace to the feed + * + * @param string $namespace the namespace + * @param string $uri namspace uri + * + * @return void + */ function addNamespace($namespace, $uri) { $ns = array($namespace => $uri); @@ -172,6 +221,3 @@ class Atom10Feed extends XMLStringer } } - - - diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index ce87ec9e6..a626ab549 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -1,5 +1,47 @@ . + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +if (!defined('STATUSNET') +{ + exit(1); +} + +/** + * Class for creating a feed that represents a collection of notices. Builds the + * feed in memory. Get the feed as a string with AtomNoticeFeed::getString(). + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ class AtomNoticeFeed extends Atom10Feed { function __construct($indent = true) { @@ -29,6 +71,12 @@ class AtomNoticeFeed extends Atom10Feed ); } + /** + * Add more than one Notice to the feed + * + * @param mixed $notices an array of Notice objects or handle + * + */ function addEntryFromNotices($notices) { if (is_array($notices)) { @@ -42,9 +90,14 @@ class AtomNoticeFeed extends Atom10Feed } } + /** + * Add a single Notice to the feed + * + * @param Notice $notice a Notice to add + */ function addEntryFromNotice($notice) { $this->addEntryRaw($notice->asAtomEntry()); } -} \ No newline at end of file +} -- cgit v1.2.3-54-g00ecf From c465f675d9dbcf9f808bc31a1d01e753df4ddf58 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 11 Feb 2010 13:54:40 -0800 Subject: Make Atom timelines in the API use Atom10feed --- actions/apitimelinefavorites.php | 69 ++++++++++++++++++++++++++-------- actions/apitimelinefriends.php | 74 ++++++++++++++++++++++++++----------- actions/apitimelinehome.php | 60 ++++++++++++++++++++++-------- actions/apitimelinementions.php | 34 ++++++++++++++--- actions/apitimelinepublic.php | 27 +++++++++++--- actions/apitimelineretweetsofme.php | 36 ++++++++++++++++-- actions/apitimelinetag.php | 51 ++++++++++++++++++------- actions/apitimelineuser.php | 25 +++++-------- lib/api.php | 18 +++++++++ lib/atom10feed.php | 8 +++- lib/atomnoticefeed.php | 4 +- 11 files changed, 308 insertions(+), 98 deletions(-) diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php index 1027d97d4..f7f900ddf 100644 --- a/actions/apitimelinefavorites.php +++ b/actions/apitimelinefavorites.php @@ -100,11 +100,11 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction function showTimeline() { - $profile = $this->user->getProfile(); - $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - $sitename = common_config('site', 'name'); - $title = sprintf( + $sitename = common_config('site', 'name'); + $title = sprintf( _('%1$s / Favorites from %2$s'), $sitename, $this->user->nickname @@ -112,32 +112,69 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction $taguribase = common_config('integration', 'taguri'); $id = "tag:$taguribase:Favorites:" . $this->user->id; - $link = common_local_url( - 'favorites', - array('nickname' => $this->user->nickname) - ); - $subtitle = sprintf( + + $subtitle = sprintf( _('%1$s updates favorited by %2$s / %2$s.'), $sitename, $profile->getBestName(), $this->user->nickname ); - $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + $logo = !empty($avatar) + ? $avatar->displayUrl() + : Avatar::defaultImage(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); + $link = common_local_url( + 'showfavorites', + array('nickname' => $this->user->nickname) + ); + $this->showRssTimeline( + $this->notices, + $title, + $link, + $subtitle, + null, + $logo + ); break; case 'atom': - $selfuri = common_root_url() . - ltrim($_SERVER['QUERY_STRING'], 'p='); - $this->showAtomTimeline( - $this->notices, $title, $id, $link, $subtitle, - null, $selfuri, $logo + + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed(); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + + $atom->addLink( + common_local_url( + 'showfavorites', + array('nickname' => $this->user->nickname) + ) + ); + + $id = $this->arg('id'); + $aargs = array('format' => 'atom'); + if (!empty($id)) { + $aargs['id'] = $id; + } + + $atom->addLink( + $this->getSelfUri('ApiTimelineFavorites', $aargs), + array('rel' => 'self', 'type' => 'application/atom+xml') ); + + $atom->addEntryFromNotices($this->notices); + + $this->raw($atom->getString()); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index 4e3827bae..0af04fe4f 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -114,39 +114,71 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction $title = sprintf(_("%s and friends"), $this->user->nickname); $taguribase = common_config('integration', 'taguri'); $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; - $link = common_local_url( - 'all', array('nickname' => $this->user->nickname) - ); - $subtitle = sprintf( - _('Updates from %1$s and friends on %2$s!'), - $this->user->nickname, $sitename - ); - $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + + $subtitle = sprintf( + _('Updates from %1$s and friends on %2$s!'), + $this->user->nickname, $sitename + ); + + $logo = (!empty($avatar)) + ? $avatar->displayUrl() + : Avatar::defaultImage(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); + + $link = common_local_url( + 'all', array( + 'nickname' => $this->user->nickname + ) + ); + + $this->showRssTimeline( + $this->notices, + $title, + $link, + $subtitle, + null, + $logo + ); break; case 'atom': - $target_id = $this->arg('id'); + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed(); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); - if (isset($target_id)) { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline/' . - $target_id . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline.atom'; + $atom->addLink( + common_local_url( + 'all', + array('nickname' => $this->user->nickname) + ) + ); + + $id = $this->arg('id'); + $aargs = array('format' => 'atom'); + if (!empty($id)) { + $aargs['id'] = $id; } - $this->showAtomTimeline( - $this->notices, $title, $id, $link, - $subtitle, null, $selfuri, $logo - ); + $atom->addLink( + $this->getSelfUri('ApiTimelineFriends', $aargs), + array('rel' => 'self', 'type' => 'application/atom+xml') + ); + + $atom->addEntryFromNotices($this->notices); + + $this->raw($atom->getString()); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/actions/apitimelinehome.php b/actions/apitimelinehome.php index 828eae6cf..ae4168070 100644 --- a/actions/apitimelinehome.php +++ b/actions/apitimelinehome.php @@ -115,39 +115,67 @@ class ApiTimelineHomeAction extends ApiBareAuthAction $title = sprintf(_("%s and friends"), $this->user->nickname); $taguribase = common_config('integration', 'taguri'); $id = "tag:$taguribase:HomeTimeline:" . $this->user->id; - $link = common_local_url( - 'all', array('nickname' => $this->user->nickname) - ); + $subtitle = sprintf( _('Updates from %1$s and friends on %2$s!'), $this->user->nickname, $sitename ); - $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + + $logo = (!empty($avatar)) + ? $avatar->displayUrl() + : Avatar::defaultImage(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); + $link = common_local_url( + 'all', + array('nickname' => $this->user->nickname) + ); + $this->showRssTimeline( + $this->notices, + $title, + $link, + $subtitle, + null, + $logo + ); break; case 'atom': - $target_id = $this->arg('id'); + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed(); - if (isset($target_id)) { - $selfuri = common_root_url() . - 'api/statuses/home_timeline/' . - $target_id . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/home_timeline.atom'; + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + + $atom->addLink( + common_local_url( + 'all', + array('nickname' => $this->user->nickname) + ) + ); + + $id = $this->arg('id'); + $aargs = array('format' => 'atom'); + if (!empty($id)) { + $aargs['id'] = $id; } - $this->showAtomTimeline( - $this->notices, $title, $id, $link, - $subtitle, null, $selfuri, $logo + $atom->addLink( + $this->getSelfUri('ApiTimelineHome', $aargs), + array('rel' => 'self', 'type' => 'application/atom+xml') ); + + $atom->addEntryFromNotices($this->notices); + $this->raw($atom->getString()); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php index 9dc2162cc..d2e31d0bd 100644 --- a/actions/apitimelinementions.php +++ b/actions/apitimelinementions.php @@ -137,12 +137,36 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); break; case 'atom': - $selfuri = common_root_url() . - ltrim($_SERVER['QUERY_STRING'], 'p='); - $this->showAtomTimeline( - $this->notices, $title, $id, $link, $subtitle, - null, $selfuri, $logo + + $atom = new AtomNoticeFeed(); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + + $atom->addLink( + common_local_url( + 'replies', + array('nickname' => $this->user->nickname) + ) + ); + + $id = $this->arg('id'); + $aargs = array('format' => 'atom'); + if (!empty($id)) { + $aargs['id'] = $id; + } + + $atom->addLink( + $this->getSelfUri('ApiTimelineMentions', $aargs), + array('rel' => 'self', 'type' => 'application/atom+xml') ); + + $atom->addEntryFromNotices($this->notices); + $this->raw($atom->getString()); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index 0fb0788e9..c1fa72a3e 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -74,7 +74,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction parent::prepare($args); $this->notices = $this->getNotices(); - + if ($this->since) { throw new ServerException("since parameter is disabled for performance; use since_id", 403); } @@ -122,11 +122,28 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo); break; case 'atom': - $selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; - $this->showAtomTimeline( - $this->notices, $title, $id, $link, - $subtitle, null, $selfuri, $sitelogo + + $atom = new AtomNoticeFeed(); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($sitelogo); + $atom->setUpdated('now'); + + $atom->addLink(common_local_url('public')); + + $atom->addLink( + $this->getSelfUri( + 'ApiTimelinePublic', array('format' => 'atom') + ), + array('rel' => 'self', 'type' => 'application/atom+xml') ); + + $atom->addEntryFromNotices($this->notices); + + $this->raw($atom->getString()); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/actions/apitimelineretweetsofme.php b/actions/apitimelineretweetsofme.php index e4b09e9bd..26706a75e 100644 --- a/actions/apitimelineretweetsofme.php +++ b/actions/apitimelineretweetsofme.php @@ -99,6 +99,8 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id); + common_debug(var_export($strm, true)); + switch ($this->format) { case 'xml': $this->showXmlTimeline($strm); @@ -112,10 +114,38 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction $title = sprintf(_("Repeats of %s"), $this->auth_user->nickname); $taguribase = common_config('integration', 'taguri'); $id = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id; - $link = common_local_url('showstream', - array('nickname' => $this->auth_user->nickname)); - $this->showAtomTimeline($strm, $title, $id, $link); + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed(); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setUpdated('now'); + + $atom->addLink( + common_local_url( + 'showstream', + array('nickname' => $this->auth_user->nickname) + ) + ); + + $id = $this->arg('id'); + $aargs = array('format' => 'atom'); + if (!empty($id)) { + $aargs['id'] = $id; + } + + $atom->addLink( + $this->getSelfUri('ApiTimelineRetweetsOfMe', $aargs), + array('rel' => 'self', 'type' => 'application/atom+xml') + ); + + $atom->addEntryFromNotices($strm); + + $this->raw($atom->getString()); + break; default: diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index 1427d23b6..5b6ded4c0 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -100,10 +100,6 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction $sitename = common_config('site', 'name'); $sitelogo = (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'); $title = sprintf(_("Notices tagged with %s"), $this->tag); - $link = common_local_url( - 'tag', - array('tag' => $this->tag) - ); $subtitle = sprintf( _('Updates tagged with %1$s on %2$s!'), $this->tag, @@ -117,22 +113,51 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo); - break; - case 'atom': - $selfuri = common_root_url() . - 'api/statusnet/tags/timeline/' . - $this->tag . '.atom'; - $this->showAtomTimeline( + $link = common_local_url( + 'tag', + array('tag' => $this->tag) + ); + $this->showRssTimeline( $this->notices, $title, - $id, $link, $subtitle, null, - $selfuri, $sitelogo ); + break; + case 'atom': + + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed(); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + + $atom->addLink( + common_local_url( + 'tag', + array('tag' => $this->tag) + ) + ); + + $aargs = array('format' => 'atom'); + if (!empty($this->tag)) { + $aargs['tag'] = $this->tag; + } + + $atom->addLink( + $this->getSelfUri('ApiTimelineTag', $aargs), + array('rel' => 'self', 'type' => 'application/atom+xml') + ); + + $atom->addEntryFromNotices($this->notices); + $this->raw($atom->getString()); + break; case 'json': $this->showJsonTimeline($this->notices); diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index cb8213619..d20bb0d20 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -150,6 +150,12 @@ class ApiTimelineUserAction extends ApiBareAuthAction $atom = new AtomNoticeFeed(); + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + $atom->addLink( common_local_url( 'showstream', @@ -157,25 +163,14 @@ class ApiTimelineUserAction extends ApiBareAuthAction ) ); - $atom->setId($id); - $atom->setTitle($title); - $atom->setSubtitle($subtitle); - $atom->setLogo($logo); - $atom->setUpdated('now'); - $id = $this->arg('id'); - - if ($id) { - $selfuri = common_root_url() . - 'api/statuses/user_timeline/' . - rawurlencode($id) . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/user_timeline.atom'; + $aargs = array('format' => 'atom'); + if (!empty($id)) { + $aargs['id'] = $id; } $atom->addLink( - $selfuri, + $this->getSelfUri('ApiTimelineUser', $aargs), array('rel' => 'self', 'type' => 'application/atom+xml') ); diff --git a/lib/api.php b/lib/api.php index fd07bbbbe..8f1fe1ef7 100644 --- a/lib/api.php +++ b/lib/api.php @@ -1321,4 +1321,22 @@ class ApiAction extends Action } } + function getSelfUri($action, $aargs) + { + parse_str($_SERVER['QUERY_STRING'], $params); + $pstring = ''; + if (!empty($params)) { + unset($params['p']); + $pstring = http_build_query($params); + } + + $uri = common_local_url($action, $aargs); + + if (!empty($pstring)) { + $uri .= '?' . $pstring; + } + + return $uri; + } + } diff --git a/lib/atom10feed.php b/lib/atom10feed.php index a37f6521a..ccca76a09 100644 --- a/lib/atom10feed.php +++ b/lib/atom10feed.php @@ -27,7 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') +if (!defined('STATUSNET')) { exit(1); } @@ -108,7 +108,11 @@ class Atom10Feed extends XMLStringer $this->element('id', null, $this->id); $this->element('title', null, $this->title); $this->element('subtitle', null, $this->subtitle); - $this->element('logo', null, $this->logo); + + if (!empty($this->logo)) { + $this->element('logo', null, $this->logo); + } + $this->element('updated', null, $this->updated); $this->renderLinks(); diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index a626ab549..34ed44b2e 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -27,7 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') +if (!defined('STATUSNET')) { exit(1); } @@ -85,7 +85,7 @@ class AtomNoticeFeed extends Atom10Feed } } else { while ($notices->fetch()) { - $this->addEntryFromNotice($notice); + $this->addEntryFromNotice($notices); } } } -- cgit v1.2.3-54-g00ecf From e08657d56cc10d2cf45e9e5701856d8a0dc7351e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 11 Feb 2010 22:42:36 +0000 Subject: OStatus: correct parsing of georss:point for max interop (commas allowed, whitespace not strictly defined) --- plugins/OStatus/lib/feedmunger.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index 25b0a0931..927a2fe7a 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -269,6 +269,9 @@ class FeedMunger } /** + * Parse location given as a GeoRSS-simple point, if provided. + * http://www.georss.org/simple + * * @param feed item $entry * @return mixed Location or false */ @@ -278,7 +281,10 @@ class FeedMunger $points = $dom->getElementsByTagNameNS('http://www.georss.org/georss', 'point'); for ($i = 0; $i < $points->length; $i++) { - $point = trim($points->item(0)->textContent); + $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; -- cgit v1.2.3-54-g00ecf From 8e6b52e8994ce9a3180554f999bdc89b414fc892 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Feb 2010 00:22:16 +0000 Subject: OStatus: renamed feedinfo table to ostatus_profile -- will cover remote ostatus people and groups whether a subscription's active or not (maintains identity over unsub/resub, and between subscribers and subscribees) --- plugins/OStatus/OStatusPlugin.php | 6 +- plugins/OStatus/actions/feedsubsettings.php | 20 +- plugins/OStatus/actions/ostatussub.php | 16 +- plugins/OStatus/actions/pushcallback.php | 22 +- plugins/OStatus/classes/Feedinfo.php | 408 --------------------------- plugins/OStatus/classes/Ostatus_profile.php | 415 ++++++++++++++++++++++++++++ plugins/OStatus/lib/feedmunger.php | 18 +- 7 files changed, 455 insertions(+), 450 deletions(-) delete mode 100644 plugins/OStatus/classes/Feedinfo.php create mode 100644 plugins/OStatus/classes/Ostatus_profile.php diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index c0f9dadc4..8444c3d73 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -251,14 +251,14 @@ class OStatusPlugin extends Plugin */ function onEndUnsubscribe($user, $other) { - $feed = Feedinfo::staticGet('profile_id', $other->id); + $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"); - $feed->unsubscribe(); + $profile->unsubscribe(); } } return true; @@ -269,7 +269,7 @@ class OStatusPlugin extends Plugin */ function onCheckSchema() { $schema = Schema::get(); - $schema->ensureTable('feedinfo', Feedinfo::schemaDef()); + $schema->ensureTable('ostatus_profile', Ostatus_profile::schemaDef()); $schema->ensureTable('hubsub', HubSub::schemaDef()); return true; } diff --git a/plugins/OStatus/actions/feedsubsettings.php b/plugins/OStatus/actions/feedsubsettings.php index 6f592bf5b..af8bf4d25 100644 --- a/plugins/OStatus/actions/feedsubsettings.php +++ b/plugins/OStatus/actions/feedsubsettings.php @@ -182,9 +182,9 @@ class FeedSubSettingsAction extends ConnectSettingsAction } $this->munger = $discover->feedMunger(); - $this->feedinfo = $this->munger->feedInfo(); + $this->profile = $this->munger->ostatusProfile(); - if ($this->feedinfo->huburi == '' && !common_config('feedsub', 'nohub')) { + if ($this->profile->huburi == '' && !common_config('feedsub', 'nohub')) { $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.')); return false; } @@ -196,13 +196,13 @@ class FeedSubSettingsAction extends ConnectSettingsAction { if ($this->validateFeed()) { $this->preview = true; - $this->feedinfo = Feedinfo::ensureProfile($this->munger); + $this->profile = Ostatus_profile::ensureProfile($this->munger); // If not already in use, subscribe to updates via the hub - if ($this->feedinfo->sub_start) { - common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}"); + 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->feedinfo->subscribe(); + $ok = $this->profile->subscribe(); common_log(LOG_INFO, __METHOD__ . ": sub was $ok"); if (!$ok) { $this->showForm(_m('Feed subscription failed! Bad response from hub.')); @@ -212,15 +212,15 @@ class FeedSubSettingsAction extends ConnectSettingsAction // And subscribe the current user to the local profile $user = common_current_user(); - $profile = $this->feedinfo->getProfile(); + $profile = $this->profile->getLocalProfile(); if (!$profile) { throw new ServerException("Feed profile was not saved properly."); } - if ($this->feedinfo->isGroup()) { + if ($this->profile->isGroup()) { if ($user->isMember($profile)) { $this->showForm(_m('Already a member!')); - } elseif (Group_member::join($this->feedinfo->group_id, $user->id)) { + } elseif (Group_member::join($this->profile->group_id, $user->id)) { $this->showForm(_m('Joined remote group!')); } else { $this->showForm(_m('Remote group join failed!')); @@ -247,7 +247,7 @@ class FeedSubSettingsAction extends ConnectSettingsAction function previewFeed() { - $feedinfo = $this->munger->feedinfo(); + $profile = $this->munger->ostatusProfile(); $notice = $this->munger->notice(0, true); // preview if ($notice) { diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index ffc4ae8df..9774286fd 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -164,9 +164,9 @@ class OStatusSubAction extends Action } $this->munger = $discover->feedMunger(); - $this->feedinfo = $this->munger->feedInfo(); + $this->profile = $this->munger->ostatusProfile(); - if ($this->feedinfo->huburi == '') { + if ($this->profile->huburi == '') { $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.')); return false; } @@ -178,13 +178,13 @@ class OStatusSubAction extends Action { if ($this->validateFeed()) { $this->preview = true; - $this->feedinfo = Feedinfo::ensureProfile($this->munger); + $this->profile = Ostatus_profile::ensureProfile($this->munger); // If not already in use, subscribe to updates via the hub - if ($this->feedinfo->sub_start) { - common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}"); + 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->feedinfo->subscribe(); + $ok = $this->profile->subscribe(); common_log(LOG_INFO, __METHOD__ . ": sub was $ok"); if (!$ok) { $this->showForm(_m('Feed subscription failed! Bad response from hub.')); @@ -194,7 +194,7 @@ class OStatusSubAction extends Action // And subscribe the current user to the local profile $user = common_current_user(); - $profile = $this->feedinfo->getProfile(); + $profile = $this->profile->getProfile(); if ($user->isSubscribed($profile)) { $this->showForm(_m('Already subscribed!')); @@ -209,7 +209,7 @@ class OStatusSubAction extends Action function previewFeed() { - $feedinfo = $this->munger->feedinfo(); + $profile = $this->munger->ostatusProfile(); $notice = $this->munger->notice(0, true); // preview if ($notice) { diff --git a/plugins/OStatus/actions/pushcallback.php b/plugins/OStatus/actions/pushcallback.php index 471d079ab..a446593ff 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); } - $feedinfo = Feedinfo::staticGet('id', $feedid); - if (!$feedinfo) { - throw new ServerException('Unknown feed id ' . $feedid, 400); + $profile = Ostatus_profile::staticGet('id', $feedid); + if (!$profile) { + throw new ServerException('Unknown OStatus/PuSH feed id ' . $feedid, 400); } $hmac = ''; @@ -59,7 +59,7 @@ class PushCallbackAction extends Action } $post = file_get_contents('php://input'); - $feedinfo->postUpdates($post, $hmac); + $profile->postUpdates($post, $hmac); } /** @@ -78,8 +78,8 @@ class PushCallbackAction extends Action throw new ServerException("Bogus hub callback: bad mode", 404); } - $feedinfo = Feedinfo::staticGet('feeduri', $topic); - if (!$feedinfo) { + $profile = Ostatus_profile::staticGet('feeduri', $topic); + if (!$profile) { common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback for unknown feed $topic"); throw new ServerException("Bogus hub callback: unknown feed", 404); } @@ -93,16 +93,16 @@ class PushCallbackAction extends Action // OK! if ($mode == 'subscribe') { common_log(LOG_INFO, __METHOD__ . ': sub confirmed'); - $feedinfo->sub_start = common_sql_date(time()); + $profile->sub_start = common_sql_date(time()); if ($lease_seconds > 0) { - $feedinfo->sub_end = common_sql_date(time() + $lease_seconds); + $profile->sub_end = common_sql_date(time() + $lease_seconds); } else { - $feedinfo->sub_end = null; + $profile->sub_end = null; } - $feedinfo->update(); + $profile->update(); } else { common_log(LOG_INFO, __METHOD__ . ": unsub confirmed; deleting sub record for $topic"); - $feedinfo->delete(); + $profile->delete(); } print $challenge; diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php deleted file mode 100644 index 5b8a9039a..000000000 --- a/plugins/OStatus/classes/Feedinfo.php +++ /dev/null @@ -1,408 +0,0 @@ -. - */ - -/** - * @package FeedSubPlugin - * @maintainer Brion Vibber - */ - -/* -PuSH subscription flow: - - $feedinfo->subscribe() - generate random verification token - save to verify_token - sends a sub request to the hub... - - feedsub/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 - - feedsub/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 Feedinfo extends Memcached_DataObject -{ - public $__table = 'feedinfo'; - - public $id; - public $profile_id; - - public $feeduri; - public $homeuri; - public $huburi; - - // PuSH subscription data - public $secret; - public $verify_token; - public $sub_start; - public $sub_end; - - public $created; - public $lastupdate; - - - 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, - '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_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, - 'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, - '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); - } - - static function schemaDef() - { - return array(new ColumnDef('id', 'integer', - /*size*/ null, - /*nullable*/ false, - /*key*/ 'PRI', - /*default*/ '0', - /*extra*/ null, - /*auto_increment*/ true), - 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_start', 'datetime', - null, true), - new ColumnDef('sub_end', 'datetime', - null, true), - new ColumnDef('salmonuri', 'text', - null, true), - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('lastupdate', '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', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U'); - } - - function sequenceKey() - { - return array('id', true, false); - } - - /** - * Fetch the StatusNet-side profile for this feed - * @return Profile - */ - public function getProfile() - { - return Profile::staticGet('id', $this->profile_id); - } - - /** - * @param FeedMunger $munger - * @param boolean $isGroup is this a group record? - * @return Feedinfo - */ - public static function ensureProfile($munger) - { - $feedinfo = $munger->feedinfo(); - - $current = self::staticGet('feeduri', $feedinfo->feeduri); - if ($current) { - // @fixme we should probably update info as necessary - return $current; - } - - $feedinfo->query('BEGIN'); - - // Awful hack! Awful hack! - $feedinfo->verify = common_good_rand(16); - $feedinfo->secret = common_good_rand(32); - - try { - $profile = $munger->profile(); - $result = $profile->insert(); - if (empty($result)) { - throw new FeedDBException($profile); - } - - $avatar = $munger->getAvatar(); - if ($avatar) { - // @fixme this should be better encapsulated - // ripped from oauthstore.php (for old OMB client) - $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); - copy($avatar, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - $profile->setOriginal($filename); - } - - $feedinfo->profile_id = $profile->id; - if ($feedinfo->isGroup()) { - $group = new User_group(); - $group->nickname = $profile->nickname . '@remote'; // @fixme - $group->fullname = $profile->fullname; - $group->homepage = $profile->homepage; - $group->location = $profile->location; - $group->created = $profile->created; - $group->insert(); - - if ($avatar) { - $group->setOriginal($filename); - } - - $feedinfo->group_id = $group->id; - } - - $result = $feedinfo->insert(); - if (empty($result)) { - throw new FeedDBException($feedinfo); - } - - $feedinfo->query('COMMIT'); - } catch (FeedDBException $e) { - common_log_db_error($e->obj, 'INSERT', __FILE__); - $feedinfo->query('ROLLBACK'); - return false; - } - return $feedinfo; - } - - /** - * Damn dirty hack! - */ - function isGroup() - { - return (strpos($this->feeduri, '/groups/') !== false); - } - - /** - * 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 - */ - public function subscribe($mode='subscribe') - { - if (common_config('feedsub', 'nohub')) { - // Fake it! We're just testing remote feeds w/o hubs. - return true; - } - // @fixme use the verification token - #$token = md5(mt_rand() . ':' . $this->feeduri); - #$this->verify_token = $token; - #$this->update(); // @fixme - 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"); - return false; - } - } - - /** - * Send an 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 - */ - public function unsubscribe() { - return $this->subscribe('unsubscribe'); - } - - /** - * Read and post notices for updates from the feed. - * Currently assumes that all items in the feed are new, - * coming from a PuSH hub. - * - * @param string $xml source of Atom or RSS feed - * @param string $hmac X-Hub-Signature header, if present - */ - public function postUpdates($xml, $hmac) - { - common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $xml"); - - if ($this->secret) { - if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { - $their_hmac = strtolower($matches[1]); - $our_hmac = hash_hmac('sha1', $xml, $this->secret); - if ($their_hmac !== $our_hmac) { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac"); - return; - } - } else { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'"); - return; - } - } else if ($hmac) { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'"); - return; - } - - require_once "XML/Feed/Parser.php"; - $feed = new XML_Feed_Parser($xml, false, false, true); - $munger = new FeedMunger($feed); - - $hits = 0; - foreach ($feed as $index => $entry) { - // @fixme this might sort in wrong order if we get multiple updates - - $notice = $munger->notice($index); - - // Double-check for oldies - // @fixme this could explode horribly for multiple feeds on a blog. sigh - $dupe = new Notice(); - $dupe->uri = $notice->uri; - if ($dupe->find(true)) { - common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); - continue; - } - - // @fixme need to ensure that groups get handled correctly - $saved = Notice::saveNew($notice->profile_id, - $notice->content, - 'ostatus', - array('is_local' => Notice::REMOTE_OMB, - 'uri' => $notice->uri, - 'lat' => $notice->lat, - 'lon' => $notice->lon, - 'location_ns' => $notice->location_ns, - 'location_id' => $notice->location_id)); - - /* - common_log(LOG_DEBUG, "going to check group delivery..."); - if ($this->group_id) { - $group = User_group::staticGet($this->group_id); - if ($group) { - common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname"); - $groups = array($group); - } else { - common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?"); - } - } else { - common_log(LOG_INFO, __METHOD__ . ": no local shadow groups"); - $groups = array(); - } - common_log(LOG_DEBUG, "going to add to inboxes..."); - $notice->addToInboxes($groups, array()); - common_log(LOG_DEBUG, "added to inboxes."); - */ - - $hits++; - } - if ($hits == 0) { - common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml"); - } - } -} diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php new file mode 100644 index 000000000..748ecce18 --- /dev/null +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -0,0 +1,415 @@ +. + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +/* +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 $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 /*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, + '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, + '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); + } + + static function schemaDef() + { + return array(new ColumnDef('id', 'integer', + /*size*/ null, + /*nullable*/ false, + /*key*/ 'PRI', + /*default*/ '0', + /*extra*/ null, + /*auto_increment*/ true), + 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), + new ColumnDef('salmonuri', 'text', + null, true), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('lastupdate', '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', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U'); + } + + function sequenceKey() + { + return array('id', true, false); + } + + /** + * Fetch the StatusNet-side profile for this feed + * @return Profile + */ + public function getLocalProfile() + { + return Profile::staticGet('id', $this->profile_id); + } + + /** + * @param FeedMunger $munger + * @param boolean $isGroup is this a group record? + * @return Ostatus_profile + */ + public static function ensureProfile($munger) + { + $entity = $munger->ostatusProfile(); + + $current = self::staticGet('feeduri', $entity->feeduri); + if ($current) { + // @fixme we should probably update info as necessary + return $current; + } + + $entity->query('BEGIN'); + + // Awful hack! Awful hack! + $entity->verify = common_good_rand(16); + $entity->secret = common_good_rand(32); + + try { + $profile = $munger->profile(); + $result = $profile->insert(); + if (empty($result)) { + throw new FeedDBException($profile); + } + + $avatar = $munger->getAvatar(); + if ($avatar) { + // @fixme this should be better encapsulated + // ripped from oauthstore.php (for old OMB client) + $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); + copy($avatar, $temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + $profile->setOriginal($filename); + } + + $entity->profile_id = $profile->id; + if ($entity->isGroup()) { + $group = new User_group(); + $group->nickname = $profile->nickname . '@remote'; // @fixme + $group->fullname = $profile->fullname; + $group->homepage = $profile->homepage; + $group->location = $profile->location; + $group->created = $profile->created; + $group->insert(); + + if ($avatar) { + $group->setOriginal($filename); + } + + $entity->group_id = $group->id; + } + + $result = $entity->insert(); + if (empty($result)) { + throw new FeedDBException($entity); + } + + $entity->query('COMMIT'); + } catch (FeedDBException $e) { + common_log_db_error($e->obj, 'INSERT', __FILE__); + $entity->query('ROLLBACK'); + return false; + } + return $entity; + } + + /** + * Damn dirty hack! + */ + function isGroup() + { + return (strpos($this->feeduri, '/groups/') !== false); + } + + /** + * 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 + */ + public function subscribe($mode='subscribe') + { + if (common_config('feedsub', 'nohub')) { + // Fake it! We're just testing remote feeds w/o hubs. + return true; + } + // @fixme use the verification token + #$token = md5(mt_rand() . ':' . $this->feeduri); + #$this->verify_token = $token; + #$this->update(); // @fixme + 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"); + return false; + } + } + + /** + * Send an 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 + */ + public function unsubscribe() { + return $this->subscribe('unsubscribe'); + } + + /** + * Read and post notices for updates from the feed. + * Currently assumes that all items in the feed are new, + * coming from a PuSH hub. + * + * @param string $xml source of Atom or RSS feed + * @param string $hmac X-Hub-Signature header, if present + */ + public function postUpdates($xml, $hmac) + { + common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $xml"); + + if ($this->secret) { + if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { + $their_hmac = strtolower($matches[1]); + $our_hmac = hash_hmac('sha1', $xml, $this->secret); + if ($their_hmac !== $our_hmac) { + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac"); + return; + } + } else { + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'"); + return; + } + } else if ($hmac) { + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'"); + return; + } + + require_once "XML/Feed/Parser.php"; + $feed = new XML_Feed_Parser($xml, false, false, true); + $munger = new FeedMunger($feed); + + $hits = 0; + foreach ($feed as $index => $entry) { + // @fixme this might sort in wrong order if we get multiple updates + + $notice = $munger->notice($index); + + // Double-check for oldies + // @fixme this could explode horribly for multiple feeds on a blog. sigh + $dupe = new Notice(); + $dupe->uri = $notice->uri; + if ($dupe->find(true)) { + common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); + continue; + } + + // @fixme need to ensure that groups get handled correctly + $saved = Notice::saveNew($notice->profile_id, + $notice->content, + 'ostatus', + array('is_local' => Notice::REMOTE_OMB, + 'uri' => $notice->uri, + 'lat' => $notice->lat, + 'lon' => $notice->lon, + 'location_ns' => $notice->location_ns, + 'location_id' => $notice->location_id)); + + /* + common_log(LOG_DEBUG, "going to check group delivery..."); + if ($this->group_id) { + $group = User_group::staticGet($this->group_id); + if ($group) { + common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname"); + $groups = array($group); + } else { + common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?"); + } + } else { + common_log(LOG_INFO, __METHOD__ . ": no local shadow groups"); + $groups = array(); + } + common_log(LOG_DEBUG, "going to add to inboxes..."); + $notice->addToInboxes($groups, array()); + common_log(LOG_DEBUG, "added to inboxes."); + */ + + $hits++; + } + if ($hits == 0) { + common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml"); + } + } +} diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index 927a2fe7a..c895b6ce2 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -83,17 +83,17 @@ class FeedMunger $this->url = $url; } - function feedinfo() + function ostatusProfile() { - $feedinfo = new Feedinfo(); - $feedinfo->feeduri = $this->url; - $feedinfo->homeuri = $this->feed->link; - $feedinfo->huburi = $this->getHubLink(); + $profile = new Ostatus_profile(); + $profile->feeduri = $this->url; + $profile->homeuri = $this->feed->link; + $profile->huburi = $this->getHubLink(); $salmon = $this->getSalmonLink(); if ($salmon) { - $feedinfo->salmonuri = $salmon; + $profile->salmonuri = $salmon; } - return $feedinfo; + return $profile; } function getAtomLink($item, $attribs=array()) @@ -258,9 +258,7 @@ class FeedMunger { // hack hack hack // should get profile for this entry's author... - $feed = new Feedinfo(); - $feed->feeduri = $self; - $feed = Feedinfo::staticGet('feeduri', $this->getSelfLink()); + $remote = Ostatus_profile::staticGet('feeduri', $this->getSelfLink()); if ($feed) { return $feed->profile_id; } else { -- cgit v1.2.3-54-g00ecf From 3beddffc39e9a0bc5d32f50f4c8f93771060a032 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 11 Feb 2010 15:24:18 -0800 Subject: ostatus:attention links in Notice Atom output --- classes/Notice.php | 16 +++++++++++++++- classes/Profile.php | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 091f2dc7b..a39388cdb 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -994,7 +994,7 @@ class Notice extends Memcached_DataObject $xs->element('summary', null, $this->content); $xs->raw($profile->asAtomAuthor()); - $xs->raw($profile->asActivityActor($namespace)); + $xs->raw($profile->asActivityActor()); $xs->element('link', array('rel' => 'alternate', 'href' => $this->bestUrl())); @@ -1028,6 +1028,20 @@ class Notice extends Memcached_DataObject ); } + $reply_ids = $this->getReplies(); + + foreach ($reply_ids as $id) { + $profile = Profile::staticGet('id', $id); + if (!empty($profile)) { + $xs->element( + 'link', array( + 'rel' => 'osatus:attention', + 'href' => $profile->getAcctUri() + ) + ); + } + } + if (!empty($this->repeat_of)) { $repeat = Notice::staticGet('id', $this->repeat_of); if (!empty($repeat)) { diff --git a/classes/Profile.php b/classes/Profile.php index 664c45f64..3e5150c18 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -803,4 +803,10 @@ class Profile extends Memcached_DataObject return $xs->getString(); } + + function getAcctUri() + { + return $this->nickname . '@' . common_config('site', 'server'); + } + } -- cgit v1.2.3-54-g00ecf From 525358fa101784fa5bbbac8b214091de89ec0634 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 11 Feb 2010 17:08:50 -0800 Subject: Fix retarded spelling mistake --- classes/Notice.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index a39388cdb..924931e42 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1019,7 +1019,7 @@ class Notice extends Memcached_DataObject && $this->conversation != $this->notice->id) { $xs->element( 'link', array( - 'rel' => 'osatus:conversation', + 'rel' => 'ostatus:conversation', 'href' => common_local_url( 'conversation', array('id' => $this->conversation) @@ -1035,7 +1035,7 @@ class Notice extends Memcached_DataObject if (!empty($profile)) { $xs->element( 'link', array( - 'rel' => 'osatus:attention', + 'rel' => 'ostatus:attention', 'href' => $profile->getAcctUri() ) ); -- cgit v1.2.3-54-g00ecf From bc46621af2bea8d2f9f132f275c70c5964f880b4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Feb 2010 01:11:46 +0000 Subject: OStatus sub setup code cleanup and partial group fixes (needs more work after the Atom updates are done) --- plugins/OStatus/actions/feedsubsettings.php | 15 +-- plugins/OStatus/actions/pushcallback.php | 27 +++-- plugins/OStatus/classes/Ostatus_profile.php | 158 +++++++++++++++++++++------- 3 files changed, 138 insertions(+), 62 deletions(-) diff --git a/plugins/OStatus/actions/feedsubsettings.php b/plugins/OStatus/actions/feedsubsettings.php index af8bf4d25..6933c9bf2 100644 --- a/plugins/OStatus/actions/feedsubsettings.php +++ b/plugins/OStatus/actions/feedsubsettings.php @@ -197,6 +197,9 @@ class FeedSubSettingsAction extends ConnectSettingsAction 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) { @@ -212,13 +215,10 @@ class FeedSubSettingsAction extends ConnectSettingsAction // And subscribe the current user to the local profile $user = common_current_user(); - $profile = $this->profile->getLocalProfile(); - if (!$profile) { - throw new ServerException("Feed profile was not saved properly."); - } if ($this->profile->isGroup()) { - if ($user->isMember($profile)) { + $group = $this->profile->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!')); @@ -226,9 +226,10 @@ class FeedSubSettingsAction extends ConnectSettingsAction $this->showForm(_m('Remote group join failed!')); } } else { - if ($user->isSubscribed($profile)) { + $local = $this->profile->localProfile(); + if ($user->isSubscribed($local)) { $this->showForm(_m('Already subscribed!')); - } elseif ($user->subscribeTo($profile)) { + } elseif ($user->subscribeTo($local)) { $this->showForm(_m('Feed subscribed!')); } else { $this->showForm(_m('Feed subscription failed!')); diff --git a/plugins/OStatus/actions/pushcallback.php b/plugins/OStatus/actions/pushcallback.php index a446593ff..2601a377a 100644 --- a/plugins/OStatus/actions/pushcallback.php +++ b/plugins/OStatus/actions/pushcallback.php @@ -84,27 +84,24 @@ class PushCallbackAction extends Action throw new ServerException("Bogus hub callback: unknown feed", 404); } - # Can't currently set the token in our sub api - #if ($feedinfo->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); - #} - + 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); + } + + 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}\""); + 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->sub_start = common_sql_date(time()); - if ($lease_seconds > 0) { - $profile->sub_end = common_sql_date(time() + $lease_seconds); - } else { - $profile->sub_end = null; - } - $profile->update(); + $profile->confirmSubscribe($lease_seconds); } else { common_log(LOG_INFO, __METHOD__ . ": unsub confirmed; deleting sub record for $topic"); - $profile->delete(); + $profile->confirmUnsubscribe(); } - print $challenge; } } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 748ecce18..f7bbcd028 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -182,9 +182,24 @@ class Ostatus_profile extends Memcached_DataObject * Fetch the StatusNet-side profile for this feed * @return Profile */ - public function getLocalProfile() + public function localProfile() { - return Profile::staticGet('id', $this->profile_id); + 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; } /** @@ -194,62 +209,48 @@ class Ostatus_profile extends Memcached_DataObject */ public static function ensureProfile($munger) { - $entity = $munger->ostatusProfile(); + $profile = $munger->ostatusProfile(); - $current = self::staticGet('feeduri', $entity->feeduri); + $current = self::staticGet('feeduri', $profile->feeduri); if ($current) { // @fixme we should probably update info as necessary return $current; } - $entity->query('BEGIN'); + $profile->query('BEGIN'); // Awful hack! Awful hack! - $entity->verify = common_good_rand(16); - $entity->secret = common_good_rand(32); + $profile->verify = common_good_rand(16); + $profile->secret = common_good_rand(32); try { - $profile = $munger->profile(); - $result = $profile->insert(); - if (empty($result)) { - throw new FeedDBException($profile); - } - - $avatar = $munger->getAvatar(); - if ($avatar) { - // @fixme this should be better encapsulated - // ripped from oauthstore.php (for old OMB client) - $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); - copy($avatar, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - $profile->setOriginal($filename); - } + $local = $munger->profile(); - $entity->profile_id = $profile->id; if ($entity->isGroup()) { $group = new User_group(); - $group->nickname = $profile->nickname . '@remote'; // @fixme - $group->fullname = $profile->fullname; - $group->homepage = $profile->homepage; - $group->location = $profile->location; - $group->created = $profile->created; + $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 ($avatar) { - $group->setOriginal($filename); + if (empty($result)) { + throw new FeedDBException($group); } - - $entity->group_id = $group->id; + $profile->group_id = $group->id; + } else { + $result = $local->insert(); + if (empty($result)) { + throw new FeedDBException($local); + } + $profile->profile_id = $local->id; } - $result = $entity->insert(); + $profile->created = sql_common_date(); + $profile->lastupdate = sql_common_date(); + $result = $profile->insert(); if (empty($result)) { - throw new FeedDBException($entity); + throw new FeedDBException($profile); } $entity->query('COMMIT'); @@ -258,9 +259,46 @@ class Ostatus_profile extends Memcached_DataObject $entity->query('ROLLBACK'); return false; } + + $avatar = $munger->getAvatar(); + if ($avatar) { + try { + $this->updateAvatar($avatar); + } catch (Exception $e) { + common_log(LOG_ERR, "Exception setting OStatus avatar: " . + $e->getMessage()); + } + } + return $entity; } + /** + * 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); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->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); + } + } + /** * Damn dirty hack! */ @@ -318,6 +356,46 @@ class Ostatus_profile extends Memcached_DataObject } } + /** + * 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_date(); + + return $this->update($original); + } + + /** + * Save PuSH unsubscription confirmation. + * Wipes active PuSH sub info and resets state. + */ + 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); + } + /** * Send an unsubscription request to the hub for this feed. * The hub will later send us a confirmation POST to /main/push/callback. -- cgit v1.2.3-54-g00ecf From 5f94efc45463378f246f9db82e9f2e0e8a109f7d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 12 Feb 2010 00:42:42 -0500 Subject: stub for activities --- plugins/OStatus/lib/activity.php | 85 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 plugins/OStatus/lib/activity.php diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php new file mode 100644 index 000000000..36e227913 --- /dev/null +++ b/plugins/OStatus/lib/activity.php @@ -0,0 +1,85 @@ +. + * + * @category OStatus + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ActivityNoun +{ + const ARTICLE = 'http://activitystrea.ms/schema/1.0/article'; + const BLOGENTRY = 'http://activitystrea.ms/schema/1.0/blog-entry'; + const NOTE = 'http://activitystrea.ms/schema/1.0/note'; + const STATUS = 'http://activitystrea.ms/schema/1.0/status'; + const FILE = 'http://activitystrea.ms/schema/1.0/file'; + const PHOTO = 'http://activitystrea.ms/schema/1.0/photo'; + const ALBUM = 'http://activitystrea.ms/schema/1.0/photo-album'; + const PLAYLIST = 'http://activitystrea.ms/schema/1.0/playlist'; + const VIDEO = 'http://activitystrea.ms/schema/1.0/video'; + const AUDIO = 'http://activitystrea.ms/schema/1.0/audio'; + const BOOKMARK = 'http://activitystrea.ms/schema/1.0/bookmark'; + const PERSON = 'http://activitystrea.ms/schema/1.0/person'; + const GROUP = 'http://activitystrea.ms/schema/1.0/group'; + const PLACE = 'http://activitystrea.ms/schema/1.0/place'; + const COMMENT = 'http://activitystrea.ms/schema/1.0/comment'; // tea + + public $type; + public $id; + public $title; + public $summary; + public $content; +} + +class Activity +{ + const NAMESPACE = 'http://activitystrea.ms/schema/1.0/'; + + const POST = 'http://activitystrea.ms/schema/1.0/post'; + const SHARE = 'http://activitystrea.ms/schema/1.0/share'; + const SAVE = 'http://activitystrea.ms/schema/1.0/save'; + const FAVORITE = 'http://activitystrea.ms/schema/1.0/favorite'; + const PLAY = 'http://activitystrea.ms/schema/1.0/play'; + const FOLLOW = 'http://activitystrea.ms/schema/1.0/follow'; + 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'; + + public $actor; // an ActivityNoun + public $verb; // a string (the URL) + public $object; // an ActivityNoun + public $target; // an ActivityNoun + + static function fromAtomEntry($domEntry) + { + } + + function toAtomEntry() + { + } +} -- cgit v1.2.3-54-g00ecf From 320532560fe6fa4661d58317923c54b708c897c2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 12 Feb 2010 00:43:16 -0500 Subject: flesh out salmon endpoint --- plugins/OStatus/actions/salmon.php | 52 ++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/plugins/OStatus/actions/salmon.php b/plugins/OStatus/actions/salmon.php index 012869cf7..b616027a9 100644 --- a/plugins/OStatus/actions/salmon.php +++ b/plugins/OStatus/actions/salmon.php @@ -22,28 +22,60 @@ * @author James Walker */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('STATUSNET')) { + exit(1); +} class SalmonAction extends Action { + var $user = null; + var $xml = null; + var $activity = null; - function handle() + function prepare($args) { - parent::handle(); - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->handlePost(); + 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')); + } - function handlePost() - { - $user_id = $this->arg('id'); - common_log(LOG_DEBUG, 'Salmon: incoming post for user: '. $user_id); + $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 = Activity::fromAtomEntry($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: + } } } -- cgit v1.2.3-54-g00ecf From fd527b8de120587670eb6fdd0ecce7ca7833ea72 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 12 Feb 2010 11:34:23 +0100 Subject: Moved colour properties out of base stylesheet --- theme/base/css/display.css | 3 --- theme/default/css/display.css | 9 ++++++--- theme/identica/css/display.css | 10 +++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 8490fb580..70ddc411f 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1109,15 +1109,12 @@ right:29px; z-index:9; min-width:199px; float:none; -background-color:#FFF; padding:11px; border-radius:7px; -moz-border-radius:7px; -webkit-border-radius:7px; border-style:solid; border-width:1px; -border-color:#DDDDDD; --moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); } .dialogbox legend { diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 82eb13531..02e1645f4 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -46,7 +46,8 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3); .pagination .nav_prev a, .pagination .nav_next a, .form_settings fieldset fieldset, -.entity_moderation:hover ul { +.entity_moderation:hover ul, +.dialogbox { border-color:#DDDDDD; } @@ -221,7 +222,8 @@ border-color:transparent; #content, #site_nav_local_views .current a, .entity_send-a-message .form_notice, -.entity_moderation:hover ul { +.entity_moderation:hover ul, +.dialogbox { background-color:#FFFFFF; } @@ -308,7 +310,8 @@ background-position: 5px -718px; background-position: 5px -852px; } .entity_send-a-message .form_notice, -.entity_moderation:hover ul { +.entity_moderation:hover ul, +.dialogbox { box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 44ae4953b..6dc7d21df 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -46,7 +46,8 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3); .pagination .nav_prev a, .pagination .nav_next a, .form_settings fieldset fieldset, -.entity_moderation:hover ul { +.entity_moderation:hover ul, +.dialogbox { border-color:#DDDDDD; } @@ -88,6 +89,7 @@ color:#FFFFFF; border-color:transparent; text-shadow:none; } + .dialogbox .submit_dialogbox, input.submit, .form_notice input.submit { @@ -221,7 +223,8 @@ border-color:transparent; #content, #site_nav_local_views .current a, .entity_send-a-message .form_notice, -.entity_moderation:hover ul { +.entity_moderation:hover ul, +.dialogbox { background-color:#FFFFFF; } @@ -307,7 +310,8 @@ background-position: 5px -718px; background-position: 5px -852px; } .entity_send-a-message .form_notice, -.entity_moderation:hover ul { +.entity_moderation:hover ul, +.dialogbox { box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -- cgit v1.2.3-54-g00ecf From 42679a22dc712467db567a9bac41c58e4788dd58 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 12 Feb 2010 12:04:14 +0100 Subject: Extracted default values for dialogbox layout and uniqe for form_repeat --- theme/base/css/display.css | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 70ddc411f..990280d84 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1104,10 +1104,9 @@ left:0; .dialogbox { position:absolute; -top:-4px; -right:29px; +top:0; +right:0; z-index:9; -min-width:199px; float:none; padding:11px; border-radius:7px; @@ -1142,6 +1141,12 @@ outline:none; text-indent:-9999px; } +.form_repeat.dialogbox { +top:-4px; +right:29px; +min-width:199px; +} + .notice-options { position:relative; font-size:0.95em; -- cgit v1.2.3-54-g00ecf From b57e3dfae2f48ef6097fef04df218201783abed1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 12 Feb 2010 14:16:38 +0100 Subject: More style generalisation for dialogbox --- theme/base/css/display.css | 16 ++++++++++++++-- theme/default/css/display.css | 14 ++++++++------ theme/identica/css/display.css | 11 ++++++----- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 990280d84..3218276a6 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1104,8 +1104,8 @@ left:0; .dialogbox { position:absolute; -top:0; -right:0; +top:-1px; +right:-1px; z-index:9; float:none; padding:11px; @@ -1119,6 +1119,7 @@ border-width:1px; .dialogbox legend { display:block !important; margin-right:18px; +margin-bottom:18px; } .dialogbox button.close { @@ -1127,11 +1128,22 @@ right:3px; top:3px; } +.dialogbox .form_guide { +font-weight:normal; +padding:0; +} + .dialogbox .submit_dialogbox { font-weight:bold; text-indent:0; min-width:46px; } +.dialogbox input { +padding-left:4px; +} +.dialogbox fieldset { +margin-bottom:0; +} #wrap form.processing input.submit, .entity_actions a.processing, diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 02e1645f4..a2f101342 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -30,7 +30,9 @@ border-radius:4px; input, textarea, select, option { font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; } -input, textarea, select { +input, textarea, select, +.entity_actions .dialogbox input, +.mark-top { border-color:#AAAAAA; } @@ -79,7 +81,8 @@ background-color:transparent; input:focus, textarea:focus, select:focus, .form_notice.warning #notice_data-text, .form_notice.warning #notice_text-count, -.form_settings .form_note { +.form_settings .form_note, +.entity_actions .dialogbox .form_data input:focus { border-color:#9BB43E; } input.submit { @@ -134,9 +137,6 @@ color:#002FA7; #content tbody tr { border-top-color:#C8D1D5; } -.mark-top { -border-color:#AAAAAA; -} #aside_primary { background-color:#C8D1D5; @@ -145,7 +145,9 @@ background-color:#C8D1D5; #notice_text-count { color:#333333; } -.form_notice.warning #notice_text-count { +.form_notice.warning #notice_text-count, +.dialogbox, +.entity_actions .dialogbox input { color:#000000; } .form_notice label[for=notice_data-attach] { diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 6dc7d21df..e21404745 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -30,7 +30,9 @@ border-radius:4px; input, textarea, select, option { font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; } -input, textarea, select { +input, textarea, select, +.entity_actions .dialogbox input, +.mark-top { border-color:#AAAAAA; } @@ -135,9 +137,6 @@ color:#002FA7; #content tbody tr { border-top-color:#CEE1E9; } -.mark-top { -border-color:#AAAAAA; -} #aside_primary { background-color:#CEE1E9; @@ -146,7 +145,9 @@ background-color:#CEE1E9; #notice_text-count { color:#333333; } -.form_notice.warning #notice_text-count { +.form_notice.warning #notice_text-count, +.dialogbox, +.entity_actions .dialogbox input { color:#000000; } .form_notice label[for=notice_data-attach] { -- cgit v1.2.3-54-g00ecf From 094565b4aa1893e6a4422f3d05a0a43844e47a67 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 12 Feb 2010 18:20:13 +0100 Subject: Added 'pre' to pick up Palm Pre's UA string: Mozilla/5.0 (webOS/1.3.5.1; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0 --- plugins/MobileProfile/MobileProfilePlugin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index cd2531fa7..e9b4a05f7 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -121,6 +121,7 @@ class MobileProfilePlugin extends WAP20Plugin 'philips', 'pocketpc', 'portalmmm', + 'pre', 'rover', 'samsung', 'sanyo', -- cgit v1.2.3-54-g00ecf From 47f6b0afc9a94b5e649102dd209950b94c6ac33a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 12 Feb 2010 18:30:27 +0100 Subject: Revert "Added 'pre' to pick up Palm Pre's UA string:" This reverts commit 094565b4aa1893e6a4422f3d05a0a43844e47a67. On second thought, "pre" is probably the stupidest way of differentiating one agent from another. Need a different solution. --- plugins/MobileProfile/MobileProfilePlugin.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index e9b4a05f7..cd2531fa7 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -121,7 +121,6 @@ class MobileProfilePlugin extends WAP20Plugin 'philips', 'pocketpc', 'portalmmm', - 'pre', 'rover', 'samsung', 'sanyo', -- cgit v1.2.3-54-g00ecf From b39047d95b447251de75d15b986017286aca05e0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Feb 2010 18:54:48 +0000 Subject: OStatus: prep work for sending notifications on sub/unsub/join/leave/favorite/unfavorite via Salmon; needs to be completed and hooked up once feed gen is fixed. --- classes/Profile.php | 34 ++++++- plugins/OStatus/classes/Ostatus_profile.php | 153 +++++++++++++++++++++++++++- 2 files changed, 184 insertions(+), 3 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 3e5150c18..ab05bb854 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -755,6 +755,14 @@ class Profile extends Memcached_DataObject return !empty($notice); } + /** + * Returns an XML string fragment with limited profile information + * as an Atom element. + * + * Assumes that Atom has been previously set up as the base namespace. + * + * @return string + */ function asAtomAuthor() { $xs = new XMLStringer(true); @@ -767,11 +775,33 @@ class Profile extends Memcached_DataObject return $xs->getString(); } + /** + * Returns an XML string fragment with profile information as an + * Activity Streams element. + * + * Assumes that 'activity' namespace has been previously defined. + * + * @return string + */ function asActivityActor() + { + return $this->asActivityNoun('actor'); + } + + /** + * Returns an XML string fragment with profile information as an + * Activity Streams noun object with the given element type. + * + * Assumes that 'activity' namespace has been previously defined. + * + * @param string $element one of 'actor', 'subject', 'object', 'target' + * @return string + */ + function asActivityNoun($element) { $xs = new XMLStringer(true); - $xs->elementStart('activity:actor'); + $xs->elementStart('activity:' . $element); $xs->element( 'activity:object-type', null, @@ -799,7 +829,7 @@ class Profile extends Memcached_DataObject '' ); - $xs->elementEnd('activity:actor'); + $xs->elementEnd('activity:' . $element); return $xs->getString(); } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index f7bbcd028..733d8843b 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -299,6 +299,71 @@ class Ostatus_profile extends Memcached_DataObject } } + /** + * Returns an XML string fragment with profile information as an + * Activity Streams noun object with the given element type. + * + * Assumes that 'activity' namespace has been previously defined. + * + * @param string $element one of 'actor', 'subject', 'object', 'target' + * @return string + */ + function asActivityNoun($element) + { + $xs = new XMLStringer(true); + + $avatarHref = Avatar::defaultImage(AVATAR_PROFILE_SIZE); + $avatarType = 'image/png'; + if ($this->isGroup()) { + $type = 'http://activitystrea.ms/schema/1.0/group'; + $self = $this->localGroup(); + + // @fixme put a standard getAvatar() interface on groups too + if ($self->homepage_logo) { + $avatarHref = $self->homepage_logo; + $map = array('png' => 'image/png', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'gif' => 'image/gif'); + $extension = pathinfo(parse_url($avatarHref, PHP_URL_PATH), PATHINFO_EXTENSION); + if (isset($map[$extension])) { + $avatarType = $map[$extension]; + } + } + } else { + $type = 'http://activitystrea.ms/schema/1.0/person'; + $self = $this->localProfile(); + $avatar = $self->getAvatar(AVATAR_PROFILE_SIZE); + if ($avatar) { + $avatarHref = $avatar-> + $avatarType = $avatar->mediatype; + } + } + $xs->elementStart('activity:' . $element); + $xs->element( + 'activity:object-type', + null, + $type + ); + $xs->element( + 'id', + null, + $this->homeuri); // ? + $xs->element('title', null, $self->getBestName()); + + $xs->element( + 'link', array( + 'type' => $avatarType, + 'href' => $avatarHref + ), + '' + ); + + $xs->elementEnd('activity:' . $element); + + return $xs->getString(); + } + /** * Damn dirty hack! */ @@ -397,7 +462,7 @@ class Ostatus_profile extends Memcached_DataObject } /** - * Send an unsubscription request to the hub for this feed. + * 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 @@ -406,6 +471,92 @@ class Ostatus_profile extends Memcached_DataObject return $this->subscribe('unsubscribe'); } + /** + * 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 + */ + public function notify(Profile $actor, $verb, $object=null) + { + 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'); + $entry->element('id', null, $id); + $entry->element('title', null, $text); + $entry->element('summary', null, $text); + $entry->element('published', null, common_date_w3dtf()); + + $entry->element('activity:verb', null, $verb); + $entry->raw($profile->asAtomAuthor()); + $entry->raw($profile->asActivityActor()); + $entry->raw($object->asActivityNoun('object')); + $entry->elmentEnd('entry'); + + $feed = $this->atomFeed($actor); + $feed->initFeed(); + $feed->addEntry($entry); + $feed->renderEntries(); + $feed->endFeed(); + + $xml = $feed->getString(); + common_log(LOG_INFO, "Posting to Salmon endpoint $salmon: $xml"); + + $salmon = new Salmon(); // ? + $salmon->post($this->salmonuri, $xml); + } + } + + function getBestName() + { + if ($this->isGroup()) { + return $this->localGroup()->getBestName(); + } else { + return $this->localProfile()->getBestName(); + } + } + + function atomFeed($actor) + { + $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('georss', 'http://www.georss.org/georss'); + $feed->addNamespace('ostatus', 'http://ostatus.org/schema/1.0'); + + $taguribase = common_config('integration', 'taguri'); + $feed->setId("tag:{$taguribase}:UserTimeline:{$actor->id}"); // ??? + + $feed->setTitle($actor->getBestName() . ' timeline'); // @fixme + $feed->setUpdated(time()); + $feed->setPublished(time()); + + $feed->addLink(common_url('ApiTimelineUser', + array('id' => $actor->id, + 'type' => 'atom')), + array('rel' => 'self', + 'type' => 'application/atom+xml')); + + $feed->addLink(common_url('userbyid', + array('id' => $actor->id)), + array('rel' => 'alternate', + 'type' => 'text/html')); + + return $feed; + } + /** * Read and post notices for updates from the feed. * Currently assumes that all items in the feed are new, -- cgit v1.2.3-54-g00ecf From fd3c9334bfcfe627446feb86ac3054b24ed05449 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Feb 2010 11:15:12 -0800 Subject: PHP 5.3 compatibility hack for DB_DataObject statusnet.links.ini file could not be read anymore due to the entry for nonce containing a comma in its key value. PHP's parse_ini_file() function no longer allows commas in keys, and rejects the *ENTIRE FILE* if it's present, breaking various automatic joins. --- classes/Nonce.php | 15 +++++++++++++++ classes/statusnet.links.ini | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/classes/Nonce.php b/classes/Nonce.php index 486a65a3c..2f8ab00b5 100644 --- a/classes/Nonce.php +++ b/classes/Nonce.php @@ -22,4 +22,19 @@ class Nonce extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + /** + * Compatibility hack for PHP 5.3 + * + * The statusnet.links.ini entry cannot be read because "," is no longer + * allowed in key names when read by parse_ini_file(). + * + * @return array + * @access public + */ + function links() + { + return array('consumer_key,token' => 'token:consumer_key,token'); + } + } diff --git a/classes/statusnet.links.ini b/classes/statusnet.links.ini index 7f233e676..b9dd5af0c 100644 --- a/classes/statusnet.links.ini +++ b/classes/statusnet.links.ini @@ -19,8 +19,11 @@ profile_id = profile:id [token] consumer_key = consumer:consumer_key -[nonce] -consumer_key,token = token:consumer_key,token +; Compatibility hack for PHP 5.3 +; This entry has been moved to the class definition, as commas are no longer +; considered valid in keys, causing parse_ini_file() to reject the whole file. +;[nonce] +;consumer_key,token = token:consumer_key,token [confirm_address] user_id = user:id -- cgit v1.2.3-54-g00ecf