2011-05-02 06:33:05 +02:00
|
|
|
<?php
|
2017-04-26 13:13:26 +02:00
|
|
|
|
|
|
|
namespace SilverStripe\FullTextSearch\Search\Queries;
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
use SilverStripe\Dev\Deprecation;
|
2018-07-12 03:28:40 +02:00
|
|
|
use SilverStripe\FullTextSearch\Search\Adapters\SearchAdapterInterface;
|
|
|
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteria;
|
|
|
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteriaInterface;
|
2017-04-26 13:13:26 +02:00
|
|
|
use SilverStripe\View\ViewableData;
|
|
|
|
use stdClass;
|
|
|
|
|
2011-05-02 06:33:05 +02:00
|
|
|
/**
|
|
|
|
* Represents a search query
|
|
|
|
*
|
2012-08-29 21:52:56 +02:00
|
|
|
* API very much still in flux.
|
2011-05-02 06:33:05 +02:00
|
|
|
*/
|
2015-11-21 07:19:20 +01:00
|
|
|
class SearchQuery extends ViewableData
|
|
|
|
{
|
|
|
|
public static $missing = null;
|
|
|
|
public static $present = null;
|
|
|
|
|
|
|
|
public static $default_page_size = 10;
|
|
|
|
|
|
|
|
/** These are public, but only for index & variant access - API users should not manually access these */
|
|
|
|
|
2017-12-05 04:49:55 +01:00
|
|
|
public $search = [];
|
2015-11-21 07:19:20 +01:00
|
|
|
|
2017-12-05 04:49:55 +01:00
|
|
|
public $classes = [];
|
2015-11-21 07:19:20 +01:00
|
|
|
|
2017-12-05 04:49:55 +01:00
|
|
|
public $require = [];
|
|
|
|
public $exclude = [];
|
2015-11-21 07:19:20 +01:00
|
|
|
|
2018-07-12 03:28:40 +02:00
|
|
|
/**
|
|
|
|
* @var SearchCriteriaInterface[]
|
|
|
|
*/
|
|
|
|
public $criteria = [];
|
|
|
|
|
2015-11-21 07:19:20 +01:00
|
|
|
protected $start = 0;
|
|
|
|
protected $limit = -1;
|
|
|
|
|
2018-07-12 03:28:40 +02:00
|
|
|
/**
|
|
|
|
* @var SearchAdapterInterface
|
|
|
|
*/
|
|
|
|
protected $adapter = null;
|
|
|
|
|
2015-11-21 07:19:20 +01:00
|
|
|
/** These are the API functions */
|
|
|
|
|
2018-07-12 03:28:40 +02:00
|
|
|
/**
|
|
|
|
* SearchQuery constructor.
|
|
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
|
|
*/
|
2015-11-21 07:19:20 +01:00
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
if (self::$missing === null) {
|
|
|
|
self::$missing = new stdClass();
|
|
|
|
}
|
|
|
|
if (self::$present === null) {
|
|
|
|
self::$present = new stdClass();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-12 03:28:40 +02:00
|
|
|
/**
|
|
|
|
* @param SearchAdapterInterface $adapter
|
|
|
|
* @return SearchQuery
|
|
|
|
*/
|
|
|
|
public function setHandler(SearchAdapterInterface $adapter)
|
|
|
|
{
|
|
|
|
$this->adapter = $adapter;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-11-21 07:19:20 +01:00
|
|
|
/**
|
2017-12-05 04:49:55 +01:00
|
|
|
* @param string $text Search terms. Exact format (grouping, boolean expressions, etc.) depends on
|
|
|
|
* the search implementation.
|
|
|
|
* @param array $fields Limits the search to specific fields (using composite field names)
|
|
|
|
* @param array $boost Map of composite field names to float values. The higher the value,
|
|
|
|
* the more important the field gets for relevancy.
|
2018-07-12 03:28:40 +02:00
|
|
|
* @return $this
|
2015-11-21 07:19:20 +01:00
|
|
|
*/
|
2018-05-14 18:04:40 +02:00
|
|
|
public function addSearchTerm($text, $fields = null, $boost = [])
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
2017-12-05 04:49:55 +01:00
|
|
|
$this->search[] = [
|
|
|
|
'text' => $text,
|
|
|
|
'fields' => $fields ? (array) $fields : null,
|
|
|
|
'boost' => $boost,
|
|
|
|
'fuzzy' => false
|
|
|
|
];
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-14 18:04:40 +02:00
|
|
|
* Similar to {@link addSearchTerm()}, but uses stemming and other similarity algorithms
|
2015-11-21 07:19:20 +01:00
|
|
|
* to find the searched terms. For example, a term "fishing" would also likely find results
|
|
|
|
* containing "fish" or "fisher". Depends on search implementation.
|
2017-11-14 21:48:52 +01:00
|
|
|
*
|
2018-05-14 18:04:40 +02:00
|
|
|
* @param string $text See {@link addSearchTerm()}
|
|
|
|
* @param array $fields See {@link addSearchTerm()}
|
|
|
|
* @param array $boost See {@link addSearchTerm()}
|
2018-07-12 03:28:40 +02:00
|
|
|
* @return $this
|
2015-11-21 07:19:20 +01:00
|
|
|
*/
|
2018-05-14 18:04:40 +02:00
|
|
|
public function addFuzzySearchTerm($text, $fields = null, $boost = [])
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
2017-12-05 04:49:55 +01:00
|
|
|
$this->search[] = [
|
|
|
|
'text' => $text,
|
|
|
|
'fields' => $fields ? (array) $fields : null,
|
|
|
|
'boost' => $boost,
|
|
|
|
'fuzzy' => true
|
|
|
|
];
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getSearchTerms()
|
|
|
|
{
|
|
|
|
return $this->search;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $class
|
|
|
|
* @param bool $includeSubclasses
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function addClassFilter($class, $includeSubclasses = true)
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
2017-12-05 04:49:55 +01:00
|
|
|
$this->classes[] = [
|
|
|
|
'class' => $class,
|
|
|
|
'includeSubclasses' => $includeSubclasses
|
|
|
|
];
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getClassFilters()
|
|
|
|
{
|
|
|
|
return $this->classes;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-14 18:04:40 +02:00
|
|
|
* Similar to {@link addSearchTerm()}, but typically used to further narrow down
|
2015-11-21 07:19:20 +01:00
|
|
|
* based on other facets which don't influence the field relevancy.
|
2017-11-14 21:48:52 +01:00
|
|
|
*
|
2017-12-05 04:49:55 +01:00
|
|
|
* @param string $field Composite name of the field
|
|
|
|
* @param mixed $values Scalar value, array of values, or an instance of SearchQuery_Range
|
2018-07-12 03:28:40 +02:00
|
|
|
* @return $this
|
2015-11-21 07:19:20 +01:00
|
|
|
*/
|
2018-05-14 18:04:40 +02:00
|
|
|
public function addFilter($field, $values)
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
2017-12-05 04:49:55 +01:00
|
|
|
$requires = isset($this->require[$field]) ? $this->require[$field] : [];
|
|
|
|
$values = is_array($values) ? $values : [$values];
|
2015-11-21 07:19:20 +01:00
|
|
|
$this->require[$field] = array_merge($requires, $values);
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getFilters()
|
|
|
|
{
|
|
|
|
return $this->require;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-14 18:04:40 +02:00
|
|
|
* Excludes results which match these criteria, inverse of {@link addFilter()}.
|
2017-11-14 21:48:52 +01:00
|
|
|
*
|
2017-12-05 04:49:55 +01:00
|
|
|
* @param string $field
|
|
|
|
* @param mixed $values
|
2018-07-12 03:28:40 +02:00
|
|
|
* @return $this
|
2015-11-21 07:19:20 +01:00
|
|
|
*/
|
2018-05-14 18:04:40 +02:00
|
|
|
public function addExclude($field, $values)
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
2017-12-05 04:49:55 +01:00
|
|
|
$excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : [];
|
|
|
|
$values = is_array($values) ? $values : [$values];
|
2015-11-21 07:19:20 +01:00
|
|
|
$this->exclude[$field] = array_merge($excludes, $values);
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getExcludes()
|
|
|
|
{
|
|
|
|
return $this->exclude;
|
|
|
|
}
|
|
|
|
|
2018-07-12 03:28:40 +02:00
|
|
|
/**
|
|
|
|
* You can pass through a string value, Criteria object, or Criterion object for $target.
|
|
|
|
*
|
|
|
|
* String value might be "SiteTree_Title" or whatever field in your index that you're trying to target.
|
|
|
|
*
|
|
|
|
* If you require complex filtering then you can build your Criteria object first with multiple layers/levels of
|
|
|
|
* Criteria, and then pass it in here when you're ready.
|
|
|
|
*
|
|
|
|
* If you have your own Criterion object that you've created that you want to use, you can also pass that in here.
|
|
|
|
*
|
|
|
|
* @param string|SearchCriteriaInterface $target
|
|
|
|
* @param mixed $value
|
|
|
|
* @param string|null $comparison
|
|
|
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
|
|
|
* @return SearchCriteriaInterface
|
|
|
|
*/
|
|
|
|
public function filterBy(
|
|
|
|
$target,
|
|
|
|
$value = null,
|
|
|
|
$comparison = null,
|
|
|
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
|
|
|
) {
|
|
|
|
if (!$target instanceof SearchCriteriaInterface) {
|
|
|
|
$target = new SearchCriteria($target, $value, $comparison, $searchQueryWriter);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->addCriteria($target);
|
|
|
|
|
|
|
|
return $target;
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
public function setStart($start)
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
|
|
|
$this->start = $start;
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getStart()
|
|
|
|
{
|
|
|
|
return $this->start;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setLimit($limit)
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
|
|
|
$this->limit = $limit;
|
2018-05-14 18:04:40 +02:00
|
|
|
return $this;
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getLimit()
|
|
|
|
{
|
|
|
|
return $this->limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setPageSize($page)
|
|
|
|
{
|
|
|
|
$this->setStart($page * self::$default_page_size);
|
|
|
|
$this->setLimit(self::$default_page_size);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getPageSize()
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
2018-05-14 18:04:40 +02:00
|
|
|
return (int) ($this->getLimit() / $this->getStart());
|
2015-11-21 07:19:20 +01:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:04:40 +02:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isFiltered()
|
2015-11-21 07:19:20 +01:00
|
|
|
{
|
|
|
|
return $this->search || $this->classes || $this->require || $this->exclude;
|
|
|
|
}
|
|
|
|
|
2018-07-12 03:28:40 +02:00
|
|
|
/**
|
|
|
|
* @return SearchAdapterInterface
|
|
|
|
*/
|
|
|
|
public function getAdapter()
|
|
|
|
{
|
|
|
|
return $this->adapter;
|
|
|
|
}
|
|
|
|
|
2015-11-21 07:19:20 +01:00
|
|
|
public function __toString()
|
|
|
|
{
|
|
|
|
return "Search Query\n";
|
|
|
|
}
|
2018-05-14 18:04:40 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function search($text, $fields = null, $boost = [])
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use addSearchTerm() instead');
|
|
|
|
return $this->addSearchTerm($text, $fields, $boost);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function fuzzysearch($text, $fields = null, $boost = [])
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use addFuzzySearchTerm() instead');
|
|
|
|
return $this->addFuzzySearchTerm($text, $fields, $boost);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function inClass($class, $includeSubclasses = true)
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use addClassFilter() instead');
|
|
|
|
return $this->addClassFilter($class, $includeSubclasses);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function filter($field, $values)
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use addFilter() instead');
|
|
|
|
return $this->addFilter($field, $values);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function exclude($field, $values)
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use addExclude() instead');
|
|
|
|
return $this->addExclude($field, $values);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function start($start)
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use setStart() instead');
|
|
|
|
return $this->setStart($start);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function limit($limit)
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use setLimit() instead');
|
|
|
|
return $this->setLimit($limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
public function page($page)
|
|
|
|
{
|
|
|
|
Deprecation::notice('4.0', 'Use setPageSize() instead');
|
|
|
|
return $this->setPageSize($page);
|
|
|
|
}
|
2018-07-12 03:28:40 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return SearchCriteriaInterface[]
|
|
|
|
*/
|
|
|
|
public function getCriteria()
|
|
|
|
{
|
|
|
|
return $this->criteria;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param SearchCriteriaInterface[] $criteria
|
|
|
|
* @return SearchQuery
|
|
|
|
*/
|
|
|
|
public function setCriteria($criteria)
|
|
|
|
{
|
|
|
|
$this->criteria = $criteria;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param SearchCriteriaInterface $criteria
|
|
|
|
* @return SearchQuery
|
|
|
|
*/
|
|
|
|
public function addCriteria($criteria)
|
|
|
|
{
|
|
|
|
$this->criteria[] = $criteria;
|
|
|
|
return $this;
|
|
|
|
}
|
2011-05-02 06:33:05 +02:00
|
|
|
}
|