2011-03-18 03:29:59 +01:00
|
|
|
<?php
|
2016-06-16 06:57:19 +02:00
|
|
|
|
2016-07-22 01:32:32 +02:00
|
|
|
namespace SilverStripe\CMS\Search;
|
|
|
|
|
2017-03-09 06:17:31 +01:00
|
|
|
use BadMethodCallException;
|
|
|
|
use SilverStripe\Assets\File;
|
2016-08-23 04:36:06 +02:00
|
|
|
use SilverStripe\CMS\Model\SiteTree;
|
2017-03-09 06:17:31 +01:00
|
|
|
use SilverStripe\Control\RequestHandler;
|
2016-08-23 04:36:06 +02:00
|
|
|
use SilverStripe\Forms\FieldList;
|
|
|
|
use SilverStripe\Forms\Form;
|
|
|
|
use SilverStripe\Forms\FormAction;
|
|
|
|
use SilverStripe\Forms\HiddenField;
|
|
|
|
use SilverStripe\Forms\TextField;
|
2016-06-16 06:57:19 +02:00
|
|
|
use SilverStripe\ORM\DB;
|
|
|
|
use SilverStripe\ORM\SS_List;
|
2016-07-22 01:32:32 +02:00
|
|
|
use Translatable;
|
2016-06-16 06:57:19 +02:00
|
|
|
|
2011-03-18 03:29:59 +01:00
|
|
|
/**
|
|
|
|
* Standard basic search form which conducts a fulltext search on all {@link SiteTree}
|
2016-01-06 00:42:07 +01:00
|
|
|
* objects.
|
2011-03-18 03:29:59 +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::get_current_locale()} when then form is constructed.
|
2016-01-06 00:42:07 +01:00
|
|
|
*
|
2011-03-18 03:29:59 +01:00
|
|
|
* @see Use ModelController and SearchContext for a more generic search implementation based around DataObject
|
|
|
|
*/
|
2017-01-25 21:59:25 +01:00
|
|
|
class SearchForm extends Form
|
|
|
|
{
|
|
|
|
/**
|
2017-03-09 06:17:31 +01:00
|
|
|
* How many results are shown per page.
|
2017-01-25 21:59:25 +01:00
|
|
|
* Relies on pagination being implemented in the search results template.
|
2017-03-09 06:17:31 +01:00
|
|
|
*
|
|
|
|
* @var int
|
2017-01-25 21:59:25 +01:00
|
|
|
*/
|
|
|
|
protected $pageLength = 10;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Classes to search
|
2017-03-09 06:17:31 +01:00
|
|
|
*
|
|
|
|
* @var array
|
2017-01-25 21:59:25 +01:00
|
|
|
*/
|
2020-04-19 06:18:01 +02:00
|
|
|
protected $classesToSearch = [
|
2017-03-09 06:17:31 +01:00
|
|
|
SiteTree::class,
|
|
|
|
File::class
|
2020-04-19 06:18:01 +02:00
|
|
|
];
|
2017-01-25 21:59:25 +01:00
|
|
|
|
2020-04-19 06:18:01 +02:00
|
|
|
private static $casting = [
|
2017-01-25 21:59:25 +01:00
|
|
|
'SearchQuery' => 'Text'
|
2020-04-19 06:18:01 +02:00
|
|
|
];
|
2017-01-25 21:59:25 +01:00
|
|
|
|
|
|
|
/**
|
2017-03-09 06:17:31 +01:00
|
|
|
* @skipUpgrade
|
|
|
|
* @param RequestHandler $controller
|
2017-01-25 21:59:25 +01:00
|
|
|
* @param string $name The name of the form (used in URL addressing)
|
|
|
|
* @param FieldList $fields Optional, defaults to a single field named "Search". Search logic needs to be customized
|
|
|
|
* if fields are added to the form.
|
|
|
|
* @param FieldList $actions Optional, defaults to a single field named "Go".
|
|
|
|
*/
|
2017-03-09 06:17:31 +01:00
|
|
|
public function __construct(
|
|
|
|
RequestHandler $controller = null,
|
|
|
|
$name = 'SearchForm',
|
|
|
|
FieldList $fields = null,
|
|
|
|
FieldList $actions = null
|
|
|
|
) {
|
2017-01-25 21:59:25 +01:00
|
|
|
if (!$fields) {
|
|
|
|
$fields = new FieldList(
|
2017-05-08 07:57:24 +02:00
|
|
|
new TextField('Search', _t(__CLASS__.'.SEARCH', 'Search'))
|
2017-01-25 21:59:25 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (class_exists('Translatable')
|
|
|
|
&& SiteTree::singleton()->hasExtension('Translatable')
|
|
|
|
) {
|
|
|
|
$fields->push(new HiddenField('searchlocale', 'searchlocale', Translatable::get_current_locale()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$actions) {
|
|
|
|
$actions = new FieldList(
|
2017-05-08 07:57:24 +02:00
|
|
|
new FormAction("results", _t(__CLASS__.'.GO', 'Go'))
|
2017-01-25 21:59:25 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
parent::__construct($controller, $name, $fields, $actions);
|
|
|
|
|
|
|
|
$this->setFormMethod('get');
|
|
|
|
|
|
|
|
$this->disableSecurityToken();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the classes to search.
|
|
|
|
* Currently you can only choose from "SiteTree" and "File", but a future version might improve this.
|
|
|
|
*
|
|
|
|
* @param array $classes
|
|
|
|
*/
|
|
|
|
public function classesToSearch($classes)
|
|
|
|
{
|
2020-04-19 06:18:01 +02:00
|
|
|
$supportedClasses = [SiteTree::class, File::class];
|
2017-01-25 21:59:25 +01:00
|
|
|
$illegalClasses = array_diff($classes, $supportedClasses);
|
|
|
|
if ($illegalClasses) {
|
2017-03-09 06:17:31 +01:00
|
|
|
throw new BadMethodCallException(
|
2017-01-25 21:59:25 +01:00
|
|
|
"SearchForm::classesToSearch() passed illegal classes '" . implode("', '", $illegalClasses)
|
2017-03-09 06:17:31 +01:00
|
|
|
. "'. At this stage, only File and SiteTree are allowed"
|
2017-01-25 21:59:25 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
$legalClasses = array_intersect($classes, $supportedClasses);
|
|
|
|
$this->classesToSearch = $legalClasses;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the classes to search
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getClassesToSearch()
|
|
|
|
{
|
|
|
|
return $this->classesToSearch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-03-09 06:17:31 +01:00
|
|
|
* Return dataObjectSet of the results using current request to get info from form.
|
2017-01-25 21:59:25 +01:00
|
|
|
* Wraps around {@link searchEngine()}.
|
|
|
|
*
|
|
|
|
* @return SS_List
|
|
|
|
*/
|
2017-03-09 06:17:31 +01:00
|
|
|
public function getResults()
|
2017-01-25 21:59:25 +01:00
|
|
|
{
|
2017-03-09 06:17:31 +01:00
|
|
|
// Get request data from request handler
|
|
|
|
$request = $this->getRequestHandler()->getRequest();
|
2017-01-25 21:59:25 +01:00
|
|
|
|
|
|
|
// set language (if present)
|
2017-03-09 06:17:31 +01:00
|
|
|
$locale = null;
|
|
|
|
$origLocale = null;
|
2017-01-25 21:59:25 +01:00
|
|
|
if (class_exists('Translatable')) {
|
2017-03-09 06:17:31 +01:00
|
|
|
$locale = $request->requestVar('searchlocale');
|
|
|
|
if (SiteTree::singleton()->hasExtension('Translatable') && $locale) {
|
|
|
|
if ($locale === "ALL") {
|
2017-01-25 21:59:25 +01:00
|
|
|
Translatable::disable_locale_filter();
|
|
|
|
} else {
|
|
|
|
$origLocale = Translatable::get_current_locale();
|
|
|
|
|
2017-03-09 06:17:31 +01:00
|
|
|
Translatable::set_current_locale($locale);
|
2017-01-25 21:59:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-09 06:17:31 +01:00
|
|
|
$keywords = $request->requestVar('Search');
|
2017-01-25 21:59:25 +01:00
|
|
|
|
2017-05-19 05:57:53 +02:00
|
|
|
$andProcessor = function ($matches) {
|
|
|
|
return ' +' . $matches[2] . ' +' . $matches[4] . ' ';
|
|
|
|
};
|
|
|
|
$notProcessor = function ($matches) {
|
|
|
|
return ' -' . $matches[3];
|
|
|
|
};
|
2011-03-18 03:29:59 +01:00
|
|
|
|
2017-01-25 21:59:25 +01:00
|
|
|
$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);
|
|
|
|
|
2017-03-09 06:17:31 +01:00
|
|
|
$pageLength = $this->getPageLength();
|
|
|
|
$start = $request->requestVar('start') ?: 0;
|
2017-01-25 21:59:25 +01:00
|
|
|
|
2017-03-12 23:11:56 +01:00
|
|
|
$booleanSearch =
|
|
|
|
strpos($keywords, '"') !== false ||
|
|
|
|
strpos($keywords, '+') !== false ||
|
|
|
|
strpos($keywords, '-') !== false ||
|
|
|
|
strpos($keywords, '*') !== false;
|
|
|
|
$results = DB::get_conn()->searchEngine($this->classesToSearch, $keywords, $start, $pageLength, "\"Relevance\" DESC", "", $booleanSearch);
|
2017-01-25 21:59:25 +01:00
|
|
|
|
|
|
|
// filter by permission
|
|
|
|
if ($results) {
|
|
|
|
foreach ($results as $result) {
|
|
|
|
if (!$result->canView()) {
|
|
|
|
$results->remove($result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset locale
|
|
|
|
if (class_exists('Translatable')) {
|
2017-03-09 06:17:31 +01:00
|
|
|
if (SiteTree::singleton()->hasExtension('Translatable') && $locale) {
|
|
|
|
if ($locale == "ALL") {
|
2017-01-25 21:59:25 +01:00
|
|
|
Translatable::enable_locale_filter();
|
|
|
|
} else {
|
|
|
|
Translatable::set_current_locale($origLocale);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function addStarsToKeywords($keywords)
|
|
|
|
{
|
|
|
|
if (!trim($keywords)) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
// Add * to each keyword
|
|
|
|
$splitWords = preg_split("/ +/", trim($keywords));
|
|
|
|
$newWords = [];
|
2017-11-01 02:51:14 +01:00
|
|
|
for ($i = 0; $i < count($splitWords); $i++) {
|
|
|
|
$word = $splitWords[$i];
|
2017-01-25 21:59:25 +01:00
|
|
|
if ($word[0] == '"') {
|
2017-11-01 02:51:14 +01:00
|
|
|
while (++$i < count($splitWords)) {
|
|
|
|
$subword = $splitWords[$i];
|
2017-01-25 21:59:25 +01:00
|
|
|
$word .= ' ' . $subword;
|
|
|
|
if (substr($subword, -1) == '"') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$word .= '*';
|
|
|
|
}
|
|
|
|
$newWords[] = $word;
|
|
|
|
}
|
|
|
|
return implode(" ", $newWords);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the search query for display in a "You searched for ..." sentence.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2017-03-09 06:17:31 +01:00
|
|
|
public function getSearchQuery()
|
2017-01-25 21:59:25 +01:00
|
|
|
{
|
2017-03-09 06:17:31 +01:00
|
|
|
return $this->getRequestHandler()->getRequest()->requestVar('Search');
|
2017-01-25 21:59:25 +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()
|
|
|
|
{
|
|
|
|
return $this->pageLength;
|
|
|
|
}
|
2011-03-18 03:29:59 +01:00
|
|
|
}
|