From 9dcf3788cda9e074d9de118fb68f014605b7051c Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos <ioigoume@gmail.com> Date: Thu, 3 Oct 2024 17:02:37 +0300 Subject: [PATCH] Subnavigation fixes --- .../src/Controller/FileSourcesController.php | 23 +++ .../AttributeCollectorsController.php | 23 +++ .../EnrollmentAttributesController.php | 8 + .../Model/Table/AttributeCollectorsTable.php | 1 - .../Model/Table/EnrollmentAttributesTable.php | 4 +- .../EnrollmentAttributes/columns.inc | 1 + app/resources/locales/en_US/operation.po | 12 ++ .../EnrollmentFlowStepsController.php | 25 +++ .../ExternalIdentitiesController.php | 28 ++- app/src/Controller/GroupMembersController.php | 1 + .../Controller/GroupNestingsController.php | 1 + .../JobHistoryRecordsController.php | 1 + app/src/Controller/PetitionsController.php | 25 ++- .../Controller/StandardPluginController.php | 1 + app/src/Lib/Util/StringUtilities.php | 6 +- .../Model/Table/EnrollmentFlowStepsTable.php | 1 - .../Table/ExternalIdentityRolesTable.php | 6 +- app/src/Model/Table/PersonRolesTable.php | 2 +- app/src/View/Helper/TabHelper.php | 183 ++++++++++++++---- app/templates/EnrollmentFlowSteps/columns.inc | 4 - app/templates/Standard/add-edit-view.php | 23 ++- .../element/subnavigation/inlineList.php | 5 +- .../element/subnavigation/supertitle.php | 70 ++++++- .../element/subnavigation/tabList.php | 2 +- .../element/subnavigation/tabTitle.php | 14 +- 25 files changed, 392 insertions(+), 78 deletions(-) diff --git a/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php b/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php index bd8407232..0e5371d43 100644 --- a/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php +++ b/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php @@ -30,6 +30,8 @@ namespace FileConnector\Controller; use App\Controller\StandardPluginController; +use Cake\Event\EventInterface; +use Cake\Http\Response; class FileSourcesController extends StandardPluginController { public $paginate = [ @@ -37,4 +39,25 @@ class FileSourcesController extends StandardPluginController { 'FileSources.id' => 'asc' ] ]; + + /** + * Callback run prior to the request render. + * + * @param EventInterface $event Cake Event + * + * @return Response|void + * @since COmanage Registry v5.0.0 + */ + + public function beforeRender(EventInterface $event) { + $link = $this->getPrimaryLink(true); + + if(!empty($link->value)) { + $this->set('vv_bc_parent_obj', $this->FileSources->ExternalIdentitySources->get($link->value)); + $this->set('vv_bc_parent_displayfield', $this->FileSources->ExternalIdentitySources->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->FileSources->ExternalIdentitySources->getPrimaryKey()); + } + + return parent::beforeRender($event); + } } diff --git a/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php b/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php index 0e279be8e..27faa2749 100644 --- a/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php +++ b/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php @@ -31,6 +31,8 @@ use App\Controller\StandardEnrollerController; use App\Lib\Enum\PetitionStatusEnum; +use Cake\Event\EventInterface; +use Cake\Http\Response; class AttributeCollectorsController extends StandardEnrollerController { public $paginate = [ @@ -39,6 +41,27 @@ class AttributeCollectorsController extends StandardEnrollerController { ] ]; + /** + * Callback run prior to the request render. + * + * @param EventInterface $event Cake Event + * + * @return Response|void + * @since COmanage Registry v5.0.0 + */ + + public function beforeRender(EventInterface $event) { + $link = $this->getPrimaryLink(true); + + if(!empty($link->value)) { + $this->set('vv_bc_parent_obj', $this->AttributeCollectors->EnrollmentFlowSteps->get($link->value)); + $this->set('vv_bc_parent_displayfield', $this->AttributeCollectors->EnrollmentFlowSteps->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->AttributeCollectors->EnrollmentFlowSteps->getPrimaryKey()); + } + + return parent::beforeRender($event); + } + /** * Dispatch an Enrollment Flow Step. * diff --git a/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php b/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php index 84e1b1c16..38285c7c6 100644 --- a/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php +++ b/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php @@ -51,6 +51,14 @@ class EnrollmentAttributesController extends StandardEnrollerController { public function beforeRender(\Cake\Event\EventInterface $event) { $this->set('vv_supported_attributes', $this->EnrollmentAttributes->supportedAttributes()); + $link = $this->getPrimaryLink(true); + + if(!empty($link->value)) { + $this->set('vv_bc_parent_obj', $this->EnrollmentAttributes->AttributeCollectors->get($link->value)); + $this->set('vv_bc_parent_displayfield', $this->EnrollmentAttributes->AttributeCollectors->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->EnrollmentAttributes->AttributeCollectors->getPrimaryKey()); + } + $ret = parent::beforeRender($event); // Override the auto-generated title diff --git a/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php b/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php index 23bd6e444..2c1699ddf 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php @@ -40,7 +40,6 @@ class AttributeCollectorsTable extends Table { use \App\Lib\Traits\AutoViewVarsTrait; use \App\Lib\Traits\CoLinkTrait; - use \App\Lib\Traits\LayoutTrait; use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\TabTrait; diff --git a/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php b/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php index 6d22a1d9d..82dbfd249 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php @@ -41,13 +41,13 @@ class EnrollmentAttributesTable extends Table { use \App\Lib\Traits\AutoViewVarsTrait; use \App\Lib\Traits\CoLinkTrait; - use \App\Lib\Traits\LayoutTrait; use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; use \App\Lib\Traits\TabTrait; - + use \App\Lib\Traits\LayoutTrait; + /** * Perform Cake Model initialization. * diff --git a/app/plugins/CoreEnroller/templates/EnrollmentAttributes/columns.inc b/app/plugins/CoreEnroller/templates/EnrollmentAttributes/columns.inc index e325623fb..9fdc0d76b 100644 --- a/app/plugins/CoreEnroller/templates/EnrollmentAttributes/columns.inc +++ b/app/plugins/CoreEnroller/templates/EnrollmentAttributes/columns.inc @@ -33,6 +33,7 @@ $indexColumns = [ 'label' => [ 'type' => 'link', 'sortable' => true, + 'class' => 'cm-modal-link nospin', // launch this in a modal 'dataAttrs' => [ ['data-cm-modal-title', __d('operation', 'EnrollmentAttributes', 1)] ] diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index c30bec0e2..2101f4d29 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -135,6 +135,12 @@ msgstr "Edit" msgid "edit.a" msgstr "Edit {0}" +msgid "edit.PersonRoles.a" +msgstr "Edit Role {0}" + +msgid "edit.ExternalIdentityRoles.a" +msgstr "Edit Role {0}" + msgid "EmailAddresses.verify.force" msgstr "Force Verify" @@ -267,3 +273,9 @@ msgstr "View" msgid "view.a" msgstr "View {0}" +msgid "view.PersonRoles.a" +msgstr "View Role {0}" + +msgid "view.ExternalIdentityRoles.a" +msgstr "View Role {0}" + diff --git a/app/src/Controller/EnrollmentFlowStepsController.php b/app/src/Controller/EnrollmentFlowStepsController.php index 8d1796340..866c9a599 100644 --- a/app/src/Controller/EnrollmentFlowStepsController.php +++ b/app/src/Controller/EnrollmentFlowStepsController.php @@ -30,6 +30,8 @@ namespace App\Controller; // XXX not doing anything with Log yet +use Cake\Event\EventInterface; +use Cake\Http\Response; use Cake\Log\Log; class EnrollmentFlowStepsController extends StandardPluggableController { @@ -38,4 +40,27 @@ class EnrollmentFlowStepsController extends StandardPluggableController { 'EnrollmentFlowSteps.ordr' => 'asc' ] ]; + + /** + * Callback run prior to the request render. + * + * @param EventInterface $event Cake Event + * + * @return Response|void + * @since COmanage Registry v5.0.0 + */ + + public function beforeRender(EventInterface $event) { + // Pull the Person name for breadcrumb rendering + + $link = $this->getPrimaryLink(true); + + if(!empty($link->value)) { + $this->set('vv_bc_parent_obj', $this->EnrollmentFlowSteps->EnrollmentFlows->get($link->value)); + $this->set('vv_bc_parent_displayfield', $this->EnrollmentFlowSteps->EnrollmentFlows->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->EnrollmentFlowSteps->EnrollmentFlows->getPrimaryKey()); + } + + return parent::beforeRender($event); + } } \ No newline at end of file diff --git a/app/src/Controller/ExternalIdentitiesController.php b/app/src/Controller/ExternalIdentitiesController.php index 441f526e3..e1ab7db68 100644 --- a/app/src/Controller/ExternalIdentitiesController.php +++ b/app/src/Controller/ExternalIdentitiesController.php @@ -29,9 +29,8 @@ namespace App\Controller; -// XXX not doing anything with Log yet -use Cake\Log\Log; -use Cake\ORM\TableRegistry; +use Cake\Event\EventInterface; +use Cake\Http\Response; // Use extend MVEAController for breadcrumb rendering. ExternalIdentities is // sort of an MVEA, so maybe it makes sense to treat it as such. @@ -45,4 +44,27 @@ class ExternalIdentitiesController extends MVEAController { 'Names.family' ] ]; + + /** + * Callback run prior to the request render. + * + * @param EventInterface $event Cake Event + * + * @return Response|void + * @since COmanage Registry v5.0.0 + */ + + public function beforeRender(EventInterface $event) { + // Pull the Person name for breadcrumb rendering + + $link = $this->getPrimaryLink(true); + + if(!empty($link->value)) { + $this->set('vv_bc_parent_obj', $this->ExternalIdentities->People->get($link->value)); + $this->set('vv_bc_parent_displayfield', $this->ExternalIdentities->People->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->ExternalIdentities->People->getPrimaryKey()); + } + + return parent::beforeRender($event); + } } \ No newline at end of file diff --git a/app/src/Controller/GroupMembersController.php b/app/src/Controller/GroupMembersController.php index b258d32db..6d7306a9a 100644 --- a/app/src/Controller/GroupMembersController.php +++ b/app/src/Controller/GroupMembersController.php @@ -58,6 +58,7 @@ public function beforeRender(EventInterface $event) { if(!empty($link->value)) { $this->set('vv_bc_parent_obj', $this->GroupMembers->Groups->get($link->value)); $this->set('vv_bc_parent_displayfield', $this->GroupMembers->Groups->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->GroupMembers->Groups->getPrimaryKey()); } return parent::beforeRender($event); diff --git a/app/src/Controller/GroupNestingsController.php b/app/src/Controller/GroupNestingsController.php index a42f86c2f..20b6d76bd 100644 --- a/app/src/Controller/GroupNestingsController.php +++ b/app/src/Controller/GroupNestingsController.php @@ -54,6 +54,7 @@ public function beforeRender(\Cake\Event\EventInterface $event) { if(!empty($link->value)) { $this->set('vv_bc_parent_obj', $this->GroupNestings->Groups->get($link->value)); $this->set('vv_bc_parent_displayfield', $this->GroupNestings->Groups->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->GroupNestings->Groups->getPrimaryKey()); } // We need to calculate the available set of groups for nesting. We do this diff --git a/app/src/Controller/JobHistoryRecordsController.php b/app/src/Controller/JobHistoryRecordsController.php index 66948a95d..95ed62b55 100644 --- a/app/src/Controller/JobHistoryRecordsController.php +++ b/app/src/Controller/JobHistoryRecordsController.php @@ -55,6 +55,7 @@ public function beforeRender(\Cake\Event\EventInterface $event) { if(!empty($link->value)) { $this->set('vv_bc_parent_obj', $this->JobHistoryRecords->Jobs->get($link->value)); $this->set('vv_bc_parent_displayfield', $this->JobHistoryRecords->Jobs->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->JobHistoryRecords->Jobs->getPrimaryKey()); } return parent::beforeRender($event); diff --git a/app/src/Controller/PetitionsController.php b/app/src/Controller/PetitionsController.php index 4ab8a0d1a..53e746196 100644 --- a/app/src/Controller/PetitionsController.php +++ b/app/src/Controller/PetitionsController.php @@ -30,12 +30,14 @@ namespace App\Controller; // XXX not doing anything with Log yet +use Cake\Event\EventInterface; +use Cake\Http\Response; use Cake\Log\Log; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; -use \App\Lib\Util\StringUtilities; use \App\Lib\Enum\EnrollmentActorEnum; use \App\Lib\Enum\SuspendableStatusEnum; +use \App\Lib\Util\StringUtilities; class PetitionsController extends StandardController { use \App\Lib\Traits\EnrollmentControllerTrait; @@ -49,6 +51,27 @@ class PetitionsController extends StandardController { // Cached copy of the next step information private $nextStep = null; + /** + * Callback run prior to the request render. + * + * @param EventInterface $event Cake Event + * + * @return Response|void + * @since COmanage Registry v5.0.0 + */ + + public function beforeRender(EventInterface $event) { + $link = $this->getPrimaryLink(true); + + if(!empty($link->value)) { + $this->set('vv_bc_parent_obj', $this->Petitions->EnrollmentFlows->get($link->value)); + $this->set('vv_bc_parent_displayfield', $this->Petitions->EnrollmentFlows->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $this->Petitions->EnrollmentFlows->getPrimaryKey()); + } + + return parent::beforeRender($event); + } + /** * Calculate authorization for the current request. * diff --git a/app/src/Controller/StandardPluginController.php b/app/src/Controller/StandardPluginController.php index 3b132fd14..53f96cc35 100644 --- a/app/src/Controller/StandardPluginController.php +++ b/app/src/Controller/StandardPluginController.php @@ -96,6 +96,7 @@ public function beforeRender(\Cake\Event\EventInterface $event) { $this->set('vv_bc_parent_obj', $parentObj); $this->set('vv_bc_parent_displayfield', $parentDisplayField); + $this->set('vv_bc_parent_primarykey', $parentTable->getPrimaryKey()); // Override the title set in StandardController. Since that was set in edit() // which is called before the rendering hooks, this title will take precedence. diff --git a/app/src/Lib/Util/StringUtilities.php b/app/src/Lib/Util/StringUtilities.php index cad2457de..d1d5e644c 100644 --- a/app/src/Lib/Util/StringUtilities.php +++ b/app/src/Lib/Util/StringUtilities.php @@ -162,6 +162,7 @@ public static function entityAndActionToTitle($entity, $linkTable = TableRegistry::getTableLocator()->get($modelPath); $msgId = "{$action}.a"; + $msgIdOverride = "{$action}.{$modelsName}.a"; if(Inflector::singularize(self::entityToClassName($entity)) !== Inflector::singularize($modelsName)) { $linkTable = TableRegistry::getTableLocator()->get(self::entityToClassName($entity)); @@ -188,7 +189,10 @@ public static function entityAndActionToTitle($entity, && method_exists($linkTable, 'generateDisplayField')) { // We don't use a trait for this since each table will implement different logic - $title = __d($domain, $msgId, $linkTable->generateDisplayField($entity)); + $title = __d($domain, $msgIdOverride, $linkTable->generateDisplayField($entity)); + if ($msgIdOverride === $title) { + $title = __d($domain, $msgId, $linkTable->generateDisplayField($entity)); + } $supertitle = $linkTable->generateDisplayField($entity); // Pass the display field also into subtitle for dealing with External IDs $subtitle = $linkTable->generateDisplayField($entity); diff --git a/app/src/Model/Table/EnrollmentFlowStepsTable.php b/app/src/Model/Table/EnrollmentFlowStepsTable.php index 75a5c25a3..fd3bd94f2 100644 --- a/app/src/Model/Table/EnrollmentFlowStepsTable.php +++ b/app/src/Model/Table/EnrollmentFlowStepsTable.php @@ -41,7 +41,6 @@ class EnrollmentFlowStepsTable extends Table { use \App\Lib\Traits\AutoViewVarsTrait; use \App\Lib\Traits\ChangelogBehaviorTrait; use \App\Lib\Traits\CoLinkTrait; - use \App\Lib\Traits\LayoutTrait; use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PluggableModelTrait; use \App\Lib\Traits\PrimaryLinkTrait; diff --git a/app/src/Model/Table/ExternalIdentityRolesTable.php b/app/src/Model/Table/ExternalIdentityRolesTable.php index 0215fb171..2d780f8ce 100644 --- a/app/src/Model/Table/ExternalIdentityRolesTable.php +++ b/app/src/Model/Table/ExternalIdentityRolesTable.php @@ -46,10 +46,10 @@ class ExternalIdentityRolesTable extends Table { use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\QueryModificationTrait; - use \App\Lib\Traits\TableMetaTrait; - use \App\Lib\Traits\ValidationTrait; use \App\Lib\Traits\SearchFilterTrait; use \App\Lib\Traits\TabTrait; + use \App\Lib\Traits\TableMetaTrait; + use \App\Lib\Traits\ValidationTrait; /** * Perform Cake Model initialization. @@ -91,7 +91,7 @@ public function initialize(array $config): void { ->setDependent(true) ->setCascadeCallbacks(true); - $this->setDisplayField('id'); + $this->setDisplayField('title'); $this->setPrimaryLink('external_identity_id'); $this->setRequiresCO(true); diff --git a/app/src/Model/Table/PersonRolesTable.php b/app/src/Model/Table/PersonRolesTable.php index 5f6a932de..324b81f0f 100644 --- a/app/src/Model/Table/PersonRolesTable.php +++ b/app/src/Model/Table/PersonRolesTable.php @@ -175,7 +175,7 @@ public function initialize(array $config): void { // render in index mode for the same use case/context // XXX edit should go first. 'People' => ['edit', 'view'], - 'PersonRoles' => ['edit','view','index'], + 'PersonRoles' => ['edit', 'view', 'index'], 'ExternalIdentities' => ['index'], ], // What model will have a counter-badge after the tab title diff --git a/app/src/View/Helper/TabHelper.php b/app/src/View/Helper/TabHelper.php index 6eeaa36c6..23b6adc12 100644 --- a/app/src/View/Helper/TabHelper.php +++ b/app/src/View/Helper/TabHelper.php @@ -52,32 +52,32 @@ class TabHelper extends Helper * Only one group of actions is allowed under the subnavigation. For example: * - view/edit but not index * - index but not view/edit - * todo: we do not take into account readonly flag. * * @param string $tab * @param string|int $curId * @param bool $isNested * * @return array + * @since COmanage Registry v5.0.0 */ public function constructLinkUrl(string $tab, string|int $curId, bool $isNested = false): array { - $vv_sub_nav_attributes = $this->getView()->get('vv_sub_nav_attributes'); - $vv_subnavigation_action = $isNested ? - $vv_sub_nav_attributes['nested']['action'] - : $vv_sub_nav_attributes['action']; - $linkFilter = $this->getLinkFilter($tab, $curId); - + $curController = $this->getView()->getRequest()->getParam('controller'); $modelName = $tab; $controller = $modelName; $plugin = null; - $action = $vv_subnavigation_action[$tab][0] ?? null; - if(str_contains($tab, '.Plugin')) { - $modelName = $this->retrievePluginName($tab, (int)$curId); - [$plugin, $controller] = explode('.', $modelName); - $this->setPluginName($modelName); - } else if (str_contains($tab, '.Hierarchy')) { + // Action calculation + $action = $this->getTabAction($tab, $isNested); + // id or query parameter calculation + $linkFilter = $this->getLinkFilter($tab, $curId, $action, $isNested); + + // Controller + Plugin calculation + if(str_ends_with($tab, '.Plugin')) { + // This is always the second tab of the plugin and it is configuration + $controller = $curController; + $action = 'configure'; + } else if (str_ends_with($tab, '.Hierarchy')) { $modelName = $this->retrievePluginName($tab, (int)$curId); [$plugin, ] = explode('.', $modelName); foreach ($this->getHasManyAssociationModels($modelName) as $association) { @@ -93,19 +93,13 @@ public function constructLinkUrl(string $tab, string|int $curId, bool $isNested [$plugin, $controller] = explode('.', $modelName); } - // XXX Even though we use the model name above, here we need to use the tab name - // since we want to access the configuration. As a result we want the actual configuration - // and not the plugin name we calculated above $url = [ 'plugin' => $plugin, 'controller' => $controller, 'action' => $action ]; - if ( - $action === 'index' - && \in_array('index', $vv_subnavigation_action[$tab], true) - ) { + if ($action === 'index') { $url['?'] = $linkFilter; } else { $url[] = $curId; @@ -121,6 +115,7 @@ public function constructLinkUrl(string $tab, string|int $curId, bool $isNested * @param bool $isNested * * @return string + * @since COmanage Registry v5.0.0 */ public function getLinkClass(string $tab, bool $isNested = false): string { @@ -133,8 +128,6 @@ public function getLinkClass(string $tab, bool $isNested = false): string if(isset($plugin)) { $fullModelName = "{$plugin}.{$curController}"; } -// $associationDepth = 0; -// $isParent = $this->tabBelongsToModelPath($tab, $fullModelName, $associationDepth); // The list of Nested models is in order. Which means that the first tab is always the parent and the one // that will be set as active @@ -161,11 +154,12 @@ public function getLinkClass(string $tab, bool $isNested = false): string * @param int $depth * * @return bool + * @since COmanage Registry v5.0.0 */ public function tabBelongsToModelPath(string $tab, string $modelFullName, int &$depth = 0): bool { $model = TableRegistry::getTableLocator()->get($modelFullName); - // We'll start by obtaining the set of models directly associated with the CO model. + // We'll start by getting the set of models directly associated with the CO model. $associations = $model->associations(); $depth++; @@ -186,6 +180,7 @@ public function tabBelongsToModelPath(string $tab, string $modelFullName, int &$ * @param bool $isNested * * @return int + * @since COmanage Registry v5.0.0 */ public function getCurrentId(string $tabName = null, bool $isNested = false): int { @@ -210,14 +205,9 @@ public function getCurrentId(string $tabName = null, bool $isNested = false): in TableUtilities::treeTraversalFromId($curController, (int)$tid, $results); } - $tabAction = !empty($tab_actions[$tabName][0]) ? $tab_actions[$tabName][0] : null; + $tabAction = $this->getTabAction($tabName, $isNested); - if ( - $tabAction === null - && str_contains($tabName, '@action') - ) { - return (int)$results[ $tabs[0] ]; - } else if( + if( !$isNested && ($tabAction === 'index' || $curController !== $tabName) && \in_array($tabName, $tabs, true) @@ -229,6 +219,14 @@ public function getCurrentId(string $tabName = null, bool $isNested = false): in && \in_array($tabName, $tabs, true) ) { return (int)$tid; + } else if ( + $isNested + && $curController !== $tabName + && !str_contains($tabName, '.') + && \in_array($tabAction, ['view', 'edit'], true) + && \in_array($tabAction, $tab_actions[$tabName], true) + ) { + return (int)$results[ $tabName ]; } return (int)$tid; @@ -242,6 +240,7 @@ public function getCurrentId(string $tabName = null, bool $isNested = false): in * @param string $modelName * * @return \Generator + * @since COmanage Registry v5.0.0 */ public function getHasManyAssociationModels(string $modelName): \Generator { @@ -260,15 +259,25 @@ public function getHasManyAssociationModels(string $modelName): \Generator * * @param string $tab * @param int|string $curId + * @param string $tabAction + * @param bool $isNested * * @return int[]|string[] + * @since COmanage Registry v5.0.0 */ - public function getLinkFilter(string $tab, int|string $curId): array - { + public function getLinkFilter( + string $tab, + int|string $curId, + string $tabAction, + bool $isNested = false + ): array { $vv_sub_nav_attributes = $this->getView()->get('vv_sub_nav_attributes'); $subnav_tabs = $vv_sub_nav_attributes['tabs']; $subnav_allowed_actions = $vv_sub_nav_attributes['action']; - $vv_action = $this->getView()->get('vv_action'); + if ($isNested) { + $subnav_tabs = $vv_sub_nav_attributes['nested']['tabs']; + $subnav_allowed_actions = $vv_sub_nav_attributes['nested']['action']; + } $fullModelsName = $tab; $modelName = $tab; $curController = $this->getView()->getRequest()->getParam('controller'); @@ -308,32 +317,94 @@ public function getLinkFilter(string $tab, int|string $curId): array $foreignKey = StringUtilities::classNameToForeignKey($modelName); - // - If the primary link is the co_id, it means this is a root element. As a result, we will construct - // the link filter key from the controller itself. - // - The action the endpoint url action is edit, and we allow edit for this sub-navigation element we will - // return the id instead of any foreign key - // - We will fallback to the primary link directly. return match(true) { + // The current controller and the tab controller match + // If the action is edit or view then we return the id since we actually have no filter $fullModelsName === $curController - && $vv_action === 'edit' - && \in_array($vv_action, $subnav_allowed_actions[$fullModelsName], true) => ['id' => $curId], - $primary_link === 'co_id' => [$foreignKey => $curId], - default => [$primary_link => $curId] + && $tabAction !== 'index' => ['id' => $curId], + // If the current controller and the tab constructed controller do not match + // but we have an edit or view action then we have no link filter and no id. We return empty + $fullModelsName !== $curController + && \in_array($tabAction, ['edit', 'view'], true) + && isset($subnav_allowed_actions[$fullModelsName]) => [], + // If the action is index then filter using the primary link + \in_array($tabAction, ['index'], true) + && $primary_link !== 'co_id' => [$primary_link => $curId], + // - If the primary link is the co_id, it means this is a root element. As a result, we will construct + // the link filter key from the controller itself. + $primary_link === 'co_id' => [$foreignKey => $curId], + // We fallback to the primary link directly. + default => [$primary_link => $curId] }; } /** * @return string|null + * @since COmanage Registry v5.0.0 */ public function getPluginName(): ?string { return $this->pluginName; } + /** + * Calculate the Tab link action + * - First level Tab: + * The first level usually allows edit/view action for the first tab link and index for the rest. + * There are exceptions like: + * * External Identity Sources: This has a plugin structure. The first and second tab + * refer to the same action: + * - Plugin instantiation edit view + * - Plugin configuration view + * + * - Second level Tab: + * The second level follows the same logic. The first Tab Link is a view/edit + * while the rest are always an index Link + * + * @param string $tab + * @param bool $isNested + * + * @return string|null + * @since COmanage Registry v5.0.0 + */ + public function getTabAction(string $tab, bool $isNested = false): ?string + { + $vv_sub_nav_attributes = $this->getView()->get('vv_sub_nav_attributes'); + $subnav_tabs = $vv_sub_nav_attributes['tabs']; + $subnav_allowed_actions = $vv_sub_nav_attributes['action']; + if ($isNested) { + $subnav_tabs = $vv_sub_nav_attributes['nested']['tabs']; + $subnav_allowed_actions = $vv_sub_nav_attributes['nested']['action']; + } + $vv_action = $this->getView()->get('vv_action'); + $curController = $this->getView()->getRequest()->getParam('controller'); + + $modelName = $tab; + $controller = $modelName; + + $customActionRegex = '/^.*?(@action.)(\w+)/m'; + $customAction = preg_match_all($customActionRegex, $tab, $matches, PREG_SET_ORDER, 0); + return match(true) { + // We get the action from the configuration + filter_var($customAction, FILTER_VALIDATE_BOOLEAN) => $matches[0][2], + // First level ONLY: return the index action (applies to all tabs except the first one) + $subnav_tabs[0] !== $tab + && \in_array('index', $subnav_allowed_actions[$tab], true) => 'index', + // Second level ONLY: return the action of the url. We could just say edit + // but we might have a use case with a different action? + $controller === $curController + && \in_array($vv_action, $subnav_allowed_actions[$tab], true) + && $isNested => $vv_action, + // Return the first action we allow in the configuration + default => $subnav_allowed_actions[$tab][0] + }; + } + /** * @param string|null $pluginName * * @return void + * @since COmanage Registry v5.0.0 */ public function setPluginName(?string $pluginName): void { @@ -347,6 +418,7 @@ public function setPluginName(?string $pluginName): void * @param int $curId * * @return string + * @since COmanage Registry v5.0.0 */ public function retrievePluginName(string $tab, int $curId): string { @@ -368,6 +440,7 @@ public function retrievePluginName(string $tab, int $curId): string * @param string $modelsName * * @return Table + * @since COmanage Registry v5.0.0 */ public function getModelTableReference(string $modelsName): Table { @@ -381,6 +454,7 @@ public function getModelTableReference(string $modelsName): Table * @param array $whereClause where clause array * * @return int + * @since COmanage Registry v5.0.0 */ public function getModelTotalCount(string $modelName, array $whereClause): int { @@ -395,9 +469,11 @@ public function getModelTotalCount(string $modelName, array $whereClause): int /** * Get Person Status by ID + * * @param int $personId * * @return string + * @since COmanage Registry v5.0.0 */ public function getPersonStatus(int $personId): string { @@ -411,8 +487,30 @@ public function getPersonStatus(int $personId): string return $response?->status; } + /** + * Get Person Full Name by + * + * @param int $personId + * + * @return string + * @since COmanage Registry v5.0.0 + */ + public function getPersonPrimaryName(int $personId): string + { + $namesTable = TableRegistry::getTableLocator()->get('names'); + $response = $namesTable + ->find() + ->select(['given', 'family', 'middle', 'honorific', 'suffix']) + ->where(['person_id' => $personId]) + ->where(['primary_name' => true]) + ->first(); + + return $response?->full_name; + } + /** * @return string|null + * @since COmanage Registry v5.0.0 */ public function getAssociation(): ?string { @@ -423,6 +521,7 @@ public function getAssociation(): ?string * @param string|null $association * * @return void + * @since COmanage Registry v5.0.0 */ public function setAssociation(?string $association): void { diff --git a/app/templates/EnrollmentFlowSteps/columns.inc b/app/templates/EnrollmentFlowSteps/columns.inc index 54283dc18..6786d79d2 100644 --- a/app/templates/EnrollmentFlowSteps/columns.inc +++ b/app/templates/EnrollmentFlowSteps/columns.inc @@ -33,10 +33,6 @@ $indexColumns = [ 'description' => [ 'type' => 'link', 'sortable' => true, - 'class' => 'cm-modal-link nospin row-link', // launch this in a modal - 'dataAttrs' => [ - ['data-cm-modal-title', __d('controller', 'EnrollmentFlowSteps', 1)] - ] ], 'actor_type' => [ 'type' => 'enum', diff --git a/app/templates/Standard/add-edit-view.php b/app/templates/Standard/add-edit-view.php index cfd18086b..0d6d6fbc2 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -71,14 +71,33 @@ include(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc'); $hasSubnav = $this->get('hasSupertitle'); } + +// When under a subnavigation we do not want a title with Edit or Add or View followed by a number +// We might find ourselved in that situation since we calculate the title for the breadcrumbs and +// this simple description is not wrong. It is just not appropriate for the subnavigation title +$title = $vv_title; +$re = '/^(Add|Edit|View)\s([a-zA-Z]+?)\s[0-9]+/m'; +$pregMatch = preg_match_all($re, $vv_title, $matches, PREG_SET_ORDER, 0); +if ( + $hasSubnav + && filter_var($pregMatch, FILTER_VALIDATE_BOOLEAN) +) { + $vvObjTable = $this->Tab->getModelTableReference($fullModelsName); + $displayField = $vvObjTable->getDisplayField(); + if($displayField !== 'id') { + $title = __d('operation', "$vv_action.$modelsName.a", [$vv_obj->$displayField]); + } else { + $title = __d('operation', $vv_action . '.a', [__d('controller', $modelsName, 1)]); + } +} ?> <div class="page-title-container"> <div class="page-title"> <?php if(!$hasSubnav): ?> - <h1><?= $vv_title ?></h1> + <h1><?= $title ?></h1> <?php else: ?> - <h2><?= $vv_title ?></h2> + <h2><?= $title ?></h2> <?php endif; ?> </div> <?php diff --git a/app/templates/element/subnavigation/inlineList.php b/app/templates/element/subnavigation/inlineList.php index 0d0e52374..9a3d38d60 100644 --- a/app/templates/element/subnavigation/inlineList.php +++ b/app/templates/element/subnavigation/inlineList.php @@ -33,15 +33,12 @@ $isNested = true; extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); - -$curId = $this->request->getQuery($vv_primary_link) - ?? $vv_obj->id - ?? end($vv_bc_title_links[0]['target']); ?> <?php foreach($vv_subnavigation_nested['tabs'] as $tab): ?> <li class="list-inline-item"> <?php + $curId = $this->Tab->getCurrentId($tab, $isNested); // Construct Element Title $title = $this->element('subnavigation/tabTitle', compact('tab', 'curId', 'isNested')); // Construct Target URL diff --git a/app/templates/element/subnavigation/supertitle.php b/app/templates/element/subnavigation/supertitle.php index 64ac955b7..b5f238df3 100644 --- a/app/templates/element/subnavigation/supertitle.php +++ b/app/templates/element/subnavigation/supertitle.php @@ -25,20 +25,72 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ - declare(strict_types = 1); +use App\Lib\Util\StringUtilities; +use App\Lib\Util\TableUtilities; +use \Cake\Utility\Inflector; + extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +/* + * Person with deep nested dependency + */ + +$person_id = (int)($vv_person_id ?? $vv_obj?->person_id ?? $this->getRequest()->getQuery('person_id')); +if ($person_id) { + $personFullName = $this->Tab->getPersonPrimaryName($person_id); +} + +// If we have a multi-level navigation layout but the first level is based on the `Person` then the `Supertitle` will always +// be the full name of the Person. The reason for calculating this separately is that the Full Name of the person +// is not part of the People table +if ( + isset($personFullName) + && \in_array('People', $vv_sub_nav_attributes['tabs'], true) +) { + $vv_subnavigation_tabsSupertitle = $personFullName; +} + +/* + * Plugin with deep nested dependency + * The supertitle is going to be the display value of the first tab, + * i.e., of the plugin instantiation wrapper +*/ +$objectName = Inflector::tableize($vv_controller); +if ( + (!empty($vv_obj) || !empty($$objectName)) + && !empty($this->getPlugin()) + && $vv_subnavigation_tabs[0] !== StringUtilities::entityToClassName($vv_bc_parent_obj) +) { + $object = $vv_obj ?? $$objectName?->first(); + // If we get here, it means that neither the request object nor its parent can give us a supertitle. + // We need to fetch all the ids and get the supertitle from the root tab/node + $results = []; + TableUtilities::treeTraversalFromId(StringUtilities::entityToClassName($object), (int)$object->id, $results); + $superTitleModelReference = $this->Tab->getModelTableReference($vv_subnavigation_tabs[0]); + $superTitleModelDisplayField = $superTitleModelReference->getDisplayField(); + $superTitleModelId = $results[$vv_subnavigation_tabs[0]]; + + $root_obj = $superTitleModelReference->get($superTitleModelId); + $vv_subnavigation_tabsSupertitle = $root_obj->$superTitleModelDisplayField; +} + $supertitle = match (true) { - !empty($vv_subnavigation_tabsSupertitle) => $vv_subnavigation_tabsSupertitle, - !empty($vv_bc_parent_obj?->$vv_bc_parent_displayfield) => $vv_bc_parent_obj?->$vv_bc_parent_displayfield, - !empty($vv_supertitle) => $vv_supertitle, - !empty($vv_obj->$vv_display_field) => $vv_obj->$vv_display_field, - !empty($vv_primary_link_obj->name) => $vv_primary_link_obj->name, - !empty($vv_bc_title_links) => $vv_bc_title_links[0]['label'], - !empty($vv_title) => $vv_title, - default => __d('information','global.title.none') + // Directly from the Configuration + !empty($vv_subnavigation_tabsSupertitle) => $vv_subnavigation_tabsSupertitle, + // Display parent's display field + isset($vv_bc_parent_primarykey, $vv_bc_parent_obj) + && $vv_bc_parent_primarykey !== $vv_bc_parent_obj + && !empty($vv_bc_parent_obj?->$vv_bc_parent_displayfield) => $vv_bc_parent_obj?->$vv_bc_parent_displayfield, + // Set in the Controller + !empty($vv_supertitle) => $vv_supertitle, + // Get the object display field + !empty($vv_obj->$vv_display_field) => $vv_obj->$vv_display_field, + !empty($vv_bc_title_links) => $vv_bc_title_links[0]['label'], + !empty($vv_title) => $vv_title, + default => __d('information','global.title.none') }; // This allows us to know that we have subnavigation in the diff --git a/app/templates/element/subnavigation/tabList.php b/app/templates/element/subnavigation/tabList.php index 73bd0ffc3..f5baabb3b 100644 --- a/app/templates/element/subnavigation/tabList.php +++ b/app/templates/element/subnavigation/tabList.php @@ -40,7 +40,7 @@ <?php $curId = $this->Tab->getCurrentId($tab, $isNested); // Calculate Tab Title - $title = $this->element('subnavigation/tabTitle', compact('tab', 'curId')); + $title = $this->element('subnavigation/tabTitle', compact('tab', 'curId', 'isNested')); // Construct Target URL $url = $this->Tab->constructLinkUrl($tab, $curId, $isNested); // Calculate Tab Style Class(es) diff --git a/app/templates/element/subnavigation/tabTitle.php b/app/templates/element/subnavigation/tabTitle.php index 13d2e507f..9c8c6fd16 100644 --- a/app/templates/element/subnavigation/tabTitle.php +++ b/app/templates/element/subnavigation/tabTitle.php @@ -27,6 +27,13 @@ * */ +/* + * Parameters: + * $tab : string, required + * $curId : int, required + * $isNested : boolean, required + */ + declare(strict_types = 1); use Cake\Utility\Inflector; @@ -41,7 +48,8 @@ } // We calculate this first because we want to initialize the Helper variables -$linkFilter = $this->Tab->getLinkFilter($tab, $curId); +$tabAction = $this->Tab->getTabAction($tab, $isNested); +$linkFilter = $this->Tab->getLinkFilter($tab, $curId, $tabAction, $isNested); // Simple use case $tabLanguageKey = in_array('index', $navigation_action[$tab], true) ? $tab : 'Properties'; @@ -51,12 +59,12 @@ // Plugin Configuration Tab if (str_contains($tab, '.') && in_array('edit', $navigation_action[$tab], true)) { $title = 'Configure Plugin'; -} else if (str_contains($tab, '@action')) { // Top Links/Actions +} else if (str_contains($tab, '@action.')) { // Top Links/Actions [$modelName, ] = explode('@', $tab); [, $action] = explode('.', $tab); $title = __d('operation', $modelName . '.' . $action); $tabToTableName = Inflector::tableize(Inflector::singularize($modelName)); -} else if (str_contains($tab, '.Hierarchy')) { // Deep Associations +} else if (str_ends_with($tab, '.Hierarchy')) { // Deep Associations $fullModelName = $this->Tab->getAssociation(); [$plugin, $modelName] = explode('.', $fullModelName); $poFile = Inflector::underscore($plugin);