diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php
index da6309a85..8ddb7eeff 100644
--- a/app/src/Controller/StandardController.php
+++ b/app/src/Controller/StandardController.php
@@ -434,6 +434,21 @@ public function index() {
&& $table->getIndexContains()) {
$query->contain($table->getIndexContains());
}
+
+ // SearchFilterTrait
+ if(method_exists($table, "getSearchableAttributes")) {
+ $searchableAttributes = $table->getSearchableAttributes();
+
+ if(!empty($searchableAttributes)) {
+ foreach(array_keys($searchableAttributes) as $attribute) {
+ if(!empty($this->request->getQuery($attribute))) {
+ $query = $table->whereFilter($query, $attribute, $this->request->getQuery($attribute));
+ }
+ }
+
+ $this->set('vv_searchable_attributes', $searchableAttributes);
+ }
+ }
// The Cake documents describe $this->paginate (which worked in Cake 2),
// but it doesn't seem to work in Cake 4. So we just use $this->pagination
diff --git a/app/src/Lib/Traits/SearchFilterTrait.php b/app/src/Lib/Traits/SearchFilterTrait.php
new file mode 100644
index 000000000..6a13472f9
--- /dev/null
+++ b/app/src/Lib/Traits/SearchFilterTrait.php
@@ -0,0 +1,149 @@
+searchFilters[$attribute]['label'])
+ && $this->searchFilters[$attribute]['label'] !== null) {
+ return $this->searchFilters[$attribute]['label'];
+ }
+
+ // Try to construct a label from the language key.
+ $l = __d('field', $attribute);
+
+ if($l != $attribute) {
+ return $l;
+ }
+
+ // If we make it here, just return $attribute
+ return $attribute;
+ }
+
+ /**
+ * Obtain the set of permitted search attributes.
+ *
+ * @since COmanage Registry v5.0.0
+ * @return array Array of permitted search attributes and configuration elements needed for display
+ */
+
+ public function getSearchableAttributes(): array {
+ // Not every configuration element is necessary for the search form, and
+ // some need to be calculated, so we do that work here.
+
+ $ret = [];
+
+ foreach(array_keys($this->searchFilters) as $attr) {
+ $ret[ $attr ] = [
+ 'label' => $this->getLabel($attr)
+ ];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Add a permitted search filters.
+ *
+ * @since COmanage Registry v5.0.0
+ * @param string $attribute Attribute that filtering is permitted on (database name)
+ * @param bool $caseSensitive Whether this attribute is case sensitive
+ * @param string $label Label for this search field, or null to autocalculate
+ * @param bool $substring Whether substring searching is permitted for this attribute
+ */
+
+ public function setSearchFilter(string $attribute,
+ bool $caseSensitive=false,
+ string $label=null,
+ bool $substring=true): void {
+ $this->searchFilters[$attribute] = compact('caseSensitive', 'label', 'substring');
+ }
+
+ /**
+ * Build a query where() clause for the configured attribute.
+ *
+ * @since COmanage Registry v5.0.0
+ * @param \Cake\ORM\Query $query Cake ORM Query object
+ * @param string $attribute Attribute to filter on (database name)
+ * @param string $q Value to filter on
+ * @return \Cake\ORM\Query Cake ORM Query object
+ */
+
+ public function whereFilter(\Cake\ORM\Query $query, string $attribute, string $q): object {
+ if(!empty($this->searchFilters[$attribute])) {
+ $cs = (isset($this->searchFilters[$attribute]['caseSensitive'])
+ && $this->searchFilters[$attribute]['caseSensitive']);
+
+ $sub = (isset($this->searchFilters[$attribute]['substring'])
+ && $this->searchFilters[$attribute]['substring']);
+
+ $search = $q;
+
+ if($sub) {
+ // Substring
+ // note, for now at least, a user may infix their own %
+ $search .= "%";
+ }
+
+ if($cs) {
+ // Case sensitive
+ $query->where([$attribute => $search]);
+ } else {
+ // Case insensitive
+ $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search, $sub) {
+ $lower = $query->func()->lower([
+ // https://book.cakephp.org/3/en/orm/query-builder.html#function-arguments
+ $attribute => 'identifier'
+ ]);
+ if($sub) {
+ return $exp->like($lower, strtolower($search));
+ } else {
+ return $exp->eq($lower, strtolower($search));
+ }
+ });
+ }
+ }
+ // else not a permitted attribute
+
+ return $query;
+ }
+}
diff --git a/app/src/Model/Table/CousTable.php b/app/src/Model/Table/CousTable.php
index 055fbf5c9..082ad40d6 100644
--- a/app/src/Model/Table/CousTable.php
+++ b/app/src/Model/Table/CousTable.php
@@ -39,6 +39,7 @@ class CousTable extends Table {
use \App\Lib\Traits\CoLinkTrait;
use \App\Lib\Traits\PermissionsTrait;
use \App\Lib\Traits\PrimaryLinkTrait;
+ use \App\Lib\Traits\SearchFilterTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;
@@ -71,6 +72,11 @@ public function initialize(array $config): void {
$this->setPrimaryLink('co_id');
$this->setRequiresCO(true);
+
+ // Set up the fields that may be filtered in the index view
+ $this->setSearchFilter('name', false, null, true);
+ $this->setSearchFilter('parent_id', true, null, false);
+ $this->setSearchFilter('description', false, null, true);
$this->setPermissions([
// Actions that operate over an entity (ie: require an $id)
diff --git a/app/src/Model/Table/TypesTable.php b/app/src/Model/Table/TypesTable.php
index 54b6952a0..856662495 100644
--- a/app/src/Model/Table/TypesTable.php
+++ b/app/src/Model/Table/TypesTable.php
@@ -42,6 +42,7 @@ class TypesTable extends Table {
use \App\Lib\Traits\CoLinkTrait;
use \App\Lib\Traits\PermissionsTrait;
use \App\Lib\Traits\PrimaryLinkTrait;
+ use \App\Lib\Traits\SearchFilterTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;
@@ -112,6 +113,11 @@ public function initialize(array $config): void {
'class' => 'SuspendableStatusEnum'
]
]);
+
+ // Set up the fields that may be filtered in the index view
+ $this->setSearchFilter('display_name', false, null, true);
+ $this->setSearchFilter('attribute', true, null, false);
+ $this->setSearchFilter('statuses', false, null, true);
$this->setPermissions([
// Actions that operate over an entity (ie: require an $id)
diff --git a/app/templates/Cous/columns.inc b/app/templates/Cous/columns.inc
index 72f9f5fd3..d2b50c09b 100644
--- a/app/templates/Cous/columns.inc
+++ b/app/templates/Cous/columns.inc
@@ -25,6 +25,9 @@
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/
+// Turn on the search/filter box for this index view
+$enableFiltering = true;
+
$indexColumns = [
'name' => [
'type' => 'link'
diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php
index b51b3b200..9a10ee5ca 100644
--- a/app/templates/Standard/index.php
+++ b/app/templates/Standard/index.php
@@ -143,9 +143,9 @@ function _column_key($modelsName, $c, $tz=null) {
-
- = $this->element('search'); ?>
-
+
+ = $this->element('filter'); ?>
+
diff --git a/app/templates/Types/columns.inc b/app/templates/Types/columns.inc
index b0aeca57e..34a9deb23 100644
--- a/app/templates/Types/columns.inc
+++ b/app/templates/Types/columns.inc
@@ -25,6 +25,9 @@
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/
+// Turn on the search/filter box for this index view
+$enableFiltering = true;
+
$indexColumns = [
'display_name' => [
'type' => 'link',
diff --git a/app/templates/element/search.php b/app/templates/element/filter.php
similarity index 81%
rename from app/templates/element/search.php
rename to app/templates/element/filter.php
index fad23108c..33c25a691 100644
--- a/app/templates/element/search.php
+++ b/app/templates/element/filter.php
@@ -1,6 +1,6 @@
Form->create(null, [
- 'id' => 'top-search-form',
+ 'id' => 'top-filters-form',
'type' => 'get'
]);
@@ -55,15 +55,15 @@
$hasActiveFilters = false;
?>
-
-