mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge remote branch 'origin/master' into translation-staging
This commit is contained in:
commit
33b1ff3e57
@ -205,13 +205,18 @@ This would be equivalent to a SQL query of
|
||||
|
||||
### Search Filter Modifiers
|
||||
|
||||
The where clauses showcased in the previous two sections (filter and exclude) specify case-insensitive exact
|
||||
The where clauses showcased in the previous two sections (filter and exclude) specify exact
|
||||
matches by default. However, there are a number of suffixes that you can put on field names to change this
|
||||
behaviour `":StartsWith"`, `":EndsWith"`, `":PartialMatch"`, `":GreaterThan"`, `":LessThan"`, `":Negation"`.
|
||||
|
||||
Each of these suffixes is represented in the ORM as a subclass of `[api:SearchFilter]`. Developers can define
|
||||
their own SearchFilters if needing to extend the ORM filter and exclude behaviours.
|
||||
|
||||
These suffixes can also take modifiers themselves. The modifiers currently supported are `":not"`, `":nocase"`
|
||||
and `":case"`. These negate the filter, make it case-insensitive and make it case-sensitive respectively. The
|
||||
default comparison uses the database's default. For MySQL and MSSQL, this is case-insensitive. For PostgreSQL,
|
||||
this is case-sensitive.
|
||||
|
||||
The following is a query which will return everyone whose first name doesn't start with S, who has logged in
|
||||
since 1/1/2011.
|
||||
|
||||
|
@ -374,38 +374,15 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
||||
* Return a new instance of the list with an added filter
|
||||
*/
|
||||
public function addFilter($filterArray) {
|
||||
$SQL_Statements = array();
|
||||
foreach($filterArray as $field => $value) {
|
||||
if(is_array($value)) {
|
||||
$customQuery = 'IN (\''.implode('\',\'',Convert::raw2sql($value)).'\')';
|
||||
} else {
|
||||
$customQuery = '= \''.Convert::raw2sql($value).'\'';
|
||||
}
|
||||
|
||||
if(stristr($field,':')) {
|
||||
$fieldArgs = explode(':',$field);
|
||||
$field = array_shift($fieldArgs);
|
||||
foreach($fieldArgs as $fieldArg){
|
||||
$comparisor = $this->applyFilterContext($field, $fieldArg, $value);
|
||||
}
|
||||
} else {
|
||||
if($field == 'ID') {
|
||||
$field = sprintf('"%s"."ID"', ClassInfo::baseDataClass($this->dataClass));
|
||||
} else {
|
||||
$field = '"' . Convert::raw2sql($field) . '"';
|
||||
}
|
||||
|
||||
$SQL_Statements[] = $field . ' ' . $customQuery;
|
||||
}
|
||||
$fieldArgs = explode(':', $field);
|
||||
$field = array_shift($fieldArgs);
|
||||
$filterType = array_shift($fieldArgs);
|
||||
$modifiers = $fieldArgs;
|
||||
$this->applyFilterContext($field, $filterType, $modifiers, $value);
|
||||
}
|
||||
|
||||
if(!count($SQL_Statements)) return $this;
|
||||
|
||||
return $this->alterDataQuery_30(function($query) use ($SQL_Statements){
|
||||
foreach($SQL_Statements as $SQL_Statement){
|
||||
$query->where($SQL_Statement);
|
||||
}
|
||||
});
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,16 +436,22 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
||||
*
|
||||
* @param string $field - the fieldname in the db
|
||||
* @param string $comparisators - example StartsWith, relates to a filtercontext
|
||||
* @param array $modifiers - Modifiers to pass to the filter, ie not,nocase
|
||||
* @param string $value - the value that the filtercontext will use for matching
|
||||
* @todo Deprecated SearchContexts and pull their functionality into the core of the ORM
|
||||
*/
|
||||
private function applyFilterContext($field, $comparisators, $value) {
|
||||
private function applyFilterContext($field, $comparisators, $modifiers, $value) {
|
||||
$t = singleton($this->dataClass())->dbObject($field);
|
||||
$className = "{$comparisators}Filter";
|
||||
if(!class_exists($className)){
|
||||
throw new InvalidArgumentException('There are no '.$comparisators.' comparisator');
|
||||
if($comparisators) {
|
||||
$className = "{$comparisators}Filter";
|
||||
} else {
|
||||
$className = 'ExactMatchFilter';
|
||||
}
|
||||
$t = new $className($field,$value);
|
||||
if(!class_exists($className)){
|
||||
$className = 'ExactMatchFilter';
|
||||
array_unshift($modifiers, $comparisators);
|
||||
}
|
||||
$t = new $className($field, $value, $modifiers);
|
||||
$t->apply($this->dataQuery());
|
||||
}
|
||||
|
||||
@ -500,25 +483,29 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
||||
throw new InvalidArgumentException('Incorrect number of arguments passed to exclude()');
|
||||
}
|
||||
|
||||
$SQL_Statements = array();
|
||||
foreach($whereArguments as $fieldName => $value) {
|
||||
if($fieldName == 'ID') {
|
||||
$fieldName = sprintf('"%s"."ID"', ClassInfo::baseDataClass($this->dataClass));
|
||||
} else {
|
||||
$fieldName = '"' . Convert::raw2sql($fieldName) . '"';
|
||||
return $this->alterDataQuery(function($query, $list) use ($whereArguments) {
|
||||
$subquery = $query->disjunctiveGroup();
|
||||
|
||||
foreach($whereArguments as $field => $value) {
|
||||
$fieldArgs = explode(':', $field);
|
||||
$field = array_shift($fieldArgs);
|
||||
$filterType = array_shift($fieldArgs);
|
||||
$modifiers = $fieldArgs;
|
||||
|
||||
// This is here since PHP 5.3 can't call protected/private methods in a closure.
|
||||
$t = singleton($list->dataClass())->dbObject($field);
|
||||
if($filterType) {
|
||||
$className = "{$filterType}Filter";
|
||||
} else {
|
||||
$className = 'ExactMatchFilter';
|
||||
}
|
||||
if(!class_exists($className)){
|
||||
$className = 'ExactMatchFilter';
|
||||
array_unshift($modifiers, $filterType);
|
||||
}
|
||||
$t = new $className($field, $value, $modifiers);
|
||||
$t->exclude($subquery);
|
||||
}
|
||||
|
||||
if(is_array($value)){
|
||||
$SQL_Statements[] = ($fieldName . ' NOT IN (\''.implode('\',\'', Convert::raw2sql($value)).'\')');
|
||||
} else {
|
||||
$SQL_Statements[] = ($fieldName . ' != \''.Convert::raw2sql($value).'\'');
|
||||
}
|
||||
}
|
||||
|
||||
if(!count($SQL_Statements)) return $this;
|
||||
|
||||
return $this->alterDataQuery_30(function($query) use ($SQL_Statements){
|
||||
$query->whereAny($SQL_Statements);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -17,23 +17,102 @@
|
||||
* @subpackage search
|
||||
*/
|
||||
class EndsWithFilter extends SearchFilter {
|
||||
protected function comparison($exclude = false) {
|
||||
$modifiers = $this->getModifiers();
|
||||
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
|
||||
}
|
||||
if(DB::getConn() instanceof PostgreSQLDatabase) {
|
||||
if(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE';
|
||||
} else {
|
||||
$comparison = 'ILIKE';
|
||||
}
|
||||
} elseif(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE BINARY';
|
||||
} else {
|
||||
$comparison = 'LIKE';
|
||||
}
|
||||
if($exclude) {
|
||||
$comparison = 'NOT ' . $comparison;
|
||||
}
|
||||
return $comparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a match on the trailing characters of a field value.
|
||||
*
|
||||
* @return unknown
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s %s '%%%s'",
|
||||
$this->getDbName(),
|
||||
(DB::getConn() instanceof PostgreSQLDatabase) ? 'ILIKE' : 'LIKE',
|
||||
$this->comparison(false),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a match on the trailing characters of a field value.
|
||||
* Matches against one of the many values.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function applyMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$connectives = array();
|
||||
foreach($this->getValue() as $value) {
|
||||
$connectives[] = sprintf(
|
||||
"%s %s '%%%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(false),
|
||||
Convert::raw2sql($value)
|
||||
);
|
||||
}
|
||||
$whereClause = implode(' OR ', $connectives);
|
||||
return $query->where($whereClause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a match on the trailing characters of a field value.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s NOT %s '%%%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(true),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a match on the trailing characters of a field value.
|
||||
* Excludes a field if it matches any of the values.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$connectives = array();
|
||||
foreach($this->getValue() as $value) {
|
||||
$connectives[] = sprintf(
|
||||
"%s NOT %s '%%%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(true),
|
||||
Convert::raw2sql($value)
|
||||
);
|
||||
}
|
||||
$whereClause = implode(' AND ', $connectives);
|
||||
return $query->where($whereClause);
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -14,22 +14,134 @@
|
||||
* @subpackage search
|
||||
*/
|
||||
class ExactMatchFilter extends SearchFilter {
|
||||
|
||||
protected function comparison($exclude = false) {
|
||||
$modifiers = $this->getModifiers();
|
||||
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
|
||||
}
|
||||
if(!in_array('case', $modifiers) && !in_array('nocase', $modifiers)) {
|
||||
if($exclude) {
|
||||
return '!=';
|
||||
} else {
|
||||
return '=';
|
||||
}
|
||||
} elseif(DB::getConn() instanceof PostgreSQLDatabase) {
|
||||
if(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE';
|
||||
} else {
|
||||
$comparison = 'ILIKE';
|
||||
}
|
||||
} elseif(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE BINARY';
|
||||
} else {
|
||||
$comparison = 'LIKE';
|
||||
}
|
||||
if($exclude) {
|
||||
$comparison = 'NOT ' . $comparison;
|
||||
}
|
||||
return $comparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an exact match (equals) on a field value.
|
||||
*
|
||||
* @return unknown
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s = '%s'",
|
||||
"%s %s '%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(false),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an exact match (equals) on a field value against multiple
|
||||
* possible values.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function applyMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$values = array();
|
||||
foreach($this->getValue() as $value) {
|
||||
$values[] = Convert::raw2sql($value);
|
||||
}
|
||||
if($this->comparison(false) == '=') {
|
||||
// Neither :case nor :nocase
|
||||
$valueStr = "'" . implode("', '", $values) . "'";
|
||||
return $query->where(sprintf(
|
||||
'%s IN (%s)',
|
||||
$this->getDbName(),
|
||||
$valueStr
|
||||
));
|
||||
} else {
|
||||
foreach($values as &$v) {
|
||||
$v = sprintf(
|
||||
"%s %s '%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(false),
|
||||
$v
|
||||
);
|
||||
}
|
||||
$where = implode(' OR ', $values);
|
||||
return $query->where($where);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes an exact match (equals) on a field value.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s %s '%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(true),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes an exact match (equals) on a field value against multiple
|
||||
* possible values.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$values = array();
|
||||
foreach($this->getValue() as $value) {
|
||||
$values[] = Convert::raw2sql($value);
|
||||
}
|
||||
if($this->comparison(false) == '=') {
|
||||
// Neither :case nor :nocase
|
||||
$valueStr = "'" . implode("', '", $values) . "'";
|
||||
return $query->where(sprintf(
|
||||
'%s NOT IN (%s)',
|
||||
$this->getDbName(),
|
||||
$valueStr
|
||||
));
|
||||
} else {
|
||||
foreach($values as &$v) {
|
||||
$v = sprintf(
|
||||
"%s %s '%s'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(true),
|
||||
$v
|
||||
);
|
||||
}
|
||||
$where = implode(' OR ', $values);
|
||||
return $query->where($where);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -7,46 +7,46 @@
|
||||
/**
|
||||
* Checks if a value is in a given set.
|
||||
* SQL syntax used: Column IN ('val1','val2')
|
||||
* @deprecated 3.1 Use ExactMatchFilter instead
|
||||
*
|
||||
* @todo Add negation (NOT IN)6
|
||||
* @package framework
|
||||
* @subpackage search
|
||||
*/
|
||||
class ExactMatchMultiFilter extends SearchFilter {
|
||||
function __construct($fullName, $value = false, array $modifiers = array()) {
|
||||
Deprecation::notice('3.1', 'Use ExactMatchFilter instead.');
|
||||
parent::__construct($fullName, $value, $modifiers);
|
||||
}
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
// hack
|
||||
// PREVIOUS $values = explode(',',$this->getValue());
|
||||
$values = array();
|
||||
if (is_string($this->getValue())) {
|
||||
if (!is_array($this->getValue())) {
|
||||
$values = explode(',',$this->getValue());
|
||||
} else {
|
||||
$values = $this->getValue();
|
||||
}
|
||||
else {
|
||||
foreach($this->getValue() as $v) {
|
||||
$values[] = $v;
|
||||
}
|
||||
$filter = new ExactMatchFilter($this->getFullName(), $values, $this->getModifiers());
|
||||
return $filter->apply($query);
|
||||
}
|
||||
|
||||
protected function applyOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function exclude(DataQuery $query) {
|
||||
if (!is_array($this->getValue())) {
|
||||
$values = explode(',',$this->getValue());
|
||||
} else {
|
||||
$values = $this->getValue();
|
||||
}
|
||||
|
||||
|
||||
if(! $values) return false;
|
||||
for($i = 0; $i < count($values); $i++) {
|
||||
if(! is_numeric($values[$i])) {
|
||||
// @todo Fix string replacement to only replace leading and tailing quotes
|
||||
$values[$i] = str_replace("'", '', $values[$i]);
|
||||
$values[$i] = Convert::raw2sql($values[$i]);
|
||||
}
|
||||
}
|
||||
$SQL_valueStr = "'" . implode("','", $values) . "'";
|
||||
|
||||
return $query->where(sprintf(
|
||||
"%s IN (%s)",
|
||||
$this->getDbName(),
|
||||
$SQL_valueStr
|
||||
));
|
||||
$filter = new ExactMatchFilter($this->getFullName(), $values, $this->getModifiers());
|
||||
return $filter->exclude($query);
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
*/
|
||||
class FulltextFilter extends SearchFilter {
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
return $query->where(sprintf(
|
||||
"MATCH (%s) AGAINST ('%s')",
|
||||
$this->getDbName(),
|
||||
@ -35,7 +35,15 @@ class FulltextFilter extends SearchFilter {
|
||||
));
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
return $query->where(sprintf(
|
||||
"NOT MATCH (%s) AGAINST ('%s')",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,9 @@
|
||||
class GreaterThanFilter extends SearchFilter {
|
||||
|
||||
/**
|
||||
* @return $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$value = $this->getDbFormattedValue();
|
||||
|
||||
@ -21,8 +21,21 @@ class GreaterThanFilter extends SearchFilter {
|
||||
|
||||
return $query->where($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$value = $this->getDbFormattedValue();
|
||||
|
||||
if(is_numeric($value)) $filter = sprintf("%s <= %s", $this->getDbName(), Convert::raw2sql($value));
|
||||
else $filter = sprintf("%s <= '%s'", $this->getDbName(), Convert::raw2sql($value));
|
||||
|
||||
return $query->where($filter);
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,9 @@
|
||||
class LessThanFilter extends SearchFilter {
|
||||
|
||||
/**
|
||||
* @return $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$value = $this->getDbFormattedValue();
|
||||
|
||||
@ -21,8 +21,21 @@ class LessThanFilter extends SearchFilter {
|
||||
|
||||
return $query->where($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$value = $this->getDbFormattedValue();
|
||||
|
||||
if(is_numeric($value)) $filter = sprintf("%s >= %s", $this->getDbName(), Convert::raw2sql($value));
|
||||
else $filter = sprintf("%s >= '%s'", $this->getDbName(), Convert::raw2sql($value));
|
||||
|
||||
return $query->where($filter);
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,33 @@
|
||||
/**
|
||||
* Matches on rows where the field is not equal to the given value.
|
||||
*
|
||||
* @deprecated 3.1 Use ExactMatchFilter:not instead
|
||||
* @package framework
|
||||
* @subpackage search
|
||||
*/
|
||||
class NegationFilter extends SearchFilter {
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s != '%s'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
function __construct($fullName, $value = false, array $modifiers = array()) {
|
||||
Deprecation::notice('3.1', 'Use ExactMatchFilter:not instead.');
|
||||
$modifiers[] = 'not';
|
||||
parent::__construct($fullName, $value, $modifiers);
|
||||
}
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
$filter = new ExactMatchFilter($this->getFullName(), $this->getValue(), $this->getModifiers());
|
||||
return $filter->apply($query);
|
||||
}
|
||||
|
||||
protected function applyOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function exclude(DataQuery $query) {
|
||||
$filter = new ExactMatchFilter($this->getFullName(), $this->getValue(), $this->getModifiers());
|
||||
return $filter->exclude($query);
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,24 +11,68 @@
|
||||
* @subpackage search
|
||||
*/
|
||||
class PartialMatchFilter extends SearchFilter {
|
||||
protected function comparison($exclude = false) {
|
||||
$modifiers = $this->getModifiers();
|
||||
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
|
||||
}
|
||||
if(DB::getConn() instanceof PostgreSQLDatabase) {
|
||||
if(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE';
|
||||
} else {
|
||||
$comparison = 'ILIKE';
|
||||
}
|
||||
} elseif(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE BINARY';
|
||||
} else {
|
||||
$comparison = 'LIKE';
|
||||
}
|
||||
if($exclude) {
|
||||
$comparison = 'NOT ' . $comparison;
|
||||
}
|
||||
return $comparison;
|
||||
}
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$comparison = $this->comparison(false);
|
||||
$where = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue()));
|
||||
|
||||
return $query->where($where);
|
||||
}
|
||||
|
||||
protected function applyMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$where = array();
|
||||
$comparison = (DB::getConn() instanceof PostgreSQLDatabase) ? 'ILIKE' : 'LIKE';
|
||||
if(is_array($this->getValue())) {
|
||||
foreach($this->getValue() as $value) {
|
||||
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value));
|
||||
}
|
||||
|
||||
} else {
|
||||
$where[] = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue()));
|
||||
$comparison = $this->comparison(false);
|
||||
foreach($this->getValue() as $value) {
|
||||
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value));
|
||||
}
|
||||
|
||||
return $query->where(implode(' OR ', $where));
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$comparison = $this->comparison(true);
|
||||
$where = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue()));
|
||||
|
||||
return $query->where($where);
|
||||
}
|
||||
|
||||
protected function excludeMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$where = array();
|
||||
$comparison = $this->comparison(true);
|
||||
foreach($this->getValue() as $value) {
|
||||
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value));
|
||||
}
|
||||
|
||||
return $query->where(implode(' AND ', $where));
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,11 @@ abstract class SearchFilter extends Object {
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $modifiers;
|
||||
|
||||
/**
|
||||
* @var string Name of a has-one, has-many or many-many relation (not the classname).
|
||||
* Set in the constructor as part of the name in dot-notation, and used in
|
||||
@ -43,12 +48,14 @@ abstract class SearchFilter extends Object {
|
||||
* the necessary tables (e.g. "Comments.Name" to join the "Comments" has-many relationship and
|
||||
* search the "Name" column when applying this filter to a SiteTree class).
|
||||
* @param mixed $value
|
||||
* @param array $modifiers
|
||||
*/
|
||||
public function __construct($fullName, $value = false) {
|
||||
public function __construct($fullName, $value = false, array $modifiers = array()) {
|
||||
$this->fullName = $fullName;
|
||||
// sets $this->name and $this->relation
|
||||
$this->addRelation($fullName);
|
||||
$this->value = $value;
|
||||
$this->modifiers = array_map('strtolower', $modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,6 +102,24 @@ abstract class SearchFilter extends Object {
|
||||
public function getValue() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current modifiers to apply to the filter
|
||||
*
|
||||
* @param array $modifiers
|
||||
*/
|
||||
public function setModifiers(array $modifiers) {
|
||||
$this->modifiers = array_map('strtolower', $modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the current modifiers to apply to the filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getModifiers() {
|
||||
return $this->modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* The original name of the field.
|
||||
@ -173,10 +198,74 @@ abstract class SearchFilter extends Object {
|
||||
/**
|
||||
* Apply filter criteria to a SQL query.
|
||||
*
|
||||
* @param SQLQuery $query
|
||||
* @return SQLQuery
|
||||
* @param DataQuery $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
abstract public function apply(DataQuery $query);
|
||||
public function apply(DataQuery $query) {
|
||||
if(($key = array_search('not', $this->modifiers)) !== false) {
|
||||
unset($this->modifiers[$key]);
|
||||
return $this->exclude($query);
|
||||
}
|
||||
if(is_array($this->value)) {
|
||||
return $this->applyMany($query);
|
||||
} else {
|
||||
return $this->applyOne($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter criteria to a SQL query with a single value.
|
||||
*
|
||||
* @param DataQuery $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
abstract protected function applyOne(DataQuery $query);
|
||||
|
||||
/**
|
||||
* Apply filter criteria to a SQL query with an array of values.
|
||||
*
|
||||
* @param DataQuery $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function applyMany(DataQuery $query) {
|
||||
throw new InvalidArgumentException(get_class($this) . "can't be used to filter by a list of items.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude filter criteria from a SQL query.
|
||||
*
|
||||
* @param DataQuery $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function exclude(DataQuery $query) {
|
||||
if(($key = array_search('not', $this->modifiers)) !== false) {
|
||||
unset($this->modifiers[$key]);
|
||||
return $this->apply($query);
|
||||
}
|
||||
if(is_array($this->value)) {
|
||||
return $this->excludeMany($query);
|
||||
} else {
|
||||
return $this->excludeOne($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude filter criteria from a SQL query with a single value.
|
||||
*
|
||||
* @param DataQuery $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
abstract protected function excludeOne(DataQuery $query);
|
||||
|
||||
/**
|
||||
* Exclude filter criteria from a SQL query with an array of values.
|
||||
*
|
||||
* @param DataQuery $query
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeMany(DataQuery $query) {
|
||||
throw new InvalidArgumentException(get_class($this) . "can't be used to filter by a list of items.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a field has a value,
|
||||
|
@ -17,23 +17,102 @@
|
||||
* @subpackage search
|
||||
*/
|
||||
class StartsWithFilter extends SearchFilter {
|
||||
protected function comparison($exclude = false) {
|
||||
$modifiers = $this->getModifiers();
|
||||
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
|
||||
}
|
||||
if(DB::getConn() instanceof PostgreSQLDatabase) {
|
||||
if(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE';
|
||||
} else {
|
||||
$comparison = 'ILIKE';
|
||||
}
|
||||
} elseif(in_array('case', $modifiers)) {
|
||||
$comparison = 'LIKE BINARY';
|
||||
} else {
|
||||
$comparison = 'LIKE';
|
||||
}
|
||||
if($exclude) {
|
||||
$comparison = 'NOT ' . $comparison;
|
||||
}
|
||||
return $comparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a substring match on a field value.
|
||||
* Applies a match on the starting characters of a field value.
|
||||
*
|
||||
* @return unknown
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s %s '%s%%'",
|
||||
$this->getDbName(),
|
||||
(DB::getConn() instanceof PostgreSQLDatabase) ? 'ILIKE' : 'LIKE',
|
||||
$this->comparison(false),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a match on the starting characters of a field value.
|
||||
* Matches against one of the many values.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function applyMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$connectives = array();
|
||||
foreach($this->getValue() as $value) {
|
||||
$connectives[] = sprintf(
|
||||
"%s %s '%s%%'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(false),
|
||||
Convert::raw2sql($value)
|
||||
);
|
||||
}
|
||||
$whereClause = implode(' OR ', $connectives);
|
||||
return $query->where($whereClause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a match on the starting characters of a field value.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s %s '%s%%'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(true),
|
||||
Convert::raw2sql($this->getValue())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a match on the starting characters of a field value.
|
||||
* Excludes a field if it matches any of the values.
|
||||
*
|
||||
* @return DataQuery
|
||||
*/
|
||||
protected function excludeMany(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$connectives = array();
|
||||
foreach($this->getValue() as $value) {
|
||||
$connectives[] = sprintf(
|
||||
"%s %s '%s%%'",
|
||||
$this->getDbName(),
|
||||
$this->comparison(true),
|
||||
Convert::raw2sql($value)
|
||||
);
|
||||
}
|
||||
$whereClause = implode(' AND ', $connectives);
|
||||
return $query->where($whereClause);
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -6,29 +6,47 @@
|
||||
|
||||
/**
|
||||
* Checks if a value starts with one of the items of in a given set.
|
||||
* SQL syntax used: Column IN ('val1','val2')
|
||||
* @deprecated 3.1 Use StartsWithFilter instead
|
||||
*
|
||||
* @todo Add negation (NOT IN)6
|
||||
* @package framework
|
||||
* @subpackage search
|
||||
*/
|
||||
class StartsWithMultiFilter extends SearchFilter {
|
||||
function __construct($fullName, $value = false, array $modifiers = array()) {
|
||||
Deprecation::notice('3.1', 'Use StartsWithFilter instead.');
|
||||
parent::__construct($fullName, $value, $modifiers);
|
||||
}
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$values = explode(',', $this->getValue());
|
||||
|
||||
foreach($values as $value) {
|
||||
$matches[] = sprintf("%s LIKE '%s%%'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql(str_replace("'", '', $value))
|
||||
);
|
||||
if (!is_array($this->getValue())) {
|
||||
$values = explode(',',$this->getValue());
|
||||
} else {
|
||||
$values = $this->getValue();
|
||||
}
|
||||
|
||||
return $query->where(implode(" OR ", $matches));
|
||||
$filter = new StartsWithFilter($this->getFullName(), $values, $this->getModifiers());
|
||||
return $filter->apply($query);
|
||||
}
|
||||
|
||||
protected function applyOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function exclude(DataQuery $query) {
|
||||
if (!is_array($this->getValue())) {
|
||||
$values = explode(',',$this->getValue());
|
||||
} else {
|
||||
$values = $this->getValue();
|
||||
}
|
||||
$filter = new StartsWithFilter($this->getFullName(), $values, $this->getModifiers());
|
||||
return $filter->exclude($query);
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
@ -12,22 +12,33 @@
|
||||
* @subpackage search
|
||||
*/
|
||||
class SubstringFilter extends PartialMatchFilter {
|
||||
public function __construct($fullName, $value = false) {
|
||||
public function __construct($fullName, $value = false, array $modifiers = array()) {
|
||||
Deprecation::notice('3.0', 'PartialMatchFilter instead.');
|
||||
SearchFilter::__construct($fullName, $value);
|
||||
parent::__construct($fullName, $value, $modifiers);
|
||||
}
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"LOCATE('%s', %s) != 0",
|
||||
Convert::raw2sql($this->getValue()),
|
||||
$this->getDbName()
|
||||
));
|
||||
$values = $this->getValue();
|
||||
$filter = new PartialMatchFilter($this->getFullName(), $values, $this->getModifiers());
|
||||
return $filter->apply($query);
|
||||
}
|
||||
|
||||
protected function applyOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function exclude(DataQuery $query) {
|
||||
$values = $this->getValue();
|
||||
$filter = new PartialMatchFilter($this->getFullName(), $values, $this->getModifiers());
|
||||
return $filter->exclude($query);
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return $this->getValue() == null || $this->getValue() == '';
|
||||
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class WithinRangeFilter extends SearchFilter {
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
protected function applyOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s >= '%s' AND %s <= '%s'",
|
||||
@ -35,6 +35,15 @@ class WithinRangeFilter extends SearchFilter {
|
||||
Convert::raw2sql($this->max)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function excludeOne(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->where(sprintf(
|
||||
"%s < '%s' OR %s > '%s'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->min),
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->max)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -442,10 +442,21 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->filter(array(
|
||||
'Name'=>array('Bob','Phil'),
|
||||
'TeamID'=>array($this->idFromFixture('DataObjectTest_Team', 'team1'))));
|
||||
$this->assertEquals(1, $list->count(), 'There should be one comments');
|
||||
$this->assertEquals(1, $list->count(), 'There should be one comment');
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
|
||||
}
|
||||
|
||||
public function testFilterWithModifiers() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$nocaseList = $list->filter('Name:nocase', 'bob');
|
||||
$this->assertEquals(1, $nocaseList->count(), 'There should be one comment');
|
||||
$caseList = $list->filter('Name:case', 'bob');
|
||||
$this->assertEquals(0, $caseList->count(), 'There should be no comments');
|
||||
$gtList = $list->filter('TeamID:GreaterThan:not',
|
||||
$this->idFromFixture('DataObjectTest_Team', 'team1'));
|
||||
$this->assertEquals(2, $gtList->count());
|
||||
}
|
||||
|
||||
public function testFilterAndExcludeById() {
|
||||
$id = $this->idFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$list = DataObjectTest_SubTeam::get()->filter('ID', $id);
|
||||
@ -501,7 +512,7 @@ class DataListTest extends SapphireTest {
|
||||
*/
|
||||
public function testMultipleExclude() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list->exclude(array('Name'=>'Bob', 'Comment'=>'This is a team comment by Bob'));
|
||||
$list = $list->exclude(array('Name'=>'Bob', 'Comment'=>'This is a team comment by Bob'));
|
||||
$this->assertEquals(2, $list->count());
|
||||
}
|
||||
|
||||
@ -514,9 +525,17 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->exclude('Name', 'Bob');
|
||||
|
||||
$this->assertContains(
|
||||
'WHERE ("Comment" = \'Phil is a unique guy, and comments on team2\') AND ("Name" != \'Bob\')',
|
||||
'WHERE ("DataObjectTest_TeamComment"."Comment" = '
|
||||
. '\'Phil is a unique guy, and comments on team2\') '
|
||||
. 'AND (("DataObjectTest_TeamComment"."Name" != \'Bob\'))',
|
||||
$list->sql());
|
||||
}
|
||||
|
||||
public function testExcludeWithSearchFilter() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->exclude('Name:LessThan', 'Bob');
|
||||
$this->assertContains('WHERE (("DataObjectTest_TeamComment"."Name" >= \'Bob\'))', $list->sql());
|
||||
}
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43
|
||||
|
Loading…
Reference in New Issue
Block a user