Skip to content

Commit

Permalink
Merge pull request #24 from loannis/CFM-135_Model_Index_Filtering_Inf…
Browse files Browse the repository at this point in the history
…rastructure

CFM-135_Model_Index_Filtering_Infrastructure
  • Loading branch information
arlen authored May 19, 2022
2 parents da7620f + a82976b commit c3a304f
Show file tree
Hide file tree
Showing 34 changed files with 520 additions and 222 deletions.
16 changes: 16 additions & 0 deletions app/src/Controller/StandardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,17 @@ public function index() {
$searchableAttributes = $table->getSearchableAttributes();

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) {
if(!empty($this->request->getQuery($attribute))) {
$query = $table->whereFilter($query, $attribute, $this->request->getQuery($attribute));
} elseif (!empty($this->request->getQuery($attribute . "_starts_at"))
|| !empty($this->request->getQuery($attribute . "_ends_at"))) {
$search_date = [];
// We allow empty for dates since we might refer to infinity (from whenever or to always)
$search_date[] = $this->request->getQuery($attribute . "_starts_at") ?? "";
$search_date[] = $this->request->getQuery($attribute . "_ends_at") ?? "";
$query = $table->whereFilter($query, $attribute, $search_date);
}
}

Expand Down Expand Up @@ -572,6 +580,14 @@ protected function populateAutoViewVars(object $obj=null) {

$this->set($vvar, $query->toArray());
break;
case 'parent':
$modelsName = $this->name;
// $table = the actual table object
$table = $this->$modelsName;
// XXX We assume that all models that load the Tree behavior will
// implement a potentialParents method
$this->set($vvar, $table->potentialParents($this->getCOID()));
break;
default:
// XXX I18n? and in match?
throw new \LogicException('Unknonwn Auto View Var Type {0}', [$avv['type']]);
Expand Down
160 changes: 67 additions & 93 deletions app/src/Lib/Traits/SearchFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,11 @@
namespace App\Lib\Traits;

use Cake\Utility\Inflector;
use Cake\I18n\FrozenTime;

trait SearchFilterTrait {
// Array (and configuration) of permitted search filters
private $searchFilters = array();

/**
* Determine the UI label for the specified attribute.
*
* @since COmanage Registry v5.0.0
* @param string $attribute Attribute
* @return string Label
* @todo Merge this with _column_key from index.ctp
*/

public function getLabel(string $attribute): string {
if(isset($this->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 a pretty version of the $attribute name
return Inflector::humanize($attribute);
}

/**
* Obtain the set of permitted search attributes.
*
Expand All @@ -69,83 +43,83 @@ public function getLabel(string $attribute): string {
*/

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)
foreach ($this->filterMetadataFields() as $column => $type) {
// If the column is an array then we are accessing the Metadata fields. Skip
if(is_array($type)) {
continue;
}
$this->searchFilters[$column] = [
'type' => $type,
'label' => (__d('field', $column) ?? Inflector::humanize($column))
];

// For the date fields we search ranges
if($type === 'timestamp') {
$this->searchFilters[$column]['alias'][] = $column . '_starts_at';
$this->searchFilters[$column]['alias'][] = $column . '_ends_at';
}
}

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');

return $this->searchFilters ?? [];
}

/**
* 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
* @param \Cake\ORM\Query $query Cake ORM Query object
* @param string $attribute Attribute to filter on (database name)
* @param string|array $q Value to filter on
*
* @return \Cake\ORM\Query Cake ORM Query object
* @since COmanage Registry v5.0.0
*/

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));
}
public function whereFilter(\Cake\ORM\Query $query, string $attribute, string|array $q): object {
// not a permitted attribute
if(empty($this->searchFilters[$attribute])) {
return $query;
}

$search = $q;
$sub = false;
// Primitive types
$search_types = ['integer', 'boolean'];
if( $this->searchFilters[$attribute]['type'] == "string") {
$search = "%" . $search . "%";
$sub = true;
} elseif(in_array($this->searchFilters[$attribute]['type'], $search_types, true)) {
return $query->where([$attribute => $search]);
} 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] . "'");
});
// 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);
});
// 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);
});
} else {
// We return everything
return $query;
}

}
// else not a permitted attribute

return $query;

// 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 ($sub) ? $exp->like($lower, strtolower($search))
: $exp->eq($lower, strtolower($search));
});
}
}
65 changes: 65 additions & 0 deletions app/src/Lib/Traits/TableMetaTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

namespace App\Lib\Traits;

use Cake\Utility\Inflector;

trait TableMetaTrait {
// Does this Table represent Registry objects or configuration?
private $confTable = false;
Expand All @@ -54,4 +56,67 @@ public function getIsConfigurationTable() {
public function setIsConfigurationTable(bool $confTable) {
$this->confTable = $confTable;
}


/**
* Filter metadata fields.
*
* @since COmanage Registry v5.0.0
* @return array An array of columns distinguished in metadata and non-metadata
*/

protected function filterMetadataFields() {
// Get the list of columns
$coltype = $this->getSchema()->typeMap();
$entity = $this->getEntityClass();
$entity_namespace = explode('\\', $entity);
$modelName = end($entity_namespace);

// Get the list of belongs_to associations and construct an exclude array
$assc_keys = [];
foreach ($this->associations() as $assc) {
if($assc->type() === "manyToOne") {
$assc_keys[] = Inflector::underscore(Inflector::classify($assc->getClassName())) . "_id";
}
}
// Map the model (eg: Person) to the changelog key (person_id)
$mfk = Inflector::underscore($modelName) . "_id";


$meta_fields = [
...$assc_keys,
$mfk,
'actor_identifier',
// 'provisioning_target_id',
'created', // todo: I might need to revisit this. We might want to filter according to date in some occassions. Like petitions
'deleted',
'id',
'modified',
'revision',
'lft', // XXX For now i skip lft.rght column for tree structures
'rght',
// 'parent_id', // todo: We need to filter using the parent_id. This should be an enumerator and should apply for all the models that use TreeBehavior
'api_key'
// 'source_ad_hoc_attribute_id',
// 'source_address_id',
// 'source_email_address_id',
// 'source_identifier_id',
// 'source_name_id',
// 'source_external_identity_id',
// 'source_telephone_number_id',
];

$newa = array();
foreach($coltype as $clmn => $type) {
if(in_array($clmn, $meta_fields,true)) {
// Move the value to metadata
$newa['meta'][$clmn] = $type;
} else {
// Just copy the value
$newa[$clmn] = $type;
}
}

return $newa ?? [];
}
}
1 change: 1 addition & 0 deletions app/src/Model/Table/AdHocAttributesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class AdHocAttributesTable extends Table {
use \App\Lib\Traits\PrimaryLinkTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

/**
* Perform Cake Model initialization.
Expand Down
1 change: 1 addition & 0 deletions app/src/Model/Table/AddressesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class AddressesTable extends Table {
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\TypeTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

// Default "out of the box" types for this model. Entries here should be
// given a default localization in app/resources/locales/*/defaultType.po
Expand Down
3 changes: 2 additions & 1 deletion app/src/Model/Table/ApiUsersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class ApiUsersTable extends Table {
use \App\Lib\Traits\PrimaryLinkTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;

use \App\Lib\Traits\SearchFilterTrait;

/**
* Perform Cake Model initialization.
*
Expand Down
12 changes: 7 additions & 5 deletions app/src/Model/Table/CousTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ 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)
Expand All @@ -91,6 +86,12 @@ public function initialize(array $config): void {
'index' => ['platformAdmin', 'coAdmin']
]
]);

$this->setAutoViewVars([
'parent_ids' => [
'type' => 'parent'
]
]);
}

/**
Expand Down Expand Up @@ -122,6 +123,7 @@ public function buildTableRules(RulesChecker $rules): RulesChecker {
* @param int $id COU ID to determine potential parents of, or null for any (or a new) COU
* @param bool $hierarchy Render the hierarchy in the name
* @return Array Array of COU IDs and COU Names
* @todo Make a TreeTrait and move the function there
*/

public function potentialParents(int $coId, int $id=null, bool $hierarchy=false) {
Expand Down
1 change: 1 addition & 0 deletions app/src/Model/Table/EmailAddressesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class EmailAddressesTable extends Table {
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\TypeTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

// Default "out of the box" types for this model. Entries here should be
// given a default localization in app/resources/locales/*/defaultType.po
Expand Down
1 change: 1 addition & 0 deletions app/src/Model/Table/ExternalIdentitiesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ExternalIdentitiesTable extends Table {
use \App\Lib\Traits\QueryModificationTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

/**
* Perform Cake Model initialization.
Expand Down
1 change: 1 addition & 0 deletions app/src/Model/Table/ExternalIdentityRolesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ExternalIdentityRolesTable extends Table {
use \App\Lib\Traits\QueryModificationTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

/**
* Perform Cake Model initialization.
Expand Down
1 change: 1 addition & 0 deletions app/src/Model/Table/HistoryRecordsTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class HistoryRecordsTable extends Table {
use \App\Lib\Traits\QueryModificationTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

/**
* Perform Cake Model initialization.
Expand Down
1 change: 1 addition & 0 deletions app/src/Model/Table/IdentifiersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class IdentifiersTable extends Table {
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\TypeTrait;
use \App\Lib\Traits\ValidationTrait;
use \App\Lib\Traits\SearchFilterTrait;

// Default "out of the box" types for this model. Entries here should be
// given a default localization in app/resources/locales/*/defaultType.po
Expand Down
Loading

0 comments on commit c3a304f

Please sign in to comment.