2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
2008-02-25 03:10:37 +01:00
|
|
|
/**
|
2008-08-06 04:43:46 +02:00
|
|
|
* Standard basic search form which conducts a fulltext search on all {@link SiteTree}
|
|
|
|
* objects.
|
2009-01-19 03:18:41 +01:00
|
|
|
*
|
|
|
|
* If multilingual content is enabled through the {@link Translatable} extension,
|
|
|
|
* only pages the currently set language on the holder for this searchform are found.
|
|
|
|
* The language is set through a hidden field in the form, which is prepoluated
|
|
|
|
* with {@link Translatable::current_lang()} when then form is constructed.
|
2008-08-06 04:43:46 +02:00
|
|
|
*
|
2008-09-26 06:35:29 +02:00
|
|
|
* @see Use ModelController and SearchContext for a more generic search implementation based around DataObject
|
2008-02-25 03:10:37 +01:00
|
|
|
* @package sapphire
|
|
|
|
* @subpackage search
|
|
|
|
*/
|
2007-07-19 12:40:28 +02:00
|
|
|
class SearchForm extends Form {
|
|
|
|
|
2008-11-22 04:33:00 +01:00
|
|
|
/**
|
|
|
|
* @var boolean $showInSearchTurnOn
|
|
|
|
* @deprecated 2.3 SiteTree->ShowInSearch should always be respected
|
|
|
|
*/
|
2007-07-19 12:40:28 +02:00
|
|
|
protected $showInSearchTurnOn;
|
|
|
|
|
2008-11-22 04:33:00 +01:00
|
|
|
/**
|
2008-12-04 23:38:32 +01:00
|
|
|
* @deprecated 2.3 Use {@link $pageLength}.
|
|
|
|
*/
|
|
|
|
protected $numPerPage;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var int $pageLength How many results are shown per page.
|
2008-11-22 04:33:00 +01:00
|
|
|
* Relies on pagination being implemented in the search results template.
|
|
|
|
*/
|
2008-12-04 23:38:32 +01:00
|
|
|
protected $pageLength = 10;
|
2008-11-22 04:33:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param Controller $controller
|
|
|
|
* @param string $name The name of the form (used in URL addressing)
|
|
|
|
* @param FieldSet $fields Optional, defaults to a single field named "Search". Search logic needs to be customized
|
|
|
|
* if fields are added to the form.
|
|
|
|
* @param FieldSet $actions Optional, defaults to a single field named "Go".
|
|
|
|
* @param boolean $showInSearchTurnOn DEPRECATED 2.3
|
|
|
|
*/
|
2007-07-19 12:40:28 +02:00
|
|
|
function __construct($controller, $name, $fields = null, $actions = null, $showInSearchTurnOn = true) {
|
|
|
|
$this->showInSearchTurnOn = $showInSearchTurnOn;
|
2008-11-11 22:16:51 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if(!$fields) {
|
2008-11-11 22:16:51 +01:00
|
|
|
$fields = new FieldSet(
|
|
|
|
new TextField('Search', _t('SearchForm.SEARCH', 'Search')
|
|
|
|
));
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2008-11-11 22:16:51 +01:00
|
|
|
|
2009-01-19 03:18:41 +01:00
|
|
|
if(Translatable::is_enabled()) {
|
|
|
|
$fields->push(new HiddenField('lang', 'lang', Translatable::current_lang()));
|
|
|
|
}
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if(!$actions) {
|
|
|
|
$actions = new FieldSet(
|
2008-02-25 03:10:37 +01:00
|
|
|
new FormAction("getResults", _t('SearchForm.GO', 'Go'))
|
2007-07-19 12:40:28 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
parent::__construct($controller, $name, $fields, $actions);
|
2007-10-28 22:44:38 +01:00
|
|
|
|
2008-12-04 23:38:32 +01:00
|
|
|
$this->setFormMethod('get');
|
|
|
|
|
2007-10-28 22:44:38 +01:00
|
|
|
$this->disableSecurityToken();
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2008-12-04 23:38:32 +01:00
|
|
|
public function forTemplate() {
|
2008-11-11 22:16:51 +01:00
|
|
|
return $this->renderWith(array(
|
|
|
|
'SearchForm',
|
|
|
|
'Form'
|
|
|
|
));
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return dataObjectSet of the results using $_REQUEST to get info from form.
|
2008-11-22 04:33:00 +01:00
|
|
|
* Wraps around {@link searchEngine()}.
|
|
|
|
*
|
2008-12-04 23:38:32 +01:00
|
|
|
* @param int $pageLength DEPRECATED 2.3 Use SearchForm->pageLength
|
2008-11-22 04:33:00 +01:00
|
|
|
* @param array $data Request data as an associative array. Should contain at least a key 'Search' with all searched keywords.
|
|
|
|
* @return DataObjectSet
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2008-12-04 23:38:32 +01:00
|
|
|
public function getResults($pageLength = null, $data = null){
|
2008-11-22 04:33:00 +01:00
|
|
|
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
|
|
|
|
if(!isset($data)) $data = $_REQUEST;
|
2009-01-19 03:18:41 +01:00
|
|
|
|
|
|
|
// set language (if present)
|
|
|
|
if(Translatable::is_enabled() && isset($data['lang'])) {
|
|
|
|
Translatable::set_reading_lang($data['lang']);
|
|
|
|
}
|
2008-11-22 04:33:00 +01:00
|
|
|
|
|
|
|
$keywords = $data['Search'];
|
2007-07-19 12:40:28 +02:00
|
|
|
|
|
|
|
$andProcessor = create_function('$matches','
|
|
|
|
return " +" . $matches[2] . " +" . $matches[4] . " ";
|
|
|
|
');
|
|
|
|
$notProcessor = create_function('$matches', '
|
|
|
|
return " -" . $matches[3];
|
|
|
|
');
|
|
|
|
|
|
|
|
$keywords = preg_replace_callback('/()("[^()"]+")( and )("[^"()]+")()/i', $andProcessor, $keywords);
|
|
|
|
$keywords = preg_replace_callback('/(^| )([^() ]+)( and )([^ ()]+)( |$)/i', $andProcessor, $keywords);
|
|
|
|
$keywords = preg_replace_callback('/(^| )(not )("[^"()]+")/i', $notProcessor, $keywords);
|
|
|
|
$keywords = preg_replace_callback('/(^| )(not )([^() ]+)( |$)/i', $notProcessor, $keywords);
|
|
|
|
|
|
|
|
$keywords = $this->addStarsToKeywords($keywords);
|
|
|
|
|
|
|
|
if(strpos($keywords, '"') !== false || strpos($keywords, '+') !== false || strpos($keywords, '-') !== false || strpos($keywords, '*') !== false) {
|
2008-12-04 23:38:32 +01:00
|
|
|
$results = $this->searchEngine($keywords, $pageLength, "Relevance DESC", "", true);
|
2007-07-19 12:40:28 +02:00
|
|
|
} else {
|
2008-12-04 23:38:32 +01:00
|
|
|
$results = $this->searchEngine($keywords, $pageLength);
|
2008-11-22 04:33:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// filter by permission
|
|
|
|
if($results) foreach($results as $result) {
|
|
|
|
if(!$result->canView()) $results->remove($result);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2008-11-22 04:33:00 +01:00
|
|
|
protected function addStarsToKeywords($keywords) {
|
2007-07-19 12:40:28 +02:00
|
|
|
if(!trim($keywords)) return "";
|
|
|
|
// Add * to each keyword
|
|
|
|
$splitWords = split(" +" , trim($keywords));
|
|
|
|
while(list($i,$word) = each($splitWords)) {
|
|
|
|
if($word[0] == '"') {
|
|
|
|
while(list($i,$subword) = each($splitWords)) {
|
|
|
|
$word .= ' ' . $subword;
|
|
|
|
if(substr($subword,-1) == '"') break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$word .= '*';
|
|
|
|
}
|
|
|
|
$newWords[] = $word;
|
|
|
|
}
|
|
|
|
return implode(" ", $newWords);
|
|
|
|
}
|
2008-11-22 04:33:00 +01:00
|
|
|
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The core search engine, used by this class and its subclasses to do fun stuff.
|
|
|
|
* Searches both SiteTree and File.
|
2008-11-22 04:33:00 +01:00
|
|
|
*
|
|
|
|
* @param string $keywords Keywords as a string.
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2008-12-04 23:38:32 +01:00
|
|
|
public function searchEngine($keywords, $pageLength = null, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) {
|
|
|
|
if(!$pageLength) $pageLength = $this->pageLength;
|
2007-07-19 12:40:28 +02:00
|
|
|
$fileFilter = '';
|
2009-01-19 03:49:42 +01:00
|
|
|
|
|
|
|
$keywords = Convert::raw2sql($keywords);
|
|
|
|
$htmlEntityKeywords = htmlentities($keywords);
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if($booleanSearch) $boolean = "IN BOOLEAN MODE";
|
2009-01-19 03:49:42 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if($extraFilter) {
|
2009-01-19 03:49:42 +01:00
|
|
|
$extraFilter = " AND $extraFilter";
|
|
|
|
$fileFilter = ($alternativeFileFilter) ? " AND $alternativeFileFilter" : $extraFilter;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if($this->showInSearchTurnOn) $extraFilter .= " AND showInSearch <> 0";
|
|
|
|
|
2007-10-17 05:22:37 +02:00
|
|
|
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
|
2008-12-04 23:38:32 +01:00
|
|
|
$limit = $start . ", " . (int) $pageLength;
|
2007-07-19 12:40:28 +02:00
|
|
|
|
|
|
|
$notMatch = $invertedMatch ? "NOT " : "";
|
|
|
|
if($keywords) {
|
2009-01-19 03:49:42 +01:00
|
|
|
$matchContent = "
|
|
|
|
MATCH (Title, MenuTitle, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)
|
|
|
|
+ MATCH (Content) AGAINST ('$htmlEntityKeywords' $boolean)
|
|
|
|
";
|
2007-07-19 12:40:28 +02:00
|
|
|
$matchFile = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
|
|
|
|
|
|
|
|
// We make the relevance search by converting a boolean mode search into a normal one
|
|
|
|
$relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
|
2009-01-19 03:49:42 +01:00
|
|
|
$htmlEntityRelevanceKeywords = str_replace(array('*','+','-'),'',$htmlEntityKeywords);
|
|
|
|
$relevanceContent = "
|
|
|
|
MATCH (Title) AGAINST ('$relevanceKeywords')
|
|
|
|
+ MATCH(Content) AGAINST ('$htmlEntityRelevanceKeywords')
|
|
|
|
+ MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')
|
|
|
|
";
|
2007-07-19 12:40:28 +02:00
|
|
|
$relevanceFile = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
|
|
|
|
} else {
|
|
|
|
$relevanceContent = $relevanceFile = 1;
|
|
|
|
$matchContent = $matchFile = "1 = 1";
|
|
|
|
}
|
|
|
|
|
|
|
|
$queryContent = singleton('SiteTree')->extendedSQL($notMatch . $matchContent . $extraFilter, "");
|
|
|
|
|
|
|
|
$baseClass = reset($queryContent->from);
|
|
|
|
// There's no need to do all that joining
|
2008-11-23 01:31:06 +01:00
|
|
|
$queryContent->from = array(str_replace(array('`','"'),'',$baseClass) => $baseClass);
|
2008-11-24 20:28:46 +01:00
|
|
|
$queryContent->select = array("\"ClassName\"","$baseClass.\"ID\"","\"ParentID\"","\"Title\"",
|
|
|
|
"\"URLSegment\"","\"Content\"","\"LastEdited\"","\"Created\"","'' AS \"Filename\"",
|
|
|
|
"'' AS \"Name\"", "$relevanceContent AS \"Relevance\"", "\"CanViewType\"");
|
2007-07-19 12:40:28 +02:00
|
|
|
$queryContent->orderby = null;
|
|
|
|
|
|
|
|
$queryFiles = singleton('File')->extendedSQL($notMatch . $matchFile . $fileFilter, "");
|
|
|
|
$baseClass = reset($queryFiles->from);
|
|
|
|
// There's no need to do all that joining
|
2008-11-23 01:31:06 +01:00
|
|
|
$queryFiles->from = array(str_replace(array('`','"'),'',$baseClass) => $baseClass);
|
2008-11-24 20:28:46 +01:00
|
|
|
$queryFiles->select = array("\"ClassName\"","$baseClass.\"ID\"","'' AS \"ParentID\"","\"Title\"",
|
|
|
|
"'' AS \"URLSegment\"","\"Content\"","\"LastEdited\"","\"Created\"","\"Filename\"","\"Name\"",
|
2008-11-24 10:31:14 +01:00
|
|
|
"$relevanceFile AS \"Relevance\"","NULL AS \"CanViewType\"");
|
2007-07-19 12:40:28 +02:00
|
|
|
$queryFiles->orderby = null;
|
|
|
|
|
|
|
|
$fullQuery = $queryContent->sql() . " UNION " . $queryFiles->sql() . " ORDER BY $sortBy LIMIT $limit";
|
|
|
|
$totalCount = $queryContent->unlimitedRowCount() + $queryFiles->unlimitedRowCount();
|
|
|
|
|
|
|
|
$records = DB::query($fullQuery);
|
|
|
|
|
|
|
|
foreach($records as $record)
|
|
|
|
$objects[] = new $record['ClassName']($record);
|
2007-10-17 05:26:25 +02:00
|
|
|
|
|
|
|
if(isset($objects)) $doSet = new DataObjectSet($objects);
|
|
|
|
else $doSet = new DataObjectSet();
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2008-12-04 23:38:32 +01:00
|
|
|
$doSet->setPageLimits($start, $pageLength, $totalCount);
|
2007-07-19 12:40:28 +02:00
|
|
|
return $doSet;
|
|
|
|
}
|
2008-11-11 22:16:51 +01:00
|
|
|
|
2008-11-22 04:33:00 +01:00
|
|
|
/**
|
|
|
|
* Get the search query for display in a "You searched for ..." sentence.
|
|
|
|
*
|
|
|
|
* @param array $data
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getSearchQuery($data = null) {
|
|
|
|
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
|
|
|
|
if(!isset($data)) $data = $_REQUEST;
|
|
|
|
|
|
|
|
return Convert::raw2xml($data['Search']);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2008-12-04 23:38:32 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the maximum number of records shown on each page.
|
|
|
|
*
|
|
|
|
* @param int $length
|
|
|
|
*/
|
|
|
|
public function setPageLength($length) {
|
|
|
|
$this->pageLength = $length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getPageLength() {
|
|
|
|
// legacy handling for deprecated $numPerPage
|
|
|
|
return (isset($this->numPerPage)) ? $this->numPerPage : $this->pageLength;
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2008-08-06 04:43:46 +02:00
|
|
|
?>
|