2008-08-06 05:28:25 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Manages searching of properties on one or more {@link DataObject}
|
|
|
|
* types, based on a given set of input parameters.
|
|
|
|
* SearchContext is intentionally decoupled from any controller-logic,
|
|
|
|
* it just receives a set of search parameters and an object class it acts on.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2013-06-21 00:32:08 +02:00
|
|
|
* The default output of a SearchContext is either a {@link SQLSelect} object
|
2011-10-26 08:09:04 +02:00
|
|
|
* for further refinement, or a {@link SS_List} that can be used to display
|
2008-08-06 05:28:25 +02:00
|
|
|
* search results, e.g. in a {@link TableListField} instance.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2008-08-06 05:28:25 +02:00
|
|
|
* In case you need multiple contexts, consider namespacing your request parameters
|
2011-10-28 03:37:27 +02:00
|
|
|
* by using {@link FieldList->namespace()} on the $fields constructor parameter.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2009-09-15 23:18:21 +02:00
|
|
|
* Each DataObject subclass can have multiple search contexts for different cases,
|
|
|
|
* e.g. for a limited frontend search and a fully featured backend search.
|
|
|
|
* By default, you can use {@link DataObject->getDefaultSearchContext()} which is automatically
|
|
|
|
* scaffolded. It uses {@link DataObject::$searchable_fields} to determine which fields
|
|
|
|
* to include.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2009-09-15 23:18:21 +02:00
|
|
|
* @see http://doc.silverstripe.com/doku.php?id=searchcontext
|
2009-03-22 23:59:14 +01:00
|
|
|
*
|
2012-04-12 08:02:46 +02:00
|
|
|
* @package framework
|
2009-03-22 23:59:14 +01:00
|
|
|
* @subpackage search
|
2008-08-06 05:28:25 +02:00
|
|
|
*/
|
|
|
|
class SearchContext extends Object {
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
/**
|
|
|
|
* DataObject subclass to which search parameters relate to.
|
|
|
|
* Also determines as which object each result is provided.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $modelClass;
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
/**
|
|
|
|
* FormFields mapping to {@link DataObject::$db} properties
|
|
|
|
* which are supposed to be searchable.
|
|
|
|
*
|
2011-10-28 03:37:27 +02:00
|
|
|
* @var FieldList
|
2008-08-06 05:28:25 +02:00
|
|
|
*/
|
|
|
|
protected $fields;
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
/**
|
|
|
|
* Array of {@link SearchFilter} subclasses.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $filters;
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-11 04:25:44 +02:00
|
|
|
/**
|
|
|
|
* The logical connective used to join WHERE clauses. Defaults to AND.
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public $connective = 'AND';
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
/**
|
|
|
|
* A key value pair of values that should be searched for.
|
|
|
|
* The keys should match the field names specified in {@link self::$fields}.
|
|
|
|
* Usually these values come from a submitted searchform
|
|
|
|
* in the form of a $_REQUEST object.
|
|
|
|
* CAUTION: All values should be treated as insecure client input.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2009-03-22 23:59:14 +01:00
|
|
|
* @param string $modelClass The base {@link DataObject} class that search properties related to.
|
|
|
|
* Also used to generate a set of result objects based on this class.
|
2011-05-11 09:51:54 +02:00
|
|
|
* @param FieldList $fields Optional. FormFields mapping to {@link DataObject::$db} properties
|
2014-08-15 08:53:05 +02:00
|
|
|
* which are to be searched. Derived from modelclass using
|
2009-03-22 23:59:14 +01:00
|
|
|
* {@link DataObject::scaffoldSearchFields()} if left blank.
|
|
|
|
* @param array $filters Optional. Derived from modelclass if left blank
|
2014-08-15 08:53:05 +02:00
|
|
|
*/
|
2012-09-19 12:07:39 +02:00
|
|
|
public function __construct($modelClass, $fields = null, $filters = null) {
|
2008-08-06 05:28:25 +02:00
|
|
|
$this->modelClass = $modelClass;
|
2011-05-11 09:51:54 +02:00
|
|
|
$this->fields = ($fields) ? $fields : new FieldList();
|
2008-08-11 01:03:35 +02:00
|
|
|
$this->filters = ($filters) ? $filters : array();
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
parent::__construct();
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 05:54:55 +02:00
|
|
|
/**
|
|
|
|
* Returns scaffolded search fields for UI.
|
|
|
|
*
|
2011-10-28 03:37:27 +02:00
|
|
|
* @return FieldList
|
2008-08-09 05:54:55 +02:00
|
|
|
*/
|
2008-08-06 05:28:25 +02:00
|
|
|
public function getSearchFields() {
|
2008-08-11 01:29:30 +02:00
|
|
|
return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields();
|
2008-08-09 06:06:52 +02:00
|
|
|
// $this->fields is causing weirdness, so we ignore for now, using the default scaffolding
|
2008-08-11 01:29:30 +02:00
|
|
|
//return singleton($this->modelClass)->scaffoldSearchFields();
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 06:53:34 +02:00
|
|
|
/**
|
2013-06-21 00:32:08 +02:00
|
|
|
* @todo move to SQLSelect
|
2008-08-09 06:53:34 +02:00
|
|
|
* @todo fix hack
|
|
|
|
*/
|
|
|
|
protected function applyBaseTableFields() {
|
|
|
|
$classes = ClassInfo::dataClassesFor($this->modelClass);
|
2016-05-25 07:09:29 +02:00
|
|
|
$baseTable = DataObject::getSchema()->baseDataTable($this->modelClass);
|
|
|
|
$fields = array("\"{$baseTable}\".*");
|
|
|
|
if($this->modelClass != $classes[0]) {
|
|
|
|
$fields[] = '"'.$classes[0].'".*';
|
|
|
|
}
|
2008-08-09 06:53:34 +02:00
|
|
|
//$fields = array_keys($model->db());
|
2009-09-30 06:00:12 +02:00
|
|
|
$fields[] = '"'.$classes[0].'".\"ClassName\" AS "RecordClassName"';
|
2008-08-09 06:53:34 +02:00
|
|
|
return $fields;
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
/**
|
2008-08-09 06:06:52 +02:00
|
|
|
* Returns a SQL object representing the search context for the given
|
|
|
|
* list of query parameters.
|
2008-08-06 05:28:25 +02:00
|
|
|
*
|
2009-09-15 23:18:21 +02:00
|
|
|
* @param array $searchParams Map of search criteria, mostly taked from $_REQUEST.
|
|
|
|
* If a filter is applied to a relationship in dot notation,
|
|
|
|
* the parameter name should have the dots replaced with double underscores,
|
|
|
|
* for example "Comments__Name" instead of the filter name "Comments.Name".
|
2016-05-25 07:30:01 +02:00
|
|
|
* @param array|bool|string $sort Database column to sort on.
|
2009-09-15 23:18:21 +02:00
|
|
|
* Falls back to {@link DataObject::$default_sort} if not provided.
|
2016-05-25 07:30:01 +02:00
|
|
|
* @param array|bool|string $limit
|
2012-03-08 07:23:32 +01:00
|
|
|
* @param DataList $existingQuery
|
|
|
|
* @return DataList
|
2016-05-25 07:30:01 +02:00
|
|
|
* @throws Exception
|
2008-08-06 05:28:25 +02:00
|
|
|
*/
|
2008-08-09 07:57:44 +02:00
|
|
|
public function getQuery($searchParams, $sort = false, $limit = false, $existingQuery = null) {
|
2012-06-15 06:20:45 +02:00
|
|
|
if($existingQuery) {
|
2012-09-26 23:34:00 +02:00
|
|
|
if(!($existingQuery instanceof DataList)) {
|
|
|
|
throw new InvalidArgumentException("existingQuery must be DataList");
|
|
|
|
}
|
|
|
|
if($existingQuery->dataClass() != $this->modelClass) {
|
2014-08-15 08:53:05 +02:00
|
|
|
throw new InvalidArgumentException("existingQuery's dataClass is " . $existingQuery->dataClass()
|
2012-09-26 23:34:00 +02:00
|
|
|
. ", $this->modelClass expected.");
|
|
|
|
}
|
2012-06-15 06:20:45 +02:00
|
|
|
$query = $existingQuery;
|
|
|
|
} else {
|
|
|
|
$query = DataList::create($this->modelClass);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_array($limit)) {
|
2012-09-26 23:34:00 +02:00
|
|
|
$query = $query->limit(
|
2014-08-15 08:53:05 +02:00
|
|
|
isset($limit['limit']) ? $limit['limit'] : null,
|
2012-09-26 23:34:00 +02:00
|
|
|
isset($limit['start']) ? $limit['start'] : null
|
|
|
|
);
|
2012-06-15 06:20:45 +02:00
|
|
|
} else {
|
|
|
|
$query = $query->limit($limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
$query = $query->sort($sort);
|
|
|
|
|
2014-08-15 08:53:05 +02:00
|
|
|
// hack to work with $searchParems when it's an Object
|
2008-08-11 05:39:14 +02:00
|
|
|
if (is_object($searchParams)) {
|
|
|
|
$searchParamArray = $searchParams->getVars();
|
2009-02-05 02:48:19 +01:00
|
|
|
} else {
|
2008-08-11 05:39:14 +02:00
|
|
|
$searchParamArray = $searchParams;
|
|
|
|
}
|
2009-09-15 23:18:21 +02:00
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
foreach($searchParamArray as $key => $value) {
|
2008-08-11 02:03:57 +02:00
|
|
|
$key = str_replace('__', '.', $key);
|
|
|
|
if($filter = $this->getFilter($key)) {
|
|
|
|
$filter->setModel($this->modelClass);
|
|
|
|
$filter->setValue($value);
|
|
|
|
if(! $filter->isEmpty()) {
|
2012-12-12 05:22:45 +01:00
|
|
|
$query = $query->alterDataQuery(array($filter, 'apply'));
|
2008-08-09 06:53:34 +02:00
|
|
|
}
|
2008-08-11 02:03:57 +02:00
|
|
|
}
|
2008-08-09 06:06:52 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
if($this->connective != "AND") {
|
|
|
|
throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite.");
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 06:06:52 +02:00
|
|
|
return $query;
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-08-09 06:06:52 +02:00
|
|
|
* Returns a result set from the given search parameters.
|
2008-08-06 05:28:25 +02:00
|
|
|
*
|
2008-08-09 06:06:52 +02:00
|
|
|
* @todo rearrange start and limit params to reflect DataObject
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2008-08-06 05:28:25 +02:00
|
|
|
* @param array $searchParams
|
2016-05-25 07:30:01 +02:00
|
|
|
* @param array|bool|string $sort
|
|
|
|
* @param array|bool|string $limit
|
2011-10-26 08:09:04 +02:00
|
|
|
* @return SS_List
|
2016-05-25 07:30:01 +02:00
|
|
|
* @throws Exception
|
2008-08-06 05:28:25 +02:00
|
|
|
*/
|
2008-08-09 07:57:44 +02:00
|
|
|
public function getResults($searchParams, $sort = false, $limit = false) {
|
2012-03-02 13:41:25 +01:00
|
|
|
$searchParams = array_filter((array)$searchParams, array($this,'clearEmptySearchFields'));
|
2011-03-21 09:37:55 +01:00
|
|
|
|
|
|
|
// getQuery actually returns a DataList
|
|
|
|
return $this->getQuery($searchParams, $sort, $limit);
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
|
|
|
|
2008-08-09 06:38:44 +02:00
|
|
|
/**
|
|
|
|
* Callback map function to filter fields with empty values from
|
|
|
|
* being included in the search expression.
|
|
|
|
*
|
2016-05-25 07:30:01 +02:00
|
|
|
* @param mixed $value
|
2008-08-09 06:38:44 +02:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2012-09-19 12:07:39 +02:00
|
|
|
public function clearEmptySearchFields($value) {
|
2008-08-09 06:38:44 +02:00
|
|
|
return ($value != '');
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 06:38:44 +02:00
|
|
|
/**
|
|
|
|
* Accessor for the filter attached to a named field.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
* @return SearchFilter
|
|
|
|
*/
|
2008-08-09 06:06:52 +02:00
|
|
|
public function getFilter($name) {
|
|
|
|
if (isset($this->filters[$name])) {
|
|
|
|
return $this->filters[$name];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 06:38:44 +02:00
|
|
|
/**
|
|
|
|
* Get the map of filters in the current search context.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2008-08-06 05:28:25 +02:00
|
|
|
public function getFilters() {
|
2008-08-09 06:06:52 +02:00
|
|
|
return $this->filters;
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 08:53:26 +02:00
|
|
|
/**
|
|
|
|
* Overwrite the current search context filter map.
|
|
|
|
*
|
|
|
|
* @param array $filters
|
|
|
|
*/
|
2008-08-06 05:28:25 +02:00
|
|
|
public function setFilters($filters) {
|
|
|
|
$this->filters = $filters;
|
2014-08-15 08:53:05 +02:00
|
|
|
}
|
|
|
|
|
2008-08-11 01:03:35 +02:00
|
|
|
/**
|
|
|
|
* Adds a instance of {@link SearchFilter}.
|
|
|
|
*
|
|
|
|
* @param SearchFilter $filter
|
|
|
|
*/
|
|
|
|
public function addFilter($filter) {
|
2009-09-15 23:18:21 +02:00
|
|
|
$this->filters[$filter->getFullName()] = $filter;
|
2008-08-11 01:03:35 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-11 01:03:35 +02:00
|
|
|
/**
|
|
|
|
* Removes a filter by name.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
*/
|
|
|
|
public function removeFilterByName($name) {
|
|
|
|
unset($this->filters[$name]);
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 06:38:44 +02:00
|
|
|
/**
|
|
|
|
* Get the list of searchable fields in the current search context.
|
|
|
|
*
|
2011-10-28 03:37:27 +02:00
|
|
|
* @return FieldList
|
2008-08-09 06:38:44 +02:00
|
|
|
*/
|
|
|
|
public function getFields() {
|
2014-08-15 08:53:05 +02:00
|
|
|
return $this->fields;
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-09 06:38:44 +02:00
|
|
|
/**
|
|
|
|
* Apply a list of searchable fields to the current search context.
|
|
|
|
*
|
2011-05-11 09:51:54 +02:00
|
|
|
* @param FieldList $fields
|
2008-08-09 06:38:44 +02:00
|
|
|
*/
|
|
|
|
public function setFields($fields) {
|
|
|
|
$this->fields = $fields;
|
2008-08-06 05:43:48 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-11 01:03:35 +02:00
|
|
|
/**
|
|
|
|
* Adds a new {@link FormField} instance.
|
|
|
|
*
|
|
|
|
* @param FormField $field
|
|
|
|
*/
|
|
|
|
public function addField($field) {
|
|
|
|
$this->fields->push($field);
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-11 01:03:35 +02:00
|
|
|
/**
|
|
|
|
* Removes an existing formfield instance by its name.
|
|
|
|
*
|
|
|
|
* @param string $fieldName
|
|
|
|
*/
|
|
|
|
public function removeFieldByName($fieldName) {
|
|
|
|
$this->fields->removeByName($fieldName);
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
2012-02-12 21:22:11 +01:00
|
|
|
|