From ac7170bf6cbf36699cf182ccb1bd5214f6e8741e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 6 Mar 2009 21:09:43 -0800 Subject: Atom search results for Twitter-compatible API + phpcs stuff --- actions/twitapisearchatom.php | 368 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 actions/twitapisearchatom.php (limited to 'actions/twitapisearchatom.php') diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php new file mode 100644 index 000000000..3ab82bfb6 --- /dev/null +++ b/actions/twitapisearchatom.php @@ -0,0 +1,368 @@ +. + * + * @category Search + * @package Laconica + * @author Zach Copley + * @copyright 2008-2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/twitterapi.php'; + +/** + * Action for outputting search results in Twitter compatible Atom + * format. + * + * TODO: abstract Atom stuff into a ruseable base class like + * RSS10Action. + * + * @category Search + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + * @see TwitterapiAction + */ + +class TwitapisearchatomAction extends TwitterapiAction +{ + + var $notices; + var $cnt; + var $query; + var $lang; + var $rpp; + var $page; + var $since_id; + var $geocode; + + /** + * Constructor + * + * Just wraps the Action constructor. + * + * @param string $output URI to output to, default = stdout + * @param boolean $indent Whether to indent output, default true + * + * @see Action::__construct + */ + + function __construct($output='php://output', $indent=true) + { + parent::__construct($output, $indent); + } + + /** + * Do we need to write to the database? + * + * @return boolean true + */ + + function isReadonly() + { + return true; + } + + /** + * Read arguments and initialize members + * + * @param array $args Arguments from $_REQUEST + * + * @return boolean success + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->query = $this->trimmed('q'); + $this->lang = $this->trimmed('lang'); + $this->rpp = $this->trimmed('rpp'); + + if (!$this->rpp) { + $this->rpp = 15; + } + + if ($this->rpp > 100) { + $this->rpp = 100; + } + + $this->page = $this->trimmed('page'); + + if (!$this->page) { + $this->page = 1; + } + + // TODO: Suppport since_id -- we need to tweak the backend + // Search classes to support it. + + $this->since_id = $this->trimmed('since_id'); + $this->geocode = $this->trimmed('geocode'); + + // TODO: Also, language and geocode + + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showAtom(); + } + + /** + * Get the notices to output as results. This also sets some class + * attrs so we can use them to calculate pagination, and output + * since_id and max_id. + * + * @return array an array of Notice objects sorted in reverse chron + */ + + function getNotices() + { + // TODO: Support search operators like from: and to:, boolean, etc. + + $notice = new Notice(); + + // lcase it for comparison + $q = strtolower($this->query); + + $search_engine = $notice->getSearchEngine('identica_notices'); + $search_engine->set_sort_mode('chron'); + $search_engine->limit(($this->page - 1) * $this->rpp, + $this->rpp + 1, true); + $search_engine->query($q); + $this->cnt = $notice->find(); + + $cnt = 0; + + while ($notice->fetch()) { + + ++$cnt; + + if (!$this->max_id) { + $this->max_id = $notice->id; + } + + if ($cnt > $this->rpp) { + break; + } + + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Output search results as an Atom feed + * + * @return void + */ + + function showAtom() + { + $notices = $this->getNotices(); + + $this->initAtom(); + $this->showFeed(); + + foreach ($notices as $n) { + $this->showEntry($n); + } + + $this->endAtom(); + } + + /** + * Show feed specific Atom elements + * + * @return void + */ + + function showFeed() + { + // TODO: A9 OpenSearch stuff like search.twitter.com? + + $lang = common_config('site', 'language'); + $server = common_config('site', 'server'); + $sitename = common_config('site', 'name'); + + // XXX: Use xmlns:laconica instead? + + $this->elementStart('feed', + array('xmlns' => 'http://www.w3.org/2005/Atom', + 'xmlns:twitter' => 'http://api.twitter.com/', + 'xml:lang' => $lang)); + + $year = date('Y'); + $this->element('id', null, "tag:$server,$year:search/$server"); + + $site_uri = common_path(false); + + $search_uri = $site_uri . 'api/search.atom?q=' . urlencode($this->query); + + if ($this->rpp != 15) { + $search_uri .= '&rpp=' . $this->rpp; + } + + // FIXME: this alternate link is not quite right because our + // web-based notice search doesn't support a rpp (responses per + // page) param yet + + $this->element('link', array('type' => 'text/html', + 'rel' => 'alternate', + 'href' => $site_uri . 'search/notice?q=' . + urlencode($this->query))); + + // self link + + $self_uri = $search_uri . '&page=' . $this->page; + + $this->element('link', array('type' => 'application/atom+xml', + 'rel' => 'self', + 'href' => $self_uri)); + + $this->element('title', null, "$this->query - $sitename Search"); + + // refresh link + + $refresh_uri = $search_uri . "&since_id=" . $this->max_id; + + $this->element('link', array('type' => 'application/atom+xml', + 'rel' => 'refresh', + 'href' => $refresh_uri)); + + // pagination links + + if ($this->cnt > $this->rpp) { + + $next_uri = $search_uri . "&max_id=" . $this->max_id . + '&page=' . ($this->page + 1); + + $this->element('link', array('type' => 'application/atom+xml', + 'rel' => 'next', + 'href' => $next_uri)); + } + + if ($this->page > 1) { + + $previous_uri = $search_uri . "&max_id=" . $this->max_id . + '&page=' . ($this->page - 1); + + $this->element('link', array('type' => 'application/atom+xml', + 'rel' => 'previous', + 'href' => $previous_uri)); + } + + } + + /** + * Build an Atom entry similar to search.twitter.com's based on + * a given notice + * + * @param Notice $notice the notice to use + * + * @return void + */ + + function showEntry($notice) + { + $server = common_config('site', 'server'); + $profile = $notice->getProfile(); + $nurl = common_local_url('shownotice', array('notice' => $notice->id)); + + $this->elementStart('entry'); + + $year = date('Y', strtotime($notice->created)); + + $this->element('id', null, "tag:$server,$year:$notice->id"); + $this->element('published', null, common_date_w3dtf($notice->created)); + $this->element('link', array('type' => 'text/html', + 'rel' => 'alternate', + 'href' => $nurl)); + $this->element('title', null, common_xml_safe_str(trim($notice->content))); + $this->element('content', array('type' => 'text/html'), $notice->rendered); + $this->element('updated', null, common_date_w3dtf($notice->created)); + $this->element('link', array('type' => 'image/png', + 'rel' => 'image', + 'href' => $profile->avatarUrl())); + + // TODO: Here is where we'd put in a link to an atom feed for threads + + $this->element("twitter:source", null, + htmlentities($this->source_link($notice->source))); + + $this->elementStart('author'); + + $name = $profile->nickname; + + if ($profile->fullname) { + $name .= ' (' . $profile->fullname . ')'; + } + + $this->element('name', null, $name); + $this->element('uri', null, common_profile_uri($profile)); + $this->elementEnd('author'); + + $this->elementEnd('entry'); + } + + /** + * Initialize the Atom output, send headers + * + * @return void + */ + + function initAtom() + { + header('Content-Type: application/atom+xml; charset=utf-8'); + $this->startXml(); + } + + /** + * End the Atom feed + * + * @return void + */ + + function endAtom() + { + $this->elementEnd('feed'); + } + +} -- cgit v1.2.3-54-g00ecf From a989c58c11004f850ff0d7bb633df570c7f68f45 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 10 Mar 2009 17:20:43 -0700 Subject: Fix tag URIs in Atom feeds for search thru the API --- actions/twitapisearchatom.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'actions/twitapisearchatom.php') diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index 3ab82bfb6..c0e55df05 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -229,8 +229,8 @@ class TwitapisearchatomAction extends TwitterapiAction 'xmlns:twitter' => 'http://api.twitter.com/', 'xml:lang' => $lang)); - $year = date('Y'); - $this->element('id', null, "tag:$server,$year:search/$server"); + $taguribase = common_config('integration', 'taguri'); + $this->element('id', null, "tag:$taguribase:search/$server"); $site_uri = common_path(false); @@ -258,6 +258,7 @@ class TwitapisearchatomAction extends TwitterapiAction 'href' => $self_uri)); $this->element('title', null, "$this->query - $sitename Search"); + $this->element('updated', null, common_date_iso8601('now')); // refresh link @@ -308,9 +309,9 @@ class TwitapisearchatomAction extends TwitterapiAction $this->elementStart('entry'); - $year = date('Y', strtotime($notice->created)); + $taguribase = common_config('integration', 'taguri'); - $this->element('id', null, "tag:$server,$year:$notice->id"); + $this->element('id', null, "tag:$taguribase:$notice->id"); $this->element('published', null, common_date_w3dtf($notice->created)); $this->element('link', array('type' => 'text/html', 'rel' => 'alternate', -- cgit v1.2.3-54-g00ecf From 1e29cbd6919380aee54b57db62e08c5d1497d763 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 10 Mar 2009 18:17:20 -0700 Subject: Make search API Atom feeds more valid --- actions/twitapisearchatom.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'actions/twitapisearchatom.php') diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index c0e55df05..c5f724c12 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -52,7 +52,6 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; class TwitapisearchatomAction extends TwitterapiAction { - var $notices; var $cnt; var $query; var $lang; @@ -156,6 +155,7 @@ class TwitapisearchatomAction extends TwitterapiAction { // TODO: Support search operators like from: and to:, boolean, etc. + $notices = array(); $notice = new Notice(); // lcase it for comparison @@ -226,6 +226,10 @@ class TwitapisearchatomAction extends TwitterapiAction $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', + + // XXX: xmlns:twitter causes Atom validation to fail + // It's used for the source attr on notices + 'xmlns:twitter' => 'http://api.twitter.com/', 'xml:lang' => $lang)); @@ -251,7 +255,8 @@ class TwitapisearchatomAction extends TwitterapiAction // self link - $self_uri = $search_uri . '&page=' . $this->page; + $self_uri = $search_uri; + $self_uri .= ($this->page > 1) ? '&page=' . $this->page : ''; $this->element('link', array('type' => 'application/atom+xml', 'rel' => 'self', @@ -317,10 +322,11 @@ class TwitapisearchatomAction extends TwitterapiAction 'rel' => 'alternate', 'href' => $nurl)); $this->element('title', null, common_xml_safe_str(trim($notice->content))); - $this->element('content', array('type' => 'text/html'), $notice->rendered); + $this->element('content', array('type' => 'html'), $notice->rendered); $this->element('updated', null, common_date_w3dtf($notice->created)); $this->element('link', array('type' => 'image/png', - 'rel' => 'image', + // XXX: Twitter uses rel="image" (not valid) + 'rel' => 'related', 'href' => $profile->avatarUrl())); // TODO: Here is where we'd put in a link to an atom feed for threads -- cgit v1.2.3-54-g00ecf From 622cc150d86265cbb1dcd35ae11f4941d42ffdeb Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 10 Mar 2009 18:30:58 -0700 Subject: Fix xml:lang attr --- actions/twitapisearchatom.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'actions/twitapisearchatom.php') diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index c5f724c12..eb9ab5d8e 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -218,7 +218,6 @@ class TwitapisearchatomAction extends TwitterapiAction { // TODO: A9 OpenSearch stuff like search.twitter.com? - $lang = common_config('site', 'language'); $server = common_config('site', 'server'); $sitename = common_config('site', 'name'); @@ -231,7 +230,7 @@ class TwitapisearchatomAction extends TwitterapiAction // It's used for the source attr on notices 'xmlns:twitter' => 'http://api.twitter.com/', - 'xml:lang' => $lang)); + 'xml:lang' => 'en-US')); // XXX Other locales ? $taguribase = common_config('integration', 'taguri'); $this->element('id', null, "tag:$taguribase:search/$server"); @@ -265,6 +264,9 @@ class TwitapisearchatomAction extends TwitterapiAction $this->element('title', null, "$this->query - $sitename Search"); $this->element('updated', null, common_date_iso8601('now')); + // XXX: The below "rel" links are not valid Atom, but it's what + // Twitter does... + // refresh link $refresh_uri = $search_uri . "&since_id=" . $this->max_id; -- cgit v1.2.3-54-g00ecf