. * * @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'); } }