From 521df6ee36f2f5f3ff6e5b27e45b6ddd5754e3b6 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 20 Aug 2024 12:56:39 +0300 Subject: [PATCH 01/16] Improve People Picker URL structure --- app/config/routes.php | 3 + app/src/Controller/ApiV2Controller.php | 94 +++++++++++++------ app/src/Controller/AppController.php | 3 +- app/src/Lib/Traits/IndexQueryTrait.php | 2 +- app/src/Model/Table/PeopleTable.php | 4 +- app/templates/GroupMembers/columns.inc | 1 - app/templates/element/peopleAutocomplete.php | 3 +- .../autocomplete/cm-autocomplete-people.js | 5 +- 8 files changed, 78 insertions(+), 37 deletions(-) diff --git a/app/config/routes.php b/app/config/routes.php index cde13ebb2..89664c075 100644 --- a/app/config/routes.php +++ b/app/config/routes.php @@ -114,6 +114,9 @@ function (RouteBuilder $builder) { ['controller' => 'ApiV2', 'action' => 'generateApiKey', 'model' => 'api_users']) ->setPass(['id']) ->setPatterns(['id' => '[0-9]+']); + $builder->get( + '/people/pick', + ['controller' => 'ApiV2', 'action' => 'pick', 'model' => 'people']); // These establish the usual CRUD options on all models: $builder->delete( '/{model}/{id}', ['controller' => 'ApiV2', 'action' => 'delete']) diff --git a/app/src/Controller/ApiV2Controller.php b/app/src/Controller/ApiV2Controller.php index 5386c46e4..add5bbb5a 100644 --- a/app/src/Controller/ApiV2Controller.php +++ b/app/src/Controller/ApiV2Controller.php @@ -136,6 +136,25 @@ public function beforeRender(\Cake\Event\EventInterface $event) { return parent::beforeRender($event); } + + /** + * Calculate the CO ID associated with the request. + * + * @since COmanage Registry v5.0.0 + * @return int CO ID, or null if no CO contextwas found + */ + + public function calculateRequestedCOID(): ?int { + if($this->request->getQuery('group_id') !== null) { + $groupId = $this->request->getQuery('group_id'); + $Group = TableRegistry::getTableLocator()->get('Groups'); + + $groupRecord = $Group->get($groupId); + return $groupRecord->co_id; + } + + return null; + } /** * Handle a delete action for a Standard object. @@ -181,7 +200,42 @@ public function delete($id) { throw new BadRequestException($this->exceptionToError($e)); } } - + + protected function dispatchIndex(string $mode = 'default') { + // There are use cases where we will pass co_id and another model_id as a query parameter. The co_id might be + // required for the primary link calculations while the foreign key for filtering. Since we are using the + // most constrained identifier to calculate the co_id, we then check if the two parameters match. If not, + // the request should fail, so as to prevent any security holes. + if($this->request->getQuery('co_id') !== null + && $this->getCOID() !== null + && (int)$this->getCOID() !== (int)$this->request->getQuery('co_id')) { + $this->llog('error', 'CO Id calculated from Group ID does not match CO Id query parameter'); + // Mask this with a generic UnauthorizedException + throw new UnauthorizedException(__d('error', 'perm')); + } + + // $modelsName = Models + $modelsName = $this->name; + // $table = the actual table object + $table = $this->$modelsName; + + $reqParameters = [...$this->request->getQuery()]; + $pickerMode = ($mode === 'picker'); + + // Construct the Query + $query = $this->getIndexQuery($pickerMode, $reqParameters); + + if(method_exists($table, 'findIndexed')) { + $query = $table->findIndexed($query); + } + // This magically makes REST calls paginated... can use eg direction=, + // sort=, limit=, page= + $this->set($this->tableName, $this->paginate($query)); + + // Let the view render + $this->render('/Standard/api/v2/json/index'); + } + /** * Handle an edit action for a Standard object. * @@ -300,33 +354,7 @@ public function generateApiKey(string $id) { */ public function index() { - // $modelsName = Models - $modelsName = $this->name; - // $table = the actual table object - $table = $this->$modelsName; - - $reqParameters = []; - $pickerMode = false; - if($this->request->is('ajax')) { - $reqParameters = [...$this->request->getQuery()]; - if($this->request->getQuery('picker') !== null) { - $pickerMode = filter_var($this->request->getQuery('picker'), FILTER_VALIDATE_BOOLEAN); - } - } - - - // Construct the Query - $query = $this->getIndexQuery($pickerMode, $reqParameters); - - if(method_exists($table, 'findIndexed')) { - $query = $table->findIndexed($query); - } - // This magically makes REST calls paginated... can use eg direction=, - // sort=, limit=, page= - $this->set($this->tableName, $this->paginate($query)); - - // Let the view render - $this->render('/Standard/api/v2/json/index'); + $this->dispatchIndex(); } /** @@ -354,4 +382,14 @@ public function view($id = null) { // Let the view render $this->render('/Standard/api/v2/json/index'); } + + /** + * Pick a set of Standard Objects. + * + * @since COmanage Registry v5.0.0 + */ + + public function pick() { + $this->dispatchIndex(mode: 'picker'); + } } \ No newline at end of file diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index 01fd2ab98..e190ff1ab 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -346,7 +346,8 @@ public function getPrimaryLink(bool $lookup=false) { } } - if(empty($this->cur_pl->value) && !$this->$modelsName->allowEmptyPrimaryLink()) { + if(empty($this->cur_pl->value) + && !$this->$modelsName->allowEmptyPrimaryLink($this->request->getParam('action'))) { throw new \RuntimeException(__d('error', 'primary_link')); } } diff --git a/app/src/Lib/Traits/IndexQueryTrait.php b/app/src/Lib/Traits/IndexQueryTrait.php index 8a6eb8f64..d5236ffe9 100644 --- a/app/src/Lib/Traits/IndexQueryTrait.php +++ b/app/src/Lib/Traits/IndexQueryTrait.php @@ -201,7 +201,7 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [ // Specific expressions per view $query = match($requestParams['for'] ?? '') { // GroupMembers Add view: We need to filter the active members - 'GroupMembers' => $query->leftJoinWith('GroupMembers', fn($q) => $q->where(['GroupMembers.group_id' => (int)($requestParams['groupid'] ?? -1)])) + 'GroupMembers' => $query->leftJoinWith('GroupMembers', fn($q) => $q->where(['GroupMembers.group_id' => (int)($requestParams['group_id'] ?? -1)])) ->where($this->getTableLocator()->get('GroupMembers')->checkValidity($query)) ->where(fn(QueryExpression $exp, Query $query) => $exp->isNull('GroupMembers.' . StringUtilities::classNameToForeignKey($table->getAlias()))), // Just return the query diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 21c449f69..a81456961 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -148,6 +148,7 @@ public function initialize(array $config): void { $this->setRequiresCO(true); $this->setRedirectGoal('self'); $this->setAllowLookupPrimaryLink(['provision']); + $this->setAllowUnkeyedPrimaryLink(['pick']); // XXX does some of this stuff really belong in the controller? $this->setEditContains([ @@ -222,7 +223,8 @@ public function initialize(array $config): void { // Actions that operate over a table (ie: do not require an $id) 'table' => [ 'add' => ['platformAdmin', 'coAdmin'], - 'index' => ['platformAdmin', 'coAdmin'] + 'index' => ['platformAdmin', 'coAdmin'], + 'pick' => ['platformAdmin', 'coAdmin'], ], // Related models whose permissions we'll need, typically for table views 'related' => [ diff --git a/app/templates/GroupMembers/columns.inc b/app/templates/GroupMembers/columns.inc index 8cef9bbc8..a9674af44 100644 --- a/app/templates/GroupMembers/columns.inc +++ b/app/templates/GroupMembers/columns.inc @@ -101,7 +101,6 @@ $peoplePicker = [ 'label' => __d('operation','add.member'), 'viewConfigParameters' => [ 'for' => 'GroupMembers', - 'action' => $vv_action, 'groupId' => $this->getRequest()?->getQuery('group_id') ], 'actionUrl' => [ diff --git a/app/templates/element/peopleAutocomplete.php b/app/templates/element/peopleAutocomplete.php index 73e690f97..97462f730 100644 --- a/app/templates/element/peopleAutocomplete.php +++ b/app/templates/element/peopleAutocomplete.php @@ -67,7 +67,8 @@ api: { viewConfigParameters: , webroot: 'request->getAttribute('webroot') ?>', - searchPeople: `request->getAttribute('webroot') ?>api/ajax/v2/people?co_id=id ?>&picker=on&for=` + // co_id query parameter is required since it is the People's primary link + searchPeople: `request->getAttribute('webroot') ?>api/ajax/v2/people/pick?co_id=id ?>&for=` } } diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index e5bb29cab..dfdfc50ad 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -69,10 +69,7 @@ export default { queryParams.append('given', query) queryParams.append('family', query) if(this.api.viewConfigParameters.groupId != undefined) { - queryParams.append('groupid', this.api.viewConfigParameters.groupId) - } - if(this.api.viewConfigParameters.action != undefined) { - queryParams.append('action', this.api.viewConfigParameters.action) + queryParams.append('group_id', this.api.viewConfigParameters.groupId) } // Pagination // XXX Move this to configuration From d045b6b2cc8fcf583933b2705fa5f5f3b7ee741b Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 20 Aug 2024 20:18:30 +0300 Subject: [PATCH 02/16] Remove extended query parameters from picker URL --- app/src/Lib/Traits/IndexQueryTrait.php | 29 +++++++++++++++++-- app/src/Lib/Traits/QueryModificationTrait.php | 29 +++++++++++++++++-- app/src/Lib/Traits/SearchFilterTrait.php | 2 +- app/src/Model/Table/PeopleTable.php | 5 ++++ .../autocomplete/cm-autocomplete-people.js | 1 - 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/app/src/Lib/Traits/IndexQueryTrait.php b/app/src/Lib/Traits/IndexQueryTrait.php index d5236ffe9..ad784d451 100644 --- a/app/src/Lib/Traits/IndexQueryTrait.php +++ b/app/src/Lib/Traits/IndexQueryTrait.php @@ -57,13 +57,38 @@ public function constructGetIndexContains(Query $query): object { $containClause = $table->getIndexContains(); } - if($this->request->is('restful')|| $this->request->is('ajax')) { + if($this->request->is('restful') || $this->request->is('ajax')) { $containClause = $this->containClauseFromQueryParams(); } return empty($containClause) ? $query : $query->contain($containClause); } + /** + * Construct the Picker Contain array + * + * @param Query $query + * + * @return object Cake ORM Query object + * @since COmanage Registry v5.0.0 + */ + public function constructGetPickerContains(Query $query): object { + // $this->name = Models + $modelsName = $this->name; + // $table = the actual table object + $table = $this->$modelsName; + // Initialize the containClause + $containClause = []; + + // Get whatever the table configuration has + if(method_exists($table, 'getPickerContains') + && $table->getPickerContains()) { + $containClause = $table->getPickerContains(); + } + + return empty($containClause) ? $query : $query->contain($containClause); + } + /** * Construct the Contain Clause from the query parameters of an AJAX or REST call @@ -151,7 +176,7 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [ } // Get Associated Model Data - $query = $this->constructGetIndexContains($query); + $query = $pickerMode ? $this->constructGetPickerContains($query) : $this->constructGetIndexContains($query); // Attributes to search for if(method_exists($table, 'getSearchableAttributes')) { diff --git a/app/src/Lib/Traits/QueryModificationTrait.php b/app/src/Lib/Traits/QueryModificationTrait.php index 1aa061bcd..6670ef894 100644 --- a/app/src/Lib/Traits/QueryModificationTrait.php +++ b/app/src/Lib/Traits/QueryModificationTrait.php @@ -52,6 +52,9 @@ trait QueryModificationTrait { // Array of associated models to pull during a view private $viewContains = false; + // Array of associated models to pull during a pick action + private $pickerContains = false; + /** * Construct the checkValidity for the fields valid_from and valid_through @@ -121,7 +124,18 @@ public function getIndexContains() { public function getPatchAssociated() { return $this->patchAssociated; } - + + /** + * Obtain the set of associated models to pull during a pick. + * + * @since COmanage Registry v5.0.0 + * @return array Array of associated models + */ + + public function getPickerContains() { + return $this->pickerContains; + } + /** * Obtain the set of associated models to pull during a view. * @@ -187,7 +201,18 @@ public function setIndexFilter(array|\Closure $filter) { public function setPatchAssociated(array $a) { $this->patchAssociated = $a; } - + + /** + * Set the associated models to pull during a pick. + * + * @since COmanage Registry v5.0.0 + * @param array $c Array of associated models + */ + + public function setPickerContains(array $c) { + $this->pickerContains = $c; + } + /** * Set the associated models to pull during a view. * diff --git a/app/src/Lib/Traits/SearchFilterTrait.php b/app/src/Lib/Traits/SearchFilterTrait.php index 6b266a0b8..eeaec4665 100644 --- a/app/src/Lib/Traits/SearchFilterTrait.php +++ b/app/src/Lib/Traits/SearchFilterTrait.php @@ -124,7 +124,7 @@ public function addJoins(Query $query, string $attribute, ServerRequest $request return $query->join($joinAssociations); - // XXX We can not use the inenerJoinWith since it applies EagerLoading and includes all the fields which + // XXX We can not use the innerJoinWith since it applies EagerLoading and includes all the fields which // causes problems // return $query->innerJoinWith($this->searchFilters[$attribute]['model']); } diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index a81456961..95a2d4e16 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -165,6 +165,11 @@ public function initialize(array $config): void { ]); $this->setIndexContains(['PrimaryName']); $this->setViewContains(['PrimaryName']); + $this->setPickerContains([ + 'EmailAddresses', + 'Identifiers', + 'PrimaryName', + ]); $this->setAutoViewVars([ 'statuses' => [ diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index dfdfc50ad..7495aa770 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -63,7 +63,6 @@ export default { const url = new URL(urlString); let queryParams = url.searchParams; // Query parameters - queryParams.append('extended', 'PrimaryName,Identifiers,EmailAddresses') queryParams.append('identifier', query) queryParams.append('mail', query) queryParams.append('given', query) From ced422da8b2fb9199b8f450d0a1992d5b14a34de Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 20 Aug 2024 21:36:37 +0300 Subject: [PATCH 03/16] Remove viewConfigParameter:for. Left join both group members and non group members. --- app/src/Lib/Traits/IndexQueryTrait.php | 10 ++++++---- app/templates/GroupMembers/columns.inc | 1 - app/templates/element/peopleAutocomplete.php | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/Lib/Traits/IndexQueryTrait.php b/app/src/Lib/Traits/IndexQueryTrait.php index ad784d451..7771bbeb0 100644 --- a/app/src/Lib/Traits/IndexQueryTrait.php +++ b/app/src/Lib/Traits/IndexQueryTrait.php @@ -224,11 +224,13 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [ $query = $query->where(fn(QueryExpression $exp, Query $query) => $exp->in($table->getAlias().'.status', [StatusEnum::Active, StatusEnum::GracePeriod])); // Specific expressions per view - $query = match($requestParams['for'] ?? '') { + $query = match(true) { // GroupMembers Add view: We need to filter the active members - 'GroupMembers' => $query->leftJoinWith('GroupMembers', fn($q) => $q->where(['GroupMembers.group_id' => (int)($requestParams['group_id'] ?? -1)])) - ->where($this->getTableLocator()->get('GroupMembers')->checkValidity($query)) - ->where(fn(QueryExpression $exp, Query $query) => $exp->isNull('GroupMembers.' . StringUtilities::classNameToForeignKey($table->getAlias()))), + (isset($requestParams['group_id']) && $modelsName === 'People') => $query + ->leftJoinWith('GroupMembers', fn($q) => $q->where(['GroupMembers.group_id' => (int)($requestParams['group_id'] ?? -1)])), +// XXX We want to get both members and not members. The frontend will handle the rest +// ->where($this->getTableLocator()->get('GroupMembers')->checkValidity($query)) +// ->where(fn(QueryExpression $exp, Query $query) => $exp->isNull('GroupMembers.' . StringUtilities::classNameToForeignKey($table->getAlias()))), // Just return the query default => $query }; diff --git a/app/templates/GroupMembers/columns.inc b/app/templates/GroupMembers/columns.inc index a9674af44..abd2f1c7f 100644 --- a/app/templates/GroupMembers/columns.inc +++ b/app/templates/GroupMembers/columns.inc @@ -100,7 +100,6 @@ $subnav = [ $peoplePicker = [ 'label' => __d('operation','add.member'), 'viewConfigParameters' => [ - 'for' => 'GroupMembers', 'groupId' => $this->getRequest()?->getQuery('group_id') ], 'actionUrl' => [ diff --git a/app/templates/element/peopleAutocomplete.php b/app/templates/element/peopleAutocomplete.php index 97462f730..7787bbf36 100644 --- a/app/templates/element/peopleAutocomplete.php +++ b/app/templates/element/peopleAutocomplete.php @@ -68,7 +68,7 @@ viewConfigParameters: , webroot: 'request->getAttribute('webroot') ?>', // co_id query parameter is required since it is the People's primary link - searchPeople: `request->getAttribute('webroot') ?>api/ajax/v2/people/pick?co_id=id ?>&for=` + searchPeople: `request->getAttribute('webroot') ?>api/ajax/v2/people/pick?co_id=id ?>` } } From a697e01593e562d6e876fa74e46e9b62d863c6ed Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 23 Aug 2024 08:52:47 +0300 Subject: [PATCH 04/16] add middle name as a filtering option --- app/src/Model/Table/PeopleTable.php | 8 +++++++- .../components/autocomplete/cm-autocomplete-people.js | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 95a2d4e16..aee5d57e3 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -185,6 +185,12 @@ public function initialize(array $config): void { // XXX expand/revise this as needed to work best with looking up the related models $this->setFilterConfig([ 'family' => [ + 'type' => 'string', + 'model' => 'Names', + 'active' => true, + 'order' => 3 + ], + 'middle' => [ 'type' => 'string', 'model' => 'Names', 'active' => true, @@ -200,7 +206,7 @@ public function initialize(array $config): void { 'type' => 'string', 'model' => 'EmailAddresses', 'active' => true, - 'order' => 3 + 'order' => 5 ], 'identifier' => [ 'type' => 'string', diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index 7495aa770..32f416a5b 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -66,6 +66,7 @@ export default { queryParams.append('identifier', query) queryParams.append('mail', query) queryParams.append('given', query) + queryParams.append('middle', query) queryParams.append('family', query) if(this.api.viewConfigParameters.groupId != undefined) { queryParams.append('group_id', this.api.viewConfigParameters.groupId) From 530cfff23cdeeb8772a2e84dc5226c7d006ee4ee Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 23 Aug 2024 16:01:46 +0300 Subject: [PATCH 05/16] Add middle name po entry --- app/resources/locales/en_US/field.po | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index 2a00ffeca..d2ceb620f 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -125,6 +125,9 @@ msgstr "Item" msgid "family" msgstr "Family Name" +msgid "middle" +msgstr "Middle Name" + msgid "given" msgstr "Given Name" From d5d0e99268abc8847d7f1922b20dc840e12e334a Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 23 Aug 2024 18:06:14 +0300 Subject: [PATCH 06/16] Fix CoSettings picker configuration --- app/config/schema/schema.json | 4 +- app/src/Model/Table/CoSettingsTable.php | 46 +++++++++---------- app/src/Model/Table/GroupsTable.php | 2 +- .../autocomplete/cm-autocomplete-people.js | 28 +++++++++-- .../components/autocomplete/item-with-type.js | 2 +- 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index 190826b33..da2844120 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -127,8 +127,8 @@ "required_fields_name": { "type": "string", "size": 160 }, "search_global_limit": { "type": "integer" }, "search_global_limited_models": { "type": "boolean" }, - "person_picker_email_type": { "type": "integer" }, - "person_picker_identifier_type": { "type": "integer" }, + "person_picker_email_address_type_id": { "type": "integer" }, + "person_picker_identifier_type_id": { "type": "integer" }, "person_picker_display_types": { "type": "boolean" } }, "indexes": { diff --git a/app/src/Model/Table/CoSettingsTable.php b/app/src/Model/Table/CoSettingsTable.php index 40d93824a..33884a2f5 100644 --- a/app/src/Model/Table/CoSettingsTable.php +++ b/app/src/Model/Table/CoSettingsTable.php @@ -222,25 +222,25 @@ public function addDefaults(int $coId): int { // Default values for each setting $defaultSettings = [ - 'co_id' => $coId, - 'default_address_type_id' => null, - 'default_email_address_type_id' => null, - 'default_identifier_type_id' => null, - 'default_name_type_id' => null, - 'default_pronoun_type_id' => null, - 'default_telephone_number_type_id' => null, - 'default_url_type_id' => null, - 'email_smtp_server_id' => null, - 'email_delivery_address_type_id' => null, - 'permitted_fields_name' => PermittedNameFieldsEnum::HGMFS, - 'permitted_fields_telephone_number' => PermittedTelephoneNumberFieldsEnum::CANE, - 'person_picker_email_type' => null, - 'person_picker_identifier_type' => null, - 'person_picker_display_types' => true, - 'required_fields_address' => RequiredAddressFieldsEnum::Street, - 'required_fields_name' => RequiredNameFieldsEnum::Given, - 'search_global_limit' => DEF_GLOBAL_SEARCH_LIMIT, - 'search_limited_models' => false + 'co_id' => $coId, + 'default_address_type_id' => null, + 'default_email_address_type_id' => null, + 'default_identifier_type_id' => null, + 'default_name_type_id' => null, + 'default_pronoun_type_id' => null, + 'default_telephone_number_type_id' => null, + 'default_url_type_id' => null, + 'email_smtp_server_id' => null, + 'email_delivery_address_type_id' => null, + 'permitted_fields_name' => PermittedNameFieldsEnum::HGMFS, + 'permitted_fields_telephone_number' => PermittedTelephoneNumberFieldsEnum::CANE, + 'person_picker_email_address_type_id' => null, + 'person_picker_identifier_type_id' => null, + 'person_picker_display_types' => true, + 'required_fields_address' => RequiredAddressFieldsEnum::Street, + 'required_fields_name' => RequiredNameFieldsEnum::Given, + 'search_global_limit' => DEF_GLOBAL_SEARCH_LIMIT, + 'search_limited_models' => false // XXX to add new settings, set a default here, then add a validation rule below // also update data model documentation // 'disable_expiration' => false, @@ -416,15 +416,15 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->allowEmptyString('person_picker_display_types'); - $validator->add('person_picker_email_type', [ + $validator->add('person_picker_email_address_type_id', [ 'content' => ['rule' => 'isInteger'] ]); - $validator->allowEmptyString('person_picker_email_type'); + $validator->allowEmptyString('person_picker_email_address_type_id'); - $validator->add('person_picker_identifier_type', [ + $validator->add('person_picker_identifier_type_id', [ 'content' => ['rule' => 'isInteger'] ]); - $validator->allowEmptyString('person_picker_identifier_type'); + $validator->allowEmptyString('person_picker_identifier_type_id'); $validator->add('required_fields_address', [ 'content' => ['rule' => ['inList', RequiredAddressFieldsEnum::getConstValues()]] diff --git a/app/src/Model/Table/GroupsTable.php b/app/src/Model/Table/GroupsTable.php index 3350ab7a1..f42470ee1 100644 --- a/app/src/Model/Table/GroupsTable.php +++ b/app/src/Model/Table/GroupsTable.php @@ -176,7 +176,7 @@ public function initialize(array $config): void { // For the Groups Filtering block we want to // pick/GET from the entire CO pool of people 'action' => 'GET', - // The co configuration will fall throught the default configuration + // The co configuration will fall through the default configuration 'for' => 'co' ] ] diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index 32f416a5b..aa0cad4a3 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -141,16 +141,36 @@ export default { } return str }, + filterByEmailAddressType(items) { + if(this.app.cosettings[0].person_picker_email_address_type_id == null + || this.app.cosettings[0].person_picker_email_address_type_id == '') { + return items + } + + return items.filter((item) => { + return item.type_id == this.app.cosettings[0].person_picker_email_address_type_id; + }) + }, + filterByIdentifierType(items) { + if(this.app.cosettings[0].person_picker_identifier_type_id == '' + || this.app.cosettings[0].person_picker_identifier_type_id == null) { + return items + } + + return items.filter((item) => { + return item.type_id == this.app.cosettings[0].person_picker_identifier_type_id; + }) + }, parseResponse(data) { return data?.People?.map((item) => { return { "value": item.id, "label": `${item?.primary_name?.given} ${item?.primary_name?.family} (ID: ${item?.id})`, - "email": item?.email_addresses, - "emailPretty": this.shortenString(this.constructEmailCsv(item?.email_addresses)), + "email": this.filterByEmailAddressType(item?.email_addresses), + "emailPretty": this.shortenString(this.constructEmailCsv(this.filterByEmailAddressType(item?.email_addresses))), "emailLabel": this.txt['email'] + ": ", - "identifier": item?.identifiers, - "identifierPretty": this.shortenString(this.constructIdentifierCsv(item?.identifiers)), + "identifier": this.filterByIdentifierType(item?.identifiers), + "identifierPretty": this.shortenString(this.constructIdentifierCsv(this.filterByIdentifierType(item?.identifiers))), "identifierLabel": this.txt['Identifiers'] + ": " } }) diff --git a/app/webroot/js/comanage/components/autocomplete/item-with-type.js b/app/webroot/js/comanage/components/autocomplete/item-with-type.js index 03e734beb..9766b99c3 100644 --- a/app/webroot/js/comanage/components/autocomplete/item-with-type.js +++ b/app/webroot/js/comanage/components/autocomplete/item-with-type.js @@ -54,7 +54,7 @@ export default { - + {{ this.type }} From 9142c6e1232f12d352f56dde2967aa0f7c253db7 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 23 Aug 2024 19:45:59 +0300 Subject: [PATCH 07/16] Allow picker email/identifier all or 1 type --- app/resources/locales/en_US/operation.po | 3 +++ app/src/View/Helper/FieldHelper.php | 22 ++++++++++++++++++- app/templates/CoSettings/fields.inc | 4 ++++ .../autocomplete/cm-autocomplete-people.js | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index 13e737791..ac1743b10 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -66,6 +66,9 @@ msgstr "Apply Database Schema" msgid "assign" msgstr "Assign" +msgid "all" +msgstr "All" + msgid "any" msgstr "Any" diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index e4095ca17..ca2fa9431 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -335,7 +335,27 @@ public function formField(string $fieldName, // if the field is required. This makes it clear when a value needs to be set. // Note this will be ignored for non-select controls. $fieldArgs['empty'] = !\in_array($fieldName, ['status', 'sync_status_on_delete'], true) - || (isset($fieldOptions['empty']) && $fieldOptions['empty']); + || (isset($fieldOptions['empty']) && !empty($fieldOptions['empty'])); + + // Check if the empty option comes with a value + if($fieldArgs['empty'] + && !empty($fieldOptions['empty']) + && \is_string($fieldOptions['empty'])) { + $fieldArgs['empty'] = $fieldOptions['empty']; + } + + if(!empty($fieldOptions['all'])) { + $optionName = lcfirst(StringUtilities::foreignKeyToClassName($fieldName)); + $optionValues = $this->getView()->get($optionName); + $optionValues = [ + '-1' => $fieldOptions['all'], + ...$optionValues + ]; + $this->getView()->set($optionName, $optionValues); + } + + // Is this a multiple select + $fieldArgs['multiple'] = !empty($fieldOptions['multiple']); // Manipulate the vv_object for the hasPrefix use case $this->handlePrefix($fieldPrefix, $fieldName); diff --git a/app/templates/CoSettings/fields.inc b/app/templates/CoSettings/fields.inc index b7ff8281e..67d209881 100644 --- a/app/templates/CoSettings/fields.inc +++ b/app/templates/CoSettings/fields.inc @@ -105,11 +105,15 @@ if($vv_action == 'edit') { 'person_picker_email_address_type_id' => [ 'fieldOptions' => [ 'label' => __d('field', 'mail'), + 'empty' => '(' . __d('operation', 'all') . ')', +// 'all' => '(' . __d('operation', 'all') . ')' ], ], 'person_picker_identifier_type_id' => [ 'fieldOptions' => [ 'label' => __d('field', 'identifier'), + 'empty' => '(' . __d('operation', 'all') . ')', +// 'all' => '(' . __d('operation', 'all') . ')', ], ], 'person_picker_display_types' => [ diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index aa0cad4a3..7208397ba 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -253,7 +253,7 @@ export default {