diff --git a/.docs/filters.md b/.docs/filters.md index c14061ff..ef7b9db5 100644 --- a/.docs/filters.md +++ b/.docs/filters.md @@ -158,6 +158,13 @@ $grid->addFilterText('name', 'Name') ->setSplitWordsSearch(false); ``` +If you need to find rows, that contains "foo" and "bar" (not just one of them), you can use `setConjunctionSearch()`. + +```php +$grid->addFilterText('name', 'Name') + ->setConjunctionSearch(); +``` + ## FilterSelect `FilterSelect` has one more parameter - options: diff --git a/src/DataSource/ArrayDataSource.php b/src/DataSource/ArrayDataSource.php index 2c77935f..9cecfe6c 100644 --- a/src/DataSource/ArrayDataSource.php +++ b/src/DataSource/ArrayDataSource.php @@ -180,11 +180,21 @@ protected function applyFilter(mixed $row, Filter $filter): mixed $row_value = strtolower(Strings::toAscii((string) $row[$column])); + $found = []; + foreach ($words as $word) { if (str_contains($row_value, strtolower(Strings::toAscii($word)))) { - return $row; + if ($filter instanceof FilterText && !$filter->hasConjunctionSearch()) { + return $row; + } else { + $found[] = true; + } } } + + if (count($found) === count($words)) { + return $row; + } } } diff --git a/src/DataSource/DibiFluentDataSource.php b/src/DataSource/DibiFluentDataSource.php index ecce4212..7e66a24d 100755 --- a/src/DataSource/DibiFluentDataSource.php +++ b/src/DataSource/DibiFluentDataSource.php @@ -183,7 +183,7 @@ protected function applyFilterText(FilterText $filter): void } if (count($or) > 1) { - $this->dataSource->where('(%or)', $or); + $this->dataSource->where($filter->hasConjunctionSearch() ? '(%and)' : '(%or)', $or); } else { $this->dataSource->where($or); } diff --git a/src/DataSource/DibiFluentMssqlDataSource.php b/src/DataSource/DibiFluentMssqlDataSource.php index 641ddb71..29fd2ad8 100755 --- a/src/DataSource/DibiFluentMssqlDataSource.php +++ b/src/DataSource/DibiFluentMssqlDataSource.php @@ -120,7 +120,7 @@ protected function applyFilterText(FilterText $filter): void } if (count($or) > 1) { - $this->dataSource->where('(%or)', $or); + $this->dataSource->where($filter->hasConjunctionSearch() ? '(%and)' : '(%or)', $or); } else { $this->dataSource->where($or); } diff --git a/src/DataSource/DibiFluentPostgreDataSource.php b/src/DataSource/DibiFluentPostgreDataSource.php index 3761e806..e18495f2 100755 --- a/src/DataSource/DibiFluentPostgreDataSource.php +++ b/src/DataSource/DibiFluentPostgreDataSource.php @@ -31,7 +31,7 @@ protected function applyFilterText(FilterText $filter): void } if (count($or) > 1) { - $this->dataSource->where('(%or)', $or); + $this->dataSource->where($filter->hasConjunctionSearch() ? '(%and)' : '(%or)', $or); } else { $this->dataSource->where($or); } diff --git a/src/DataSource/DoctrineCollectionDataSource.php b/src/DataSource/DoctrineCollectionDataSource.php index 37af4f7b..c701f2b6 100755 --- a/src/DataSource/DoctrineCollectionDataSource.php +++ b/src/DataSource/DoctrineCollectionDataSource.php @@ -196,7 +196,7 @@ protected function applyFilterText(FilterText $filter): void } } - $expr = call_user_func_array([Criteria::expr(), 'orX'], $exprs); + $expr = call_user_func_array([Criteria::expr(), $filter->hasConjunctionSearch() ? 'andX' : 'orX'], $exprs); $this->criteria->andWhere($expr); } diff --git a/src/DataSource/DoctrineDataSource.php b/src/DataSource/DoctrineDataSource.php index 4249eda2..5ddc5dcf 100755 --- a/src/DataSource/DoctrineDataSource.php +++ b/src/DataSource/DoctrineDataSource.php @@ -278,7 +278,7 @@ protected function applyFilterText(FilterText $filter): void } } - $or = call_user_func_array([$this->dataSource->expr(), 'orX'], $exprs); + $or = call_user_func_array([$this->dataSource->expr(), $filter->hasConjunctionSearch() ? 'andX' : 'orX'], $exprs); $this->dataSource->andWhere($or); } diff --git a/src/DataSource/NetteDatabaseTableDataSource.php b/src/DataSource/NetteDatabaseTableDataSource.php index ff05ddd7..93458dbb 100755 --- a/src/DataSource/NetteDatabaseTableDataSource.php +++ b/src/DataSource/NetteDatabaseTableDataSource.php @@ -194,31 +194,33 @@ protected function applyFilterText(FilterText $filter): void $bigOrArgs = []; $condition = $filter->getCondition(); + $operator = $filter->hasConjunctionSearch() ? 'AND' : 'OR'; + foreach ($condition as $column => $value) { $like = '('; $args = []; if ($filter->isExactSearch()) { - $like .= sprintf('%s = ? OR ', $column); + $like .= sprintf('%s = ? %s ', $column, $operator); $args[] = sprintf('%s', $value); } else { $words = $filter->hasSplitWordsSearch() === false ? [$value] : explode(' ', $value); foreach ($words as $word) { - $like .= sprintf('%s LIKE ? OR ', $column); + $like .= sprintf('%s LIKE ? %s ', $column, $operator); $args[] = sprintf('%%%s%%', $word); } } - $like = substr($like, 0, strlen($like) - 4) . ')'; + $like = substr($like, 0, strlen($like) - (strlen($operator) + 2)) . ')'; $or[] = $like; - $bigOr .= sprintf('%s OR ', $like); + $bigOr .= sprintf('%s %s ', $like, $operator); $bigOrArgs = [...$bigOrArgs, ...$args]; } if (count($or) > 1) { - $bigOr = substr($bigOr, 0, strlen($bigOr) - 4) . ')'; + $bigOr = substr($bigOr, 0, strlen($bigOr) - (strlen($operator) + 2)) . ')'; $query = [...[$bigOr], ...$bigOrArgs]; diff --git a/src/DataSource/NextrasDataSource.php b/src/DataSource/NextrasDataSource.php index 3107124c..8f725da0 100755 --- a/src/DataSource/NextrasDataSource.php +++ b/src/DataSource/NextrasDataSource.php @@ -190,7 +190,7 @@ protected function applyFilterRange(FilterRange $filter): void protected function applyFilterText(FilterText $filter): void { $conditions = [ - ICollection::OR, + $filter->hasConjunctionSearch() ? ICollection::AND : ICollection::OR, ]; foreach ($filter->getCondition() as $column => $value) { diff --git a/src/Filter/FilterText.php b/src/Filter/FilterText.php index 664becb5..e5dd27fc 100644 --- a/src/Filter/FilterText.php +++ b/src/Filter/FilterText.php @@ -16,6 +16,8 @@ class FilterText extends Filter protected bool $splitWordsSearch = true; + protected bool $conjunctionSearch = false; + /** * @param array|string[] $columns */ @@ -84,4 +86,14 @@ public function hasSplitWordsSearch(): bool return $this->splitWordsSearch; } + public function setConjunctionSearch(bool $conjunctionSearch = true): void + { + $this->conjunctionSearch = $conjunctionSearch; + } + + public function hasConjunctionSearch(): bool + { + return $this->conjunctionSearch; + } + } diff --git a/tests/Cases/DataSources/BaseDataSourceTest.phpt b/tests/Cases/DataSources/BaseDataSourceTest.phpt index 39971400..804facdc 100644 --- a/tests/Cases/DataSources/BaseDataSourceTest.phpt +++ b/tests/Cases/DataSources/BaseDataSourceTest.phpt @@ -42,6 +42,11 @@ abstract class BaseDataSourceTest extends TestCase $this->ds->filter([$filter]); Assert::same(2, $this->ds->getCount()); + + $filter->setConjunctionSearch(); + + $this->ds->filter([$filter]); + Assert::same(1, $this->ds->getCount()); } public function testGetData(): void @@ -59,6 +64,13 @@ abstract class BaseDataSourceTest extends TestCase $this->data[0], $this->data[5], ], $this->getActualResultAsArray()); + + $filter->setConjunctionSearch(); + + $this->ds->filter([$filter]); + Assert::equal([ + $this->data[5], + ], $this->getActualResultAsArray()); } public function testFilterMultipleColumns(): void