From 9672670b88f300c4924f1e6f715722f189b37109 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Mon, 13 Jan 2025 12:04:15 +0200 Subject: [PATCH 1/7] Application State.Side menu. --- app/config/schema/schema.json | 10 +-- app/resources/locales/en_US/error.po | 9 ++- app/resources/locales/en_US/information.po | 3 + app/src/Controller/ApiV2Controller.php | 21 +++++- app/src/Controller/AppController.php | 37 ++++++++++- .../Component/RegistryAuthComponent.php | 10 +-- app/src/Lib/Enum/ApplicationStateEnum.php | 34 ++++++++++ .../Model/Table/ApplicationStatesTable.php | 66 ++++++++++++++++++- app/src/Model/Table/IdentifiersTable.php | 2 - app/src/Model/Table/JobsTable.php | 2 +- app/templates/element/javascript.php | 26 ++++++++ app/templates/element/menuMain.php | 23 ++++++- app/templates/element/peopleAutocomplete.php | 2 - app/templates/layout/default.php | 13 ++++ 14 files changed, 234 insertions(+), 24 deletions(-) create mode 100644 app/src/Lib/Enum/ApplicationStateEnum.php diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index 8e1f52ca1..5ce03214a 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -874,14 +874,16 @@ "application_states": { "columns": { "id": {}, + "co_id": {}, "person_id": {}, - "tag": { "type": "string", "size": 128 }, + "username": { "type": "string", "size": 512 }, + "tag": { "type": "string", "size": 2 }, "value": { "type": "string", "size": 256 } }, "indexes": { - "application_states_i1": { "columns": [ "person_id" ] } - }, - "changelog": false + "application_states_i1": { "columns": [ "co_id" ] }, + "application_states_i2": { "columns": [ "person_id" ] } + } } }, diff --git a/app/resources/locales/en_US/error.po b/app/resources/locales/en_US/error.po index 53a0a557a..871a8b300 100644 --- a/app/resources/locales/en_US/error.po +++ b/app/resources/locales/en_US/error.po @@ -229,9 +229,6 @@ msgstr "Provided value is not an integer" msgid "Jobs.plugin.parameter.invalid" msgstr "Invalid parameter" -msgid "Jobs.plugin.parameter.required" -msgstr "Required parameter not provided" - msgid "Jobs.plugin.parameter.select" msgstr "Provided value is not a valid choice" @@ -289,6 +286,9 @@ msgstr "Notification status {0} is not a valid resolution" msgid "notprov" msgstr "{0} not provided" +msgid "notsupported" +msgstr "Not supported" + msgid "ordr.unique" msgstr "Each {0} must have a unique order" @@ -298,6 +298,9 @@ msgstr "Page number may not be larger than {0}" msgid "pagenum.nan" msgstr "Page number must be an integer" +msgid "Parameter.required" +msgstr "Required parameter '{0}' not provided" + msgid "perm" msgstr "Permission Denied" diff --git a/app/resources/locales/en_US/information.po b/app/resources/locales/en_US/information.po index ad449ff2f..10f681d64 100644 --- a/app/resources/locales/en_US/information.po +++ b/app/resources/locales/en_US/information.po @@ -135,6 +135,9 @@ msgstr "This plugin requires no configuration." msgid "plugin.inactive" msgstr "Inactive" +msgid "state.info" +msgstr "{0} successfully {1} to {2}" + msgid "record" msgstr "Record" diff --git a/app/src/Controller/ApiV2Controller.php b/app/src/Controller/ApiV2Controller.php index add5bbb5a..a9197be5c 100644 --- a/app/src/Controller/ApiV2Controller.php +++ b/app/src/Controller/ApiV2Controller.php @@ -29,6 +29,7 @@ namespace App\Controller; +use Cake\Controller\Controller; use InvalidArgumentException; use Cake\Chronos\Chronos; use Cake\Http\Exception\BadRequestException; @@ -47,7 +48,6 @@ class ApiV2Controller extends AppController { * Perform Cake Controller initialization. * * @since COmanage Registry v5.0.0 - * @param array $config Configuration options passed to constructor */ public function initialize(): void { @@ -100,7 +100,7 @@ public function add() { $results[] = ['id' => $obj->id]; // Trigger provisioning, letting errors bubble up (AR-GMR-5) - if(method_exists($this->modelsName, "requestProvisioning")) { + if(method_exists($this->$modelsName, "requestProvisioning")) { $this->llog('rule', "AR-GMR-5 Requesting provisioning for $modelsName " . $obj->id); $table->requestProvisioning(id: $obj->id, context: ProvisioningContextEnum::Automatic); } @@ -121,7 +121,22 @@ public function add() { // Let the view render $this->render('/Standard/api/v2/json/add-edit'); } - + + /** + * beforeFilter callback. + * + * @param \Cake\Event\EventInterface $event Event. + * @return \Cake\Http\Response|null|void + */ + public function beforeFilter(\Cake\Event\EventInterface $event) + { + parent::beforeFilter($event); + + if ($this->request->is('ajax') && $this->request->is(['post', 'put'])) { + $this->FormProtection->setConfig('validate', false); + } + } + /** * Callback run prior to the request rendering. * diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index f9ef73af5..be5eebf09 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -99,7 +99,7 @@ public function initialize(): void { * * In general, we don't need these protections for transactional API calls. */ - $this->loadComponent('Security'); + $this->loadComponent('FormProtection'); // CSRF Protection is enabled via in Middleware via Application.php. } @@ -142,7 +142,14 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { // so it's available to CosController::select $this->populateAvailableCos(); } - + + // Find and populate the person ID + if($this->getCOID() !== null) { + $this->set('vv_person_id', $this->RegistryAuth->getPersonId($this->getCOID())); + } + + $this->getAppPrefs(); + return parent::beforeFilter($event); } @@ -368,6 +375,32 @@ protected function primaryLinkLookup(): void } } + /** + * Get a user's Application State + * - postcondition: Application Preferences variable set + * @since COmanage Registry v5.1.0 + */ + protected function getAppPrefs() { + $request = $this->getRequest(); + $session = $request->getSession(); + + $username = $session->read('Auth.external.user'); + $appPrefs = null; + + // Get preferences if we have an Auth.User.co_person_id + if(!empty($username)) { + $ApplicationStates = $this->fetchTable('ApplicationStates'); + $appPrefs = $ApplicationStates->retrieveAll( + username: $username, + // If we have not selected a CO yet, there will be no co_id + coid: $this->getCOID(), + personid: $this->viewBuilder()->getVar('vv_person_id') + ); + } + + $this->set('vv_app_prefs', $appPrefs); + } + /** * Collect information about the Standard Object's Primary Link, if set. * The $vv_primary_link view variable is also set. diff --git a/app/src/Controller/Component/RegistryAuthComponent.php b/app/src/Controller/Component/RegistryAuthComponent.php index e0854862e..7171707e2 100644 --- a/app/src/Controller/Component/RegistryAuthComponent.php +++ b/app/src/Controller/Component/RegistryAuthComponent.php @@ -633,6 +633,7 @@ public function getMenuPermissions(?int $coId): array { * @since COmanage Registry v5.0.0 * @param int $coId CO ID * @throws RuntimeException + * @throws RecordNotFoundException */ public function getPersonID(int $coId): ?int { @@ -649,10 +650,11 @@ public function getPersonID(int $coId): ?int { $Identifiers = TableRegistry::getTableLocator()->get('Identifiers'); try { - return $Identifiers->lookupPersonByLogin($coId, $this->authenticatedUser); - } - catch(Cake\Datasource\Exception\RecordNotFoundException $e) { - return null; + $personId = (int)$Identifiers->lookupPersonByLogin($coId, $this->authenticatedUser); + } catch(RecordNotFoundException) { + $personId = null; + } finally { + return $personId; } } diff --git a/app/src/Lib/Enum/ApplicationStateEnum.php b/app/src/Lib/Enum/ApplicationStateEnum.php new file mode 100644 index 000000000..1b64fb0f6 --- /dev/null +++ b/app/src/Lib/Enum/ApplicationStateEnum.php @@ -0,0 +1,34 @@ +addBehavior('Log'); + $this->addBehavior('Changelog'); $this->addBehavior('Timestamp'); $this->setTableType(\App\Lib\Enum\TableTypeEnum::Metadata); // Define associations + $this->belongsTo('Cos'); $this->belongsTo('People'); $this->setDisplayField('tag'); + $this->setPrimaryLink(['co_id', 'person_id']); + + + $this->setPermissions([ + // Actions that operate over an entity (ie: require an $id) + 'entity' => [ + 'delete' => true, + 'edit' => true, + 'unfreeze' => true, + 'view' => true + ], + // Actions that operate over a table (ie: do not require an $id) + 'table' => [ + 'add' => true, + 'index' => true, + 'deleted' => true + ], + ]); } /** @@ -74,7 +99,46 @@ public function validationDefault(Validator $validator): Validator { $this->registerStringValidation($validator, $schema, 'tag', true); $this->registerStringValidation($validator, $schema, 'value', false); - +// $validator->add('co_id', [ +// 'content' => ['rule' => 'isInteger'] +// ]); + $this->registerStringValidation($validator, $schema, 'username', false); + $validator->add('username', [ + // Username must have at least one non-space character to avoid + 'content' => ['rule' => ['notBlank'], + 'message' => __d('error', 'input.blank')] + ]); + return $validator; } + + /** + * Retrieve all Application State. + * + * @param string $username + * @param int|null $coid + * @param int|null $personid + * + * @return array List of application states or an empty array + * @since COmanage Registry v5.1.0 + */ + + public function retrieveAll(string $username, ?int $coid, ?int $personid): array { + $subquery = $this->find(); + $subquery = $subquery->where(['username' => $username]); + if ($coid !== null) { + $subquery = $subquery->where(['co_id' => $coid]); + } else { + $subquery = $subquery->where(fn(QueryExpression $exp, Query $query) => $exp->isNull('co_id')); + } + if ($personid !== null) { + $subquery = $subquery->where(['person_id' => $coid]); + } else { + $subquery = $subquery->where(fn(QueryExpression $exp, Query $query) => $exp->isNull('person_id')); + } + $subquery = $subquery->select($this); + $tags = $subquery->toArray(); + + return $tags ?? []; + } } \ No newline at end of file diff --git a/app/src/Model/Table/IdentifiersTable.php b/app/src/Model/Table/IdentifiersTable.php index 5c7dad46d..b9579af35 100644 --- a/app/src/Model/Table/IdentifiersTable.php +++ b/app/src/Model/Table/IdentifiersTable.php @@ -205,8 +205,6 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour * * @param int $typeId Identifier Type ID * @param string $identifier Identifier - * @param int|null $coId CO Id - * @param bool $login The identifier is login enabled * * @return int Person ID * @since COmanage Registry v5.0.0 diff --git a/app/src/Model/Table/JobsTable.php b/app/src/Model/Table/JobsTable.php index f12721016..5a74f03fd 100644 --- a/app/src/Model/Table/JobsTable.php +++ b/app/src/Model/Table/JobsTable.php @@ -676,7 +676,7 @@ protected function validateJobParameters(string $plugin, int $coId, array $param if(isset($pluginParameters[$p]['required']) && $pluginParameters[$p]['required'] && empty($params[$p])) { - $ret[$p] = __d('error', 'Jobs.plugin.parameter.required'); + $ret[$p] = __d('error', 'parameter.required', [$p]); } } diff --git a/app/templates/element/javascript.php b/app/templates/element/javascript.php index 80510a242..9fa24a093 100644 --- a/app/templates/element/javascript.php +++ b/app/templates/element/javascript.php @@ -39,6 +39,32 @@ // DESKTOP MENU DRAWER BEHAVIOR $('#co-menu-collapse').click(function(){ + // Desktop mode + if ($("#navigation-drawer").hasClass("closed")) { + setApplicationPreference( + $("#navigation-drawer").data('stateattr'), + "open", + $("#navigation-drawer").data('username'), + $("#navigation-drawer").data('webroot'), + $("#navigation-drawer").data('coid'), + $("#navigation-drawer").data('personid'), + false, + false, + $("#navigation-drawer").data('appstateid') + ); + } else { + setApplicationPreference( + $("#navigation-drawer").data('stateattr'), + "closed", + $("#navigation-drawer").data('username'), + $("#navigation-drawer").data('webroot'), + $("#navigation-drawer").data('coid'), + $("#navigation-drawer").data('personid'), + false, + false, + $("#navigation-drawer").data('appstateid') + ); + } $('#navigation-drawer').toggleClass('closed'); }); diff --git a/app/templates/element/menuMain.php b/app/templates/element/menuMain.php index caad73a47..780d55788 100644 --- a/app/templates/element/menuMain.php +++ b/app/templates/element/menuMain.php @@ -28,10 +28,29 @@ // The following menu will only render if we have a user and CO (see default.ctp) // and if the user has any menu to render. -$userHasMenu = in_array( true, $vv_menu_permissions, true ); +use App\Lib\Enum\ApplicationStateEnum; + +$userHasMenu = in_array(true, $vv_menu_permissions, true ); + +$drawerState = 'open'; +$appStateId = ''; +if(!empty($vv_app_prefs)) { + $appState = (collection($vv_app_prefs)) + ?->filter(fn ($value, $key) => $value->tag == 'UD') + ?->first(); + $drawerState = $appState?->value ?? $drawerState; + $appStateId = $appState?->id ?? ''; +} ?> -