FEATURE: implemented initial cut of open search output and description functionality.

This commit is contained in:
Will Rossiter 2011-01-21 04:10:41 +00:00
parent 855f5471e4
commit 166fb8dff3
7 changed files with 208 additions and 20 deletions

View File

@ -7,7 +7,17 @@
* For more information and documentation see sapphiredocs/docs/en * For more information and documentation see sapphiredocs/docs/en
*/ */
// default location for documentation // default location for documentation. If you want this under a custom url
// define your own rule in your mysite/_config.php
Director::addRules(100, array( Director::addRules(100, array(
'dev/docs' => 'DocumentationViewer' 'dev/docs' => 'DocumentationViewer'
)); ));
// the default meta data for the OpenSearch library. More descriptive values
// can be set in your mysite file
DocumentationSearch::set_meta_data(array(
'ShortName' => _t('DocumentationViewer.OPENSEARCHNAME', 'Documentation Search'),
'Description' => _t('DocumentationViewer.OPENSEARCHDESC', 'Search the documentation'),
'Contact' => Email::getAdminEmail(),
'Tags' => _t('DocumentationViewer.OPENSEARCHTAGS', 'Documentation')
));

View File

@ -3,19 +3,82 @@
/** /**
* Documentation Search powered by Lucene. You will need Zend_Lucene installed on your path * Documentation Search powered by Lucene. You will need Zend_Lucene installed on your path
* to rebuild the indexes run the {@link RebuildLuceneDocsIndex} task. You may wish to setup * to rebuild the indexes run the {@link RebuildLuceneDocsIndex} task. You may wish to setup
* a cron job to remake the indexes on a regular basis * a cron job to remake the indexes on a regular basis.
*
* It has the ability to generate an OpenSearch RSS formatted feeds simply by using the URL
*
* <code>
* yoursite.com/search/?q=Foo&format=rss. // Format can either be specified as rss or left off.
* </code>
*
* To get a specific amount of results you can also use the modifiers start and limit
*
* <code>
* yoursite.com/search/?q=Foo&start=10&limit=10
* </code>
* *
* @package sapphiredocs * @package sapphiredocs
*/ */
class DocumentationSearch { class DocumentationSearch extends Controller {
/**
* @var bool - Is search enabled
*/
private static $enabled = false; private static $enabled = false;
/**
* @var string - OpenSearch metadata. Please use {@link DocumentationSearch::set_meta_data()}
*/
private static $meta_data;
/**
* @var DataObjectSet - Results
*/
private $results; private $results;
/**
* @var int
*/
private $totalResults; private $totalResults;
/**
* @var string
*/
private $query;
/**
* @var Controller
*/
private $outputController;
/**
* Set the current search query
*
* @param string
*/
public function setQuery($query) {
$this->query = $query;
}
/**
* Returns the current search query
*
* @return string
*/
public function getQuery() {
return $this->query;
}
/**
* Sets the {@link DocumentationViewer} or {@link DocumentationSearch} instance which this search is rendering
* on based on whether it is the results display or RSS feed
*
* @param Controller
*/
public function setOutputController($controller) {
$this->outputController = $controller;
}
/** /**
* Folder name for indexes (in the temp folder). You can override it using * Folder name for indexes (in the temp folder). You can override it using
@ -26,7 +89,8 @@ class DocumentationSearch {
private static $index_location = 'sapphiredocs'; private static $index_location = 'sapphiredocs';
static $allowed_actions = array( static $allowed_actions = array(
'buildindex' 'buildindex',
'opensearch'
); );
/** /**
@ -96,29 +160,28 @@ class DocumentationSearch {
/** /**
* Perform a search query on the index * Perform a search query on the index
*
* Rebuilds the index if it out of date
*/ */
public function performSearch($query) { public function performSearch() {
try { try {
$index = Zend_Search_Lucene::open(self::get_index_location()); $index = Zend_Search_Lucene::open(self::get_index_location());
Zend_Search_Lucene::setResultSetLimit(200); Zend_Search_Lucene::setResultSetLimit(200);
$this->results = $index->find($query); $this->results = $index->find($this->getQuery());
$this->totalResults = $index->numDocs(); $this->totalResults = $index->numDocs();
} }
catch(Zend_Search_Lucene_Exception $e) { catch(Zend_Search_Lucene_Exception $e) {
// the reindexing task has not been run
user_error('DocumentationSearch::performSearch() could not perform search as index does not exist. user_error('DocumentationSearch::performSearch() could not perform search as index does not exist.
Please run /dev/tasks/RebuildLuceneDocsIndex', E_USER_ERROR); Please run /dev/tasks/RebuildLuceneDocsIndex', E_USER_ERROR);
} }
} }
/** /**
* @return DataObjectSet * @return ArrayData
*/ */
public function getDataArrayFromHits($request) { public function getSearchResults($request) {
$pageLength = (isset($_GET['length'])) ? (int) $_GET['length'] : 10;
$data = array( $data = array(
'Results' => null, 'Results' => null,
'Query' => null, 'Query' => null,
@ -127,6 +190,7 @@ class DocumentationSearch {
'TotalPages' => null, 'TotalPages' => null,
'ThisPage' => null, 'ThisPage' => null,
'StartResult' => null, 'StartResult' => null,
'PageLength' => $pageLength,
'EndResult' => null, 'EndResult' => null,
'PrevUrl' => DBField::create('Text', 'false'), 'PrevUrl' => DBField::create('Text', 'false'),
'NextUrl' => DBField::create('Text', 'false'), 'NextUrl' => DBField::create('Text', 'false'),
@ -136,7 +200,7 @@ class DocumentationSearch {
$start = ($request->requestVar('start')) ? (int)$request->requestVar('start') : 0; $start = ($request->requestVar('start')) ? (int)$request->requestVar('start') : 0;
$query = ($request->requestVar('Search')) ? $request->requestVar('Search') : ''; $query = ($request->requestVar('Search')) ? $request->requestVar('Search') : '';
$pageLength = 10;
$currentPage = floor( $start / $pageLength ) + 1; $currentPage = floor( $start / $pageLength ) + 1;
$totalPages = ceil(count($this->results) / $pageLength ); $totalPages = ceil(count($this->results) / $pageLength );
@ -204,10 +268,12 @@ class DocumentationSearch {
} }
} }
return $data; return new ArrayData($data);
} }
/** /**
* Build a nice query string for the results
*
* @return string * @return string
*/ */
private function buildQueryUrl($params) { private function buildQueryUrl($params) {
@ -232,9 +298,79 @@ class DocumentationSearch {
return (int) $this->totalResults; return (int) $this->totalResults;
} }
/**
* Optimizes the search indexes on the File System
*
* @return void
*/
public function optimizeIndex() { public function optimizeIndex() {
$index = Zend_Search_Lucene::open(self::get_index_location()); $index = Zend_Search_Lucene::open(self::get_index_location());
if($index) $index->optimize(); if($index) $index->optimize();
} }
public function getTitle() {
return ($this->outputController) ? $this->outputController->Title : "";
}
/**
* OpenSearch MetaData. Includes 'description', 'tags', 'contact'
*
* @param array
*/
public static function set_meta_data($data) {
if(is_array($data)) {
foreach($data as $key => $value) {
self::$meta_data[strtolower($key)] = $value;
}
}
}
/**
* Returns the meta data needed by opensearch.
*
* @return array
*/
public static function get_meta_data() {
$data = self::$meta_data;
return array(
'Description' => (isset($data['description'])) ? $data['description'] : "",
'Tags' => (isset($data['tags'])) ? $data['tags'] : "",
'Contact' => (isset($data['contact'])) ? $data['contact'] : "",
'ShortName' => (isset($data['shortname'])) ? $data['shortname'] : ""
);
}
public function renderResults() {
if(!$this->results) $this->performSearch();
if(!$this->outputController) $this->outputController = $this;
$request = $this->outputController->getRequest();
$data = $this->getSearchResults($request);
$templates = array('DocumentationViewer_results', 'DocumentationViewer');
if($request->requestVar('format') && $request->requestVar('format') == "rss") {
// alter the fields for the opensearch xml.
$title = ($title = $this->getTitle()) ? ' | '. $title : "";
$data->setField('Title', $data->Title . $title);
$data->setField('DescriptionURL', 'DocumentationSearch/opensearch/');
array_unshift($templates, 'OpenSearchResults');
}
return $this->outputController->customise($data)->renderWith($templates);
}
/**
* Returns the opensearch description of the search results
*/
public function opensearch() {
$data = self::get_meta_data();
return $this->customise(new ArrayData($data))->renderWith(array('OpenSearchDescription'));
}
} }

View File

@ -711,10 +711,9 @@ class DocumentationViewer extends Controller {
*/ */
function results($data, $form, $request) { function results($data, $form, $request) {
$search = new DocumentationSearch(); $search = new DocumentationSearch();
$search->performSearch($form->dataFieldByName('Search')->dataValue()); $search->setQuery($form->dataFieldByName('Search')->dataValue());
$search->setOutputController($this);
$data = $search->getDataArrayFromHits($request); return $search->renderResults();
return $this->customise($data)->renderWith(array('DocumentationViewer_results', 'DocumentationViewer'));
} }
} }

0
lang/_manifest_exclude Executable file
View File

10
lang/en_US.php Executable file
View File

@ -0,0 +1,10 @@
<?php
global $lang;
$lang['en_US']['DocumentationSearch']['SEARCHRESULTS'] = 'Search Results';
$lang['en_US']['DocumentationViewer']['CHANGE'] = 'Change';
$lang['en_US']['DocumentationViewer']['LANGUAGE'] = 'Language';
$lang['en_US']['DocumentationViewer']['SEARCH'] = 'Search';
?>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>$ShortName</ShortName>
<Description>$Description</Description>
<Tags>$Tags</Tags>
<Contact>$Content</Contact>
<Url type="application/rss+xml" template="http://example.com/?q={searchTerms}&amp;pw={startPage?}&amp;format=rss"/>
</OpenSearchDescription>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>$Title</title>
<link>$Link</link>
<opensearch:totalResults>$TotalResults</opensearch:totalResults>
<opensearch:startIndex>$StartResult</opensearch:startIndex>
<opensearch:itemsPerPage>$PageLength</opensearch:itemsPerPage>
<atom:link rel="search" type="application/opensearchdescription+xml" href="{$BaseHref}DocumentationSearch/opensearch"/>
<opensearch:Query role="request" searchTerms="$Query" startPage="1" />
<% control Results %>
<item>
<title>$Title</title>
<link>$Link</link>
<description>$Content.LimitCharacters(200)</description>
</item>
<% end_control %>
</channel>
</rss>