silverstripe-cms/code/Search/SearchForm.php

252 lines
7.7 KiB
PHP
Raw Permalink Normal View History

<?php
2016-07-22 01:32:32 +02:00
namespace SilverStripe\CMS\Search;
use BadMethodCallException;
use SilverStripe\Assets\File;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\RequestHandler;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\SS_List;
2016-07-22 01:32:32 +02:00
use Translatable;
/**
* Standard basic search form which conducts a fulltext search on all {@link SiteTree}
* objects.
*
* 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.
*
* @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
{
/**
* 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.
*
* @var int
2017-01-25 21:59:25 +01:00
*/
protected $pageLength = 10;
/**
* Classes to search
*
* @var array
2017-01-25 21:59:25 +01:00
*/
protected $classesToSearch = [
SiteTree::class,
File::class
];
2017-01-25 21:59:25 +01:00
private static $casting = [
2017-01-25 21:59:25 +01:00
'SearchQuery' => 'Text'
];
2017-01-25 21:59:25 +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".
*/
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)
{
$supportedClasses = [SiteTree::class, File::class];
2022-04-13 07:07:59 +02:00
$illegalClasses = array_diff($classes ?? [], $supportedClasses);
2017-01-25 21:59:25 +01:00
if ($illegalClasses) {
throw new BadMethodCallException(
2017-01-25 21:59:25 +01:00
"SearchForm::classesToSearch() passed illegal classes '" . implode("', '", $illegalClasses)
. "'. At this stage, only File and SiteTree are allowed"
2017-01-25 21:59:25 +01:00
);
}
2022-04-13 07:07:59 +02:00
$legalClasses = array_intersect($classes ?? [], $supportedClasses);
2017-01-25 21:59:25 +01:00
$this->classesToSearch = $legalClasses;
}
/**
* Get the classes to search
*
* @return array
*/
public function getClassesToSearch()
{
return $this->classesToSearch;
}
/**
* 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
*/
public function getResults()
2017-01-25 21:59:25 +01:00
{
// Get request data from request handler
$request = $this->getRequestHandler()->getRequest();
2017-01-25 21:59:25 +01:00
// set language (if present)
$locale = null;
$origLocale = null;
2017-01-25 21:59:25 +01:00
if (class_exists('Translatable')) {
$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();
Translatable::set_current_locale($locale);
2017-01-25 21:59:25 +01:00
}
}
}
$keywords = $request->requestVar('Search');
2017-01-25 21:59:25 +01:00
$andProcessor = function ($matches) {
return ' +' . $matches[2] . ' +' . $matches[4] . ' ';
};
$notProcessor = function ($matches) {
return ' -' . $matches[3];
};
2022-04-13 07:07:59 +02: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 ?? '');
2017-01-25 21:59:25 +01:00
$keywords = $this->addStarsToKeywords($keywords);
$pageLength = $this->getPageLength();
$start = max(0, (int)$request->requestVar('start'));
2017-01-25 21:59:25 +01:00
2017-03-12 23:11:56 +01:00
$booleanSearch =
2022-04-13 07:07:59 +02:00
strpos($keywords ?? '', '"') !== false ||
strpos($keywords ?? '', '+') !== false ||
strpos($keywords ?? '', '-') !== false ||
strpos($keywords ?? '', '*') !== false;
2017-03-12 23:11:56 +01:00
$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')) {
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)
{
2022-04-13 07:07:59 +02:00
if (!trim($keywords ?? '')) {
2017-01-25 21:59:25 +01:00
return "";
}
// Add * to each keyword
2022-04-13 07:07:59 +02:00
$splitWords = preg_split("/ +/", trim($keywords ?? ''));
2017-01-25 21:59:25 +01:00
$newWords = [];
2022-04-13 07:07:59 +02:00
for ($i = 0; $i < count($splitWords ?? []); $i++) {
2017-11-01 02:51:14 +01:00
$word = $splitWords[$i];
2017-01-25 21:59:25 +01:00
if ($word[0] == '"') {
2022-04-13 07:07:59 +02:00
while (++$i < count($splitWords ?? [])) {
2017-11-01 02:51:14 +01:00
$subword = $splitWords[$i];
2017-01-25 21:59:25 +01:00
$word .= ' ' . $subword;
2022-04-13 07:07:59 +02:00
if (substr($subword ?? '', -1) == '"') {
2017-01-25 21:59:25 +01:00
break;
}
}
} else {
$word .= '*';
}
$newWords[] = $word;
}
return implode(" ", $newWords);
}
/**
* Get the search query for display in a "You searched for ..." sentence.
*
* @return string
*/
public function getSearchQuery()
2017-01-25 21:59:25 +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;
}
}