diff --git a/app/src/Controller/PeopleController.php b/app/src/Controller/PeopleController.php index 4577075c6..5d529a590 100644 --- a/app/src/Controller/PeopleController.php +++ b/app/src/Controller/PeopleController.php @@ -32,6 +32,7 @@ // XXX not doing anything with Log yet use Cake\Log\Log; use Cake\ORM\TableRegistry; +use http\QueryString; class PeopleController extends StandardController { public $paginate = [ @@ -49,7 +50,8 @@ class PeopleController extends StandardController { 'sortableFields' => [ 'PrimaryName.given', 'PrimaryName.family' - ] + ], + 'finder' => 'indexed' ]; /** diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index f6bc44c90..2fe09fc47 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -534,9 +534,13 @@ public function index() { if(!empty($link->attr)) { // If a link attribute is defined but no value is provided, then query // where the link attribute is NULL - $query = $table->find()->where([$table->getAlias().'.'.$link->attr => $link->value]); + // "all" is the default finder. But since we are utilizing the paginator here, we will check the configuration + // for any custom finder. + $query = $table->find( + $this->paginate['finder'] ?? "all" + )->where([$table->getAlias().'.'.$link->attr => $link->value]); } else { - $query = $table->find(); + $query = $table->find($this->paginate['finder'] ?? "all"); } // QueryModificationTrait @@ -551,7 +555,7 @@ public function index() { if(!empty($searchableAttributes)) { // Here we iterate over the attributes, and we add a new where clause for each one - foreach(array_keys($searchableAttributes) as $attribute) { + foreach($searchableAttributes as $attribute => $options) { if(!empty($this->request->getQuery($attribute))) { $query = $table->whereFilter($query, $attribute, $this->request->getQuery($attribute)); } elseif (!empty($this->request->getQuery($attribute . "_starts_at")) diff --git a/app/src/Lib/Traits/SearchFilterTrait.php b/app/src/Lib/Traits/SearchFilterTrait.php index 2dff433ce..70ffd5e18 100644 --- a/app/src/Lib/Traits/SearchFilterTrait.php +++ b/app/src/Lib/Traits/SearchFilterTrait.php @@ -67,7 +67,9 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null): $this->searchFilters[$field] = [ 'type' => 'string', // XXX for now - this needs to be looked up. 'label' => \App\Lib\Util\StringUtilities::columnKey($fieldName, $field, $vv_tz, true), - 'active' => isset($f['active']) ? $f['active'] : true + 'active' => isset($f['active']) ? $f['active'] : true, + 'model' => $f['model'], + 'order' => $f['order'] ]; } } @@ -92,7 +94,8 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null): $this->searchFilters[$column] = [ 'type' => $type, 'label' => \App\Lib\Util\StringUtilities::columnKey($modelname, $column, $vv_tz, true), - 'active' => $fieldIsActive + 'active' => $fieldIsActive, + 'order' => 99 // this is the default ]; // For the date fields we search ranges @@ -132,6 +135,27 @@ public function whereFilter(\Cake\ORM\Query $query, string $attribute, string|ar return $query; } + if(isset($this->searchFilters[$attribute]['model'])) { + $changelog_fk = strtolower(Inflector::underscore($this->searchFilters[$attribute]['model'])) . '_id'; + $fk = strtolower(Inflector::underscore(Inflector::singularize($this->_alias))) . '_id'; + $mtable_name = Inflector::tableize(Inflector::pluralize($this->searchFilters[$attribute]['model'])); + $mtable_alias = Inflector::pluralize($this->searchFilters[$attribute]['model']); + $query->join([$mtable_alias => [ + 'table' => $mtable_name, + 'conditions' => [ + $mtable_alias . '.' . $fk . '=' . $this->_alias . '.id', + $mtable_alias . '.' . 'deleted IS NOT TRUE', + $mtable_alias . '.' . $changelog_fk . ' IS NULL' + ], + 'type' => 'INNER' + ]]); + } + + // Prepend the Model name to the attribute + $attributeWithModelPrefix = isset($this->searchFilters[$attribute]['model']) ? + Inflector::pluralize($this->searchFilters[$attribute]['model']) . '.' . $attribute : + $this->_alias . '.' . $attribute; + $search = $q; $sub = false; // Primitive types @@ -139,29 +163,32 @@ public function whereFilter(\Cake\ORM\Query $query, string $attribute, string|ar if( $this->searchFilters[$attribute]['type'] == "string") { $search = "%" . $search . "%"; $sub = true; + // Search type } elseif(in_array($this->searchFilters[$attribute]['type'], $search_types, true)) { - return $query->where([$attribute => $search]); + return $query->where([$attributeWithModelPrefix => $search]); + // Date } elseif($this->searchFilters[$attribute]['type'] == "date") { // Parse the date string with FrozenTime to improve error handling - return $query->where([$attribute => FrozenTime::parseDate($search, 'y-M-d')]); + return $query->where([$attributeWithModelPrefix => FrozenTime::parseDate($search, 'y-M-d')]); + // Timestamp } elseif( $this->searchFilters[$attribute]['type'] == "timestamp") { // Date between dates if(!empty($search[0]) && !empty($search[1])) { - return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) { - return $exp->between($attribute, "'" . $search[0] . "'", "'" . $search[1] . "'"); + return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attributeWithModelPrefix, $search) { + return $exp->between($attributeWithModelPrefix, "'" . $search[0] . "'", "'" . $search[1] . "'"); }); // The starts at is non-empty. So the data should be greater than the starts_at date } elseif(!empty($search[0]) && empty($search[1])) { - return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) { - return $exp->gte("'" . FrozenTime::parse($search[0]) . "'", $attribute); + return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attributeWithModelPrefix, $search) { + return $exp->gte("'" . FrozenTime::parse($search[0]) . "'", $attributeWithModelPrefix); }); // The ends at is non-empty. So the data should be less than the ends at date } elseif(!empty($search[1]) && empty($search[0])) { - return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) { - return $exp->lte("'" . FrozenTime::parse($search[1]) . "'", $attribute); + return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attributeWithModelPrefix, $search) { + return $exp->lte("'" . FrozenTime::parse($search[1]) . "'", $attributeWithModelPrefix); }); } else { // We return everything @@ -171,8 +198,8 @@ public function whereFilter(\Cake\ORM\Query $query, string $attribute, string|ar } // String values - return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search, $sub) { - $lower = $query->func()->lower([$attribute => 'identifier']); + return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attributeWithModelPrefix, $search, $sub) { + $lower = $query->func()->lower([$attributeWithModelPrefix => 'identifier']); return ($sub) ? $exp->like($lower, strtolower($search)) : $exp->eq($lower, strtolower($search)); }); diff --git a/app/src/Lib/Util/StringUtilities.php b/app/src/Lib/Util/StringUtilities.php index 0b3e47aca..945456eb9 100644 --- a/app/src/Lib/Util/StringUtilities.php +++ b/app/src/Lib/Util/StringUtilities.php @@ -57,10 +57,16 @@ public static function classNameToForeignKey(string $className): string { public static function columnKey($modelsName, $c, $tz=null, $useCustomClMdlLabel=false): string { if(strpos($c, "_id", strlen($c)-3)) { + $postfix = ""; + if($c == "parent_id") { + // This means we are working with a model that implements a Tree behavior + $postfix = " ({$modelsName})"; + } + // Key is of the form field_id, use .ct label instead $k = self::foreignKeyToClassName($c); - return __d('controller', $k, [1]); + return __d('controller', $k, [1]) . $postfix; } // Look for a model specific key first diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 7347a53b8..513c489ec 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -93,6 +93,7 @@ public function initialize(array $config): void { ->setDependent(true) ->setCascadeCallbacks(true); $this->hasMany('GroupOwners') + ->setClassName('GroupMembers') ->setDependent(true) ->setCascadeCallbacks(true); $this->hasMany('HistoryRecords') @@ -158,27 +159,32 @@ public function initialize(array $config): void { $this->setFilterConfig([ 'family' => [ 'type' => 'relatedModel', - 'model' => 'names', - 'active' => true + 'model' => 'Name', + 'active' => true, + 'order' => 2 ], 'given' => [ 'type' => 'relatedModel', - 'model' => 'names', - 'active' => true + 'model' => 'Name', + 'active' => true, + 'order' => 1 ], 'mail' => [ 'type' => 'relatedModel', - 'model' => 'email_addresses', - 'active' => true + 'model' => 'EmailAddress', + 'active' => true, + 'order' => 3 ], 'identifier' => [ 'type' => 'relatedModel', - 'model' => 'identifiers', - 'active' => true + 'model' => 'Identifier', + 'active' => true, + 'order' => 4 ], 'timezone' => [ 'type' => 'field', - 'active' => false + 'active' => false, + 'order' => 99 ] ]); @@ -249,6 +255,29 @@ public function beforeDelete(\Cake\Event\Event $event, $entity, \ArrayObject $op return true; } + /** + * Customized finder for the Index Population View + * + * @param Query $query Cake ORM Query + * @param array $options Cake ORM Query options + * + * @return CakeORMQuery Cake ORM Query + * @since COmanage Registry v5.0.0 + */ + public function findIndexed(Query $query, array $options): Query { + return $query->select([ + 'People.id', + 'PrimaryName.given', + 'PrimaryName.family', + 'People.status', + 'People.created', + 'People.modified', + 'People.timezone', + 'People.date_of_birth' + ]) + ->distinct(); + } + /** * Table specific logic to generate a display field. * @@ -413,9 +442,9 @@ public function marshalProvisioningData(int $id): array { $identifiers = []; - foreach($ret['data']->identifiers as $id) { - if($id->status == SuspendableStatusEnum::Active) { - $identifiers[] = $id; + foreach($ret['data']->identifiers as $ident) { + if($ident->status == SuspendableStatusEnum::Active) { + $identifiers[] = $ident; } } @@ -438,9 +467,9 @@ public function marshalProvisioningData(int $id): array { $identifiers = []; - foreach($ret['data']->identifiers as $id) { - if($id->status == SuspendableStatusEnum::Active) { - $identifiers[] = $id; + foreach($ret['data']->identifiers as $ident) { + if($ident->status == SuspendableStatusEnum::Active) { + $identifiers[] = $ident; } } diff --git a/app/templates/element/filter.php b/app/templates/element/filter.php index a748cc452..359c7f749 100644 --- a/app/templates/element/filter.php +++ b/app/templates/element/filter.php @@ -121,7 +121,7 @@