From 6cc91745c5383e4b0fd42bd80d33759e6b57908e Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Mon, 6 Sep 2021 19:40:10 -0400 Subject: [PATCH] SearchKit - Filter search listing by author, adding support for OR in filters --- .../SearchDisplay/AbstractRunAction.php | 92 +++++++++++-------- .../crmSearchAdminSearchListing.component.js | 3 + .../crmSearchAdminSearchListing.html | 3 +- .../api/v4/SearchDisplay/SearchRunTest.php | 16 +++- 4 files changed, 71 insertions(+), 43 deletions(-) diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index d18254c7c1..cdd605f723 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -186,17 +186,19 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { * Applies supplied filters to the where clause */ protected function applyFilters() { + // Allow all filters that are included in SELECT clause or are fields on the Afform. + $allowedFilters = array_merge($this->getSelectAliases(), $this->getAfformFilters()); + // Ignore empty strings $filters = array_filter($this->filters, [$this, 'hasValue']); if (!$filters) { return; } - // Process all filters that are included in SELECT clause or are allowed by the Afform. - $allowedFilters = array_merge($this->getSelectAliases(), $this->getAfformFilters()); - foreach ($filters as $fieldName => $value) { - if (in_array($fieldName, $allowedFilters, TRUE)) { - $this->applyFilter($fieldName, $value); + foreach ($filters as $key => $value) { + $fieldNames = explode(',', $key); + if (in_array($key, $allowedFilters, TRUE) || !array_diff($fieldNames, $allowedFilters)) { + $this->applyFilter($fieldNames, $value); } } } @@ -221,13 +223,16 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { } /** - * @param string $fieldName + * @param array $fieldNames + * If multiple field names are given they will be combined in an OR clause * @param mixed $value */ - private function applyFilter(string $fieldName, $value) { + private function applyFilter(array $fieldNames, $value) { // Global setting determines if % wildcard should be added to both sides (default) or only the end of a search string $prefixWithWildcard = \Civi::settings()->get('includeWildCardInName'); + // Based on the first field, decide which clause to add this condition to + $fieldName = $fieldNames[0]; $field = $this->getField($fieldName); // If field is not found it must be an aggregated column & belongs in the HAVING clause. if (!$field) { @@ -248,44 +253,57 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { $clause =& $this->savedSearch['api_params']['where']; } - $dataType = $field['data_type'] ?? NULL; - - // Array is either associative `OP => VAL` or sequential `IN (...)` - if (is_array($value)) { - $value = array_filter($value, [$this, 'hasValue']); - // If array does not contain operators as keys, assume array of values - if (array_diff_key($value, array_flip(CoreUtil::getOperators()))) { - // Use IN for regular fields - if (empty($field['serialize'])) { - $clause[] = [$fieldName, 'IN', $value]; + $filterClauses = []; + + foreach ($fieldNames as $fieldName) { + $field = $this->getField($fieldName); + $dataType = $field['data_type'] ?? NULL; + // Array is either associative `OP => VAL` or sequential `IN (...)` + if (is_array($value)) { + $value = array_filter($value, [$this, 'hasValue']); + // If array does not contain operators as keys, assume array of values + if (array_diff_key($value, array_flip(CoreUtil::getOperators()))) { + // Use IN for regular fields + if (empty($field['serialize'])) { + $filterClauses[] = [$fieldName, 'IN', $value]; + } + // Use an OR group of CONTAINS for array fields + else { + $orGroup = []; + foreach ($value as $val) { + $orGroup[] = [$fieldName, 'CONTAINS', $val]; + } + $filterClauses[] = ['OR', $orGroup]; + } } - // Use an OR group of CONTAINS for array fields + // Operator => Value array else { - $orGroup = []; - foreach ($value as $val) { - $orGroup[] = [$fieldName, 'CONTAINS', $val]; + $andGroup = []; + foreach ($value as $operator => $val) { + $andGroup[] = [$fieldName, $operator, $val]; } - $clause[] = ['OR', $orGroup]; + $filterClauses[] = ['AND', $andGroup]; } } - // Operator => Value array + elseif (!empty($field['serialize'])) { + $filterClauses[] = [$fieldName, 'CONTAINS', $value]; + } + elseif (!empty($field['options']) || in_array($dataType, ['Integer', 'Boolean', 'Date', 'Timestamp'])) { + $filterClauses[] = [$fieldName, '=', $value]; + } + elseif ($prefixWithWildcard) { + $filterClauses[] = [$fieldName, 'CONTAINS', $value]; + } else { - foreach ($value as $operator => $val) { - $clause[] = [$fieldName, $operator, $val]; - } + $filterClauses[] = [$fieldName, 'LIKE', $value . '%']; } } - elseif (!empty($field['serialize'])) { - $clause[] = [$fieldName, 'CONTAINS', $value]; - } - elseif (!empty($field['options']) || in_array($dataType, ['Integer', 'Boolean', 'Date', 'Timestamp'])) { - $clause[] = [$fieldName, '=', $value]; - } - elseif ($prefixWithWildcard) { - $clause[] = [$fieldName, 'CONTAINS', $value]; + // Single field + if (count($filterClauses) === 1) { + $clause[] = $filterClauses[0]; } else { - $clause[] = [$fieldName, 'LIKE', $value . '%']; + $clause[] = ['OR', $filterClauses]; } } @@ -383,11 +401,11 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { $filterAttr = $afform['searchDisplay']['filters'] ?? NULL; if ($filterAttr && is_string($filterAttr) && $filterAttr[0] === '{') { foreach (\CRM_Utils_JS::decode($filterAttr) as $filterKey => $filterVal) { - $filterKeys[] = $filterKey; // Automatically apply filters from the markup if they have a value // (if it's a javascript variable it will have come back from decode() as NULL and we'll ignore it). + unset($this->filters[$filterKey]); if ($this->hasValue($filterVal)) { - $this->applyFilter($filterKey, $filterVal); + $this->applyFilter(explode(',', $filterKey), $filterVal); } } } diff --git a/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.component.js b/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.component.js index 933fa52889..f80bb64878 100644 --- a/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.component.js @@ -27,6 +27,9 @@ 'api_entity', 'api_entity:label', 'api_params', + // These two need to be in the select clause so they are allowed as filters + 'created_id.display_name', + 'modified_id.display_name', 'created_date', 'modified_date', 'DATE(created_date) AS date_created', diff --git a/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.html b/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.html index eb888f0000..473f38b89e 100644 --- a/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.html +++ b/ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.html @@ -1,7 +1,8 @@