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.
|
|
|
|
*
|
|
|
|
* The default output of a SearchContext is either a {@link SQLQuery} object
|
|
|
|
* for further refinement, or a {@link DataObjectSet} that can be used to display
|
|
|
|
* search results, e.g. in a {@link TableListField} instance.
|
|
|
|
*
|
|
|
|
* In case you need multiple contexts, consider namespacing your request parameters
|
|
|
|
* by using {@link FieldSet->namespace()} on the $fields constructor parameter.
|
|
|
|
*
|
|
|
|
* @usedby {@link ModelAdmin}
|
|
|
|
*
|
|
|
|
* @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.
|
|
|
|
* @param FieldSet $fields Optional. FormFields mapping to {@link DataObject::$db} properties
|
|
|
|
* which are to be searched. Derived from modelclass using
|
|
|
|
* {@link DataObject::scaffoldSearchFields()} if left blank.
|
|
|
|
* @param array $filters Optional. Derived from modelclass if left blank
|
|
|
|
*/
|
|
|
|
class SearchContext extends Object {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DataObject subclass to which search parameters relate to.
|
|
|
|
* Also determines as which object each result is provided.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $modelClass;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* FormFields mapping to {@link DataObject::$db} properties
|
|
|
|
* which are supposed to be searchable.
|
|
|
|
*
|
|
|
|
* @var FieldSet
|
|
|
|
*/
|
|
|
|
protected $fields;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of {@link SearchFilter} subclasses.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $filters;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
protected $params;
|
|
|
|
*/
|
2008-08-09 06:06:52 +02:00
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
function __construct($modelClass, $fields = null, $filters = null) {
|
|
|
|
$this->modelClass = $modelClass;
|
|
|
|
$this->fields = $fields;
|
|
|
|
$this->filters = $filters;
|
|
|
|
|
|
|
|
parent::__construct();
|
|
|
|
}
|
2008-08-09 06:06:52 +02:00
|
|
|
|
2008-08-09 05:54:55 +02:00
|
|
|
/**
|
|
|
|
* Returns scaffolded search fields for UI.
|
|
|
|
*
|
|
|
|
* @return FieldSet
|
|
|
|
*/
|
2008-08-06 05:28:25 +02:00
|
|
|
public function getSearchFields() {
|
2008-08-09 06:06:52 +02:00
|
|
|
// $this->fields is causing weirdness, so we ignore for now, using the default scaffolding
|
|
|
|
//return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields();
|
|
|
|
return singleton($this->modelClass)->scaffoldSearchFields();
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
|
|
|
|
2008-08-09 06:53:34 +02:00
|
|
|
/**
|
2008-08-09 07:57:44 +02:00
|
|
|
* @refactor move to SQLQuery
|
2008-08-09 06:53:34 +02:00
|
|
|
* @todo fix hack
|
|
|
|
*/
|
|
|
|
protected function applyBaseTableFields() {
|
|
|
|
$classes = ClassInfo::dataClassesFor($this->modelClass);
|
2008-08-09 07:57:44 +02:00
|
|
|
$fields = array($this->modelClass.'.*');
|
|
|
|
if($this->modelClass != $classes[0]) $fields[] = $classes[0].'.*';
|
2008-08-09 06:53:34 +02:00
|
|
|
//$fields = array_keys($model->db());
|
|
|
|
$fields[] = $classes[0].'.ClassName AS RecordClassName';
|
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-08-09 07:57:44 +02:00
|
|
|
* @refactor move to SQLQuery
|
2008-08-09 06:53:34 +02:00
|
|
|
* @todo fix hack
|
|
|
|
*/
|
|
|
|
protected function applyBaseTable() {
|
|
|
|
$classes = ClassInfo::dataClassesFor($this->modelClass);
|
|
|
|
return $classes[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @todo only works for one level deep of inheritance
|
|
|
|
* @todo fix hack
|
2008-08-09 07:57:44 +02:00
|
|
|
* @deprecated - remove me!
|
2008-08-09 06:53:34 +02:00
|
|
|
*/
|
|
|
|
protected function applyBaseTableJoin($query) {
|
|
|
|
$classes = ClassInfo::dataClassesFor($this->modelClass);
|
|
|
|
if (count($classes) > 1) $query->leftJoin($classes[1], "{$classes[1]}.ID = {$classes[0]}.ID");
|
|
|
|
}
|
|
|
|
|
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
|
|
|
*
|
|
|
|
* @param array $searchParams
|
2008-08-09 07:57:44 +02:00
|
|
|
* @param string|array $sort Database column to sort on. Falls back to {@link DataObject::$default_sort} if not provided.
|
|
|
|
* @param string|array $limit
|
|
|
|
* @param SQLQuery $existingQuery
|
2008-08-06 05:28:25 +02:00
|
|
|
* @return SQLQuery
|
|
|
|
*/
|
2008-08-09 07:57:44 +02:00
|
|
|
public function getQuery($searchParams, $sort = false, $limit = false, $existingQuery = null) {
|
2008-08-09 06:06:52 +02:00
|
|
|
$model = singleton($this->modelClass);
|
2008-08-09 06:53:34 +02:00
|
|
|
|
|
|
|
$fields = $this->applyBaseTableFields($model);
|
|
|
|
|
2008-08-09 07:57:44 +02:00
|
|
|
if($existingQuery) {
|
|
|
|
$query = $existingQuery;
|
|
|
|
$query->select = array_merge($query->select,$fields);
|
|
|
|
} else {
|
|
|
|
$query = $model->buildSQL();
|
|
|
|
$query->select($fields);
|
|
|
|
}
|
2008-08-09 06:53:34 +02:00
|
|
|
|
2008-08-09 07:57:44 +02:00
|
|
|
$SQL_limit = Convert::raw2sql($limit);
|
|
|
|
$query->limit($SQL_limit);
|
2008-08-09 07:00:42 +02:00
|
|
|
|
2008-08-09 07:57:44 +02:00
|
|
|
$SQL_sort = (!empty($sort)) ? Convert::raw2sql($sort) : singleton($this->modelClass)->stat('default_sort');
|
|
|
|
$query->orderby($SQL_sort);
|
2008-08-09 06:53:34 +02:00
|
|
|
|
|
|
|
|
2008-08-09 06:06:52 +02:00
|
|
|
foreach($searchParams as $key => $value) {
|
2008-08-09 06:53:34 +02:00
|
|
|
if ($value != '0') {
|
|
|
|
$key = str_replace('__', '.', $key);
|
|
|
|
$filter = $this->getFilter($key);
|
|
|
|
if ($filter) {
|
|
|
|
$filter->setModel($this->modelClass);
|
|
|
|
$filter->setValue($value);
|
|
|
|
$filter->apply($query);
|
|
|
|
}
|
2008-08-09 06:06:52 +02:00
|
|
|
}
|
|
|
|
}
|
2008-08-09 07:57:44 +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
|
|
|
|
*
|
2008-08-06 05:28:25 +02:00
|
|
|
* @param array $searchParams
|
2008-08-09 07:57:44 +02:00
|
|
|
* @param string|array $sort
|
|
|
|
* @param string|array $limit
|
2008-08-06 05:28:25 +02:00
|
|
|
* @return DataObjectSet
|
|
|
|
*/
|
2008-08-09 07:57:44 +02:00
|
|
|
public function getResults($searchParams, $sort = false, $limit = false) {
|
2008-08-09 06:38:44 +02:00
|
|
|
$searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields'));
|
2008-08-09 07:57:44 +02:00
|
|
|
$query = $this->getQuery($searchParams, $sort, $limit);
|
|
|
|
|
|
|
|
//Debug::dump($query->sql());
|
|
|
|
|
2008-08-09 06:06:52 +02:00
|
|
|
// use if a raw SQL query is needed
|
2008-08-09 06:53:34 +02:00
|
|
|
$results = new DataObjectSet();
|
|
|
|
foreach($query->execute() as $row) {
|
|
|
|
$className = $row['RecordClassName'];
|
|
|
|
$results->push(new $className($row));
|
|
|
|
}
|
|
|
|
return $results;
|
2008-08-09 06:06:52 +02:00
|
|
|
//
|
2008-08-09 06:53:34 +02:00
|
|
|
//return DataObject::get($this->modelClass, $query->getFilter(), "", "", $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.
|
|
|
|
*
|
|
|
|
* @param unknown_type $value
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
function clearEmptySearchFields($value) {
|
|
|
|
return ($value != '');
|
|
|
|
}
|
|
|
|
|
2008-08-06 05:28:25 +02:00
|
|
|
/**
|
|
|
|
* @todo documentation
|
|
|
|
* @todo implementation
|
|
|
|
*
|
|
|
|
* @param array $searchFilters
|
|
|
|
* @param SQLQuery $query
|
|
|
|
*/
|
2008-08-09 06:06:52 +02:00
|
|
|
protected function processFilters(SQLQuery $query, $searchParams) {
|
|
|
|
$conditions = array();
|
|
|
|
foreach($this->filters as $field => $filter) {
|
|
|
|
if (strstr($field, '.')) {
|
|
|
|
$path = explode('.', $field);
|
|
|
|
} else {
|
|
|
|
$conditions[] = $filter->apply($searchParams[$field]);
|
|
|
|
}
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
2008-08-09 06:06:52 +02:00
|
|
|
$query->where = $conditions;
|
2008-08-09 06:38:44 +02:00
|
|
|
return $query;
|
2008-08-06 05:28:25 +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;
|
|
|
|
}
|
|
|
|
}
|
2008-08-06 05:28:25 +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
|
|
|
}
|
|
|
|
|
|
|
|
public function setFilters($filters) {
|
|
|
|
$this->filters = $filters;
|
2008-08-09 06:38:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the list of searchable fields in the current search context.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getFields() {
|
|
|
|
return $this->fields;
|
2008-08-06 05:28:25 +02:00
|
|
|
}
|
|
|
|
|
2008-08-09 06:38:44 +02:00
|
|
|
/**
|
|
|
|
* Apply a list of searchable fields to the current search context.
|
|
|
|
*
|
|
|
|
* @param array $fields
|
|
|
|
*/
|
|
|
|
public function setFields($fields) {
|
|
|
|
$this->fields = $fields;
|
2008-08-06 05:43:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Placeholder, until I figure out the rest of the SQLQuery stuff
|
|
|
|
* and link the $searchable_fields array to the SearchContext
|
2008-08-09 06:38:44 +02:00
|
|
|
*
|
|
|
|
* @deprecated in favor of getResults
|
2008-08-06 05:43:48 +02:00
|
|
|
*/
|
|
|
|
public function getResultSet($fields) {
|
|
|
|
$filter = "";
|
|
|
|
$current = 1;
|
|
|
|
$fields = array_filter($fields, array($this,'clearEmptySearchFields'));
|
|
|
|
$length = count($fields);
|
|
|
|
foreach($fields as $key=>$val) {
|
2008-08-09 04:16:46 +02:00
|
|
|
// Array values come from more complex fields - for now let's just disable searching on them
|
|
|
|
if (!is_array($val) && $val != '') {
|
2008-08-06 05:43:48 +02:00
|
|
|
$filter .= "`$key`='$val'";
|
|
|
|
} else {
|
|
|
|
$length--;
|
|
|
|
}
|
|
|
|
if ($current < $length) {
|
|
|
|
$filter .= " AND ";
|
|
|
|
}
|
|
|
|
$current++;
|
|
|
|
}
|
|
|
|
return DataObject::get($this->modelClass, $filter);
|
|
|
|
}
|
2008-08-06 05:28:25 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
?>
|