mirror of
https://github.com/silverstripe/silverstripe-docsviewer
synced 2024-10-22 11:05:56 +02:00
FEATURE: implemented initial cut of open search output and description functionality.
This commit is contained in:
parent
855f5471e4
commit
166fb8dff3
12
_config.php
12
_config.php
@ -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')
|
||||||
|
));
|
||||||
|
@ -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'));
|
||||||
|
}
|
||||||
}
|
}
|
@ -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
0
lang/_manifest_exclude
Executable file
10
lang/en_US.php
Executable file
10
lang/en_US.php
Executable 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';
|
||||||
|
|
||||||
|
?>
|
10
templates/OpenSearchDescription.ss
Normal file
10
templates/OpenSearchDescription.ss
Normal 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}&pw={startPage?}&format=rss"/>
|
||||||
|
</OpenSearchDescription>
|
23
templates/OpenSearchResults.ss
Normal file
23
templates/OpenSearchResults.ss
Normal 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>
|
Loading…
Reference in New Issue
Block a user