From 14d2f13a8b5b8eab6c5660446c2529df7e30668b Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 25 Sep 2024 13:52:21 +0300 Subject: [PATCH 01/16] Group to tabs element --- .../AttributeCollectors/dispatch.inc | 17 +- .../AttributeCollectors/fields-nav.inc | 14 +- .../templates/EnrollmentAttributes/fields.inc | 13 - .../Component/BreadcrumbComponent.php | 13 +- app/src/Lib/Traits/TabTrait.php | 59 +++ app/src/Model/Table/GroupMembersTable.php | 15 +- app/src/Model/Table/GroupNestingsTable.php | 15 +- app/src/Model/Table/GroupsTable.php | 25 +- .../EnrollmentFlowSteps/fields-nav.inc | 6 +- .../ExternalIdentitySources/retrieve.php | 2 +- .../ExternalIdentitySources/search.php | 16 +- app/templates/GroupMembers/columns.inc | 5 - app/templates/GroupNestings/columns.inc | 4 - app/templates/GroupNestings/fields-nav.inc | 5 - app/templates/Groups/fields-nav.inc | 5 - app/templates/Standard/add-edit-view.php | 23 +- app/templates/Standard/index.php | 22 +- app/templates/element/subnavigation.php | 489 ------------------ .../element/subnavigation/enrollmentFlow.php | 76 +++ .../element/subnavigation/person.php | 107 ++++ .../element/subnavigation/plugin.php | 109 ++++ .../element/subnavigation/statusBadge.php | 48 ++ .../subnavigation/subNavigationChild.php | 111 ++++ .../subnavigation/subNavigationRoot.php | 80 +++ .../element/subnavigation/supertitle.php | 46 ++ .../element/subnavigation/tabList.php | 89 ++++ .../tabTitle.php} | 6 +- 27 files changed, 854 insertions(+), 566 deletions(-) create mode 100644 app/src/Lib/Traits/TabTrait.php delete mode 100644 app/templates/element/subnavigation.php create mode 100644 app/templates/element/subnavigation/enrollmentFlow.php create mode 100644 app/templates/element/subnavigation/person.php create mode 100644 app/templates/element/subnavigation/plugin.php create mode 100644 app/templates/element/subnavigation/statusBadge.php create mode 100644 app/templates/element/subnavigation/subNavigationChild.php create mode 100644 app/templates/element/subnavigation/subNavigationRoot.php create mode 100644 app/templates/element/subnavigation/supertitle.php create mode 100644 app/templates/element/subnavigation/tabList.php rename app/templates/element/{tabs/tabTitleWithCount.php => subnavigation/tabTitle.php} (89%) diff --git a/app/plugins/CoreEnroller/templates/AttributeCollectors/dispatch.inc b/app/plugins/CoreEnroller/templates/AttributeCollectors/dispatch.inc index d0a6475ec..86257c536 100644 --- a/app/plugins/CoreEnroller/templates/AttributeCollectors/dispatch.inc +++ b/app/plugins/CoreEnroller/templates/AttributeCollectors/dispatch.inc @@ -38,13 +38,14 @@ if($vv_action == 'dispatch') { } } - print $this->Field->control( - // We prefix the attribute ID with a string because Cake seems to sometimes have - // problems with field names that are purely integers (even if cast to strings) - fieldName: "field-".$attr->id, - labelText: $attr->label, - controlType: "string", - options: $options - ); + print $this->element('form/listItem', [ + 'arguments' => [ + // We prefix the attribute ID with a string because Cake seems to sometimes have + // problems with field names that are purely integers (even if cast to strings) + 'fieldName' => 'field-' . $attr->id, + 'fieldOptions' => $options, + 'fieldLabel' => $attr->label + ] + ]); } } \ No newline at end of file diff --git a/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc b/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc index 45b5bbb81..901bd6e13 100644 --- a/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc +++ b/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc @@ -33,7 +33,19 @@ $topLinks[] = [ 'plugin' => 'CoreEnroller', 'controller' => 'enrollment_attributes', 'action' => 'index', - 'attribute_collector_id' => $vv_obj->id + '?' => [ + 'attribute_collectors' => $vv_obj['id'], + 'attribute_collectors_id' => $vv_obj['id'], + 'attribute_collector_id' => $vv_obj['id'], + ] ], 'class' => '' ]; + +$subnav = [ + 'name' => 'plugin', + 'active' => 'plugin', +]; + +//https://comanage-ioi-dev.workbench.incommon.org/registry-pe/core-enroller/enrollment-attributes?attribute_collectors=1&attribute_collector_id=1 +//https://comanage-ioi-dev.workbench.incommon.org/registry-pe/core-enroller/enrollment-attributes?attribute_collectors=1 \ No newline at end of file diff --git a/app/plugins/CoreEnroller/templates/EnrollmentAttributes/fields.inc b/app/plugins/CoreEnroller/templates/EnrollmentAttributes/fields.inc index 1948022cf..8791062f7 100644 --- a/app/plugins/CoreEnroller/templates/EnrollmentAttributes/fields.inc +++ b/app/plugins/CoreEnroller/templates/EnrollmentAttributes/fields.inc @@ -533,19 +533,6 @@ if($vv_action == 'add' || $vv_action == 'edit') { ] ]]); -// print $this->Field->control( -// fieldName: 'default_value_validity_type', -// controlType: 'select', -// options: [ -// 'options' => [ -// 'on' => __d('core_enroller', 'enumeration.DefaultValueValidityType.on'), -// 'after' => __d('core_enroller', 'enumeration.DefaultValueValidityType.after') -// ], -//// 'empty' => false, -// 'onChange' => 'updateValidityGadgets()' -// ] -// ); - // The default value is always stored in default_value, however for select based fields // (such as cou_id or affiliation) the value is copied into default_value and the // field specific attribute is used for display purposes only diff --git a/app/src/Controller/Component/BreadcrumbComponent.php b/app/src/Controller/Component/BreadcrumbComponent.php index 071733ee8..f6e54df4f 100644 --- a/app/src/Controller/Component/BreadcrumbComponent.php +++ b/app/src/Controller/Component/BreadcrumbComponent.php @@ -191,7 +191,7 @@ public function injectPrimaryLink(object $link, bool $index=true, string $linkLa // In the case we are dealing with non-standard actions we need to fallback to a standard one // in order to get access to the contain array. We will use the permissions to decide which // action to fall back to - if(!in_array($requestAction, [ + if(!\in_array($requestAction, [ 'index', 'view', 'delete', 'add', 'edit' ])) { $permissionsArray = $this->getController()->RegistryAuth->calculatePermissionsForView($requestAction); @@ -276,7 +276,7 @@ public function injectTitleLink( $entity, string $action='edit', ?string $label=null - ) { + ): void { $displayField = $table->getDisplayField(); $this->injectTitleLinks[] = [ @@ -298,7 +298,8 @@ public function injectTitleLink( * @param array $skipPaths Array of regular expressions describing paths to be skipped */ - public function skipAll(array $skipPaths) { + public function skipAll(array $skipPaths): void + { $this->skipAllPaths = $skipPaths; } @@ -311,7 +312,8 @@ public function skipAll(array $skipPaths) { * @param array $skipPaths Array of regular expressions describing paths */ - public function skipConfig(array $skipPaths) { + public function skipConfig(array $skipPaths): void + { $this->skipConfigPaths = $skipPaths; } @@ -323,7 +325,8 @@ public function skipConfig(array $skipPaths) { * @param array $skipPaths Array of regular expressions describing paths */ - public function skipParents(array $skipPaths) { + public function skipParents(array $skipPaths): void + { $this->skipParentPaths = $skipPaths; } } \ No newline at end of file diff --git a/app/src/Lib/Traits/TabTrait.php b/app/src/Lib/Traits/TabTrait.php new file mode 100644 index 000000000..0144c11e3 --- /dev/null +++ b/app/src/Lib/Traits/TabTrait.php @@ -0,0 +1,59 @@ +tabsConfig; + } + + /** + * @param array $tabsConfig + * + * @return void + */ + public function setTabsConfig(array $tabsConfig): void + { + $this->tabsConfig = $tabsConfig; + } + +} \ No newline at end of file diff --git a/app/src/Model/Table/GroupMembersTable.php b/app/src/Model/Table/GroupMembersTable.php index b645aaf2a..5b8b61865 100644 --- a/app/src/Model/Table/GroupMembersTable.php +++ b/app/src/Model/Table/GroupMembersTable.php @@ -52,9 +52,10 @@ class GroupMembersTable extends Table { use \App\Lib\Traits\ProvisionableTrait; use \App\Lib\Traits\QueryModificationTrait; use \App\Lib\Traits\SearchFilterTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + /** * Perform Cake Model initialization. * @@ -131,6 +132,18 @@ public function initialize(array $config): void { 'order' => 1 ], ]); + + $this->setTabsConfig( + [ + 'tabs' => ['Groups', 'GroupMembers', 'GroupNestings'], + 'action' => [ + 'Groups' => ['edit', 'view'], + 'GroupMembers' => ['index'], + 'GroupNestings' => ['index'], + ], + 'counter' => ['GroupMembers'] + ] + ); } /** diff --git a/app/src/Model/Table/GroupNestingsTable.php b/app/src/Model/Table/GroupNestingsTable.php index 0fdda7cb2..6eb28c8c5 100644 --- a/app/src/Model/Table/GroupNestingsTable.php +++ b/app/src/Model/Table/GroupNestingsTable.php @@ -43,9 +43,10 @@ class GroupNestingsTable extends Table { use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\QueryModificationTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + /** * Perform Cake Model initialization. * @@ -96,6 +97,18 @@ public function initialize(array $config): void { ] ]); + $this->setTabsConfig( + [ + 'tabs' => ['Groups', 'GroupMembers', 'GroupNestings'], + 'action' => [ + 'Groups' => ['edit', 'view'], + 'GroupMembers' => ['index'], + 'GroupNestings' => ['index'], + ], + 'counter' => ['GroupMembers'] + ] + ); + // XXX Keeping for functionality reference // $this->setAutoViewVars([ // 'groupMembers' => [ diff --git a/app/src/Model/Table/GroupsTable.php b/app/src/Model/Table/GroupsTable.php index f42470ee1..4df6db0cd 100644 --- a/app/src/Model/Table/GroupsTable.php +++ b/app/src/Model/Table/GroupsTable.php @@ -52,10 +52,11 @@ class GroupsTable extends Table { use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\ProvisionableTrait; use \App\Lib\Traits\QueryModificationTrait; + use \App\Lib\Traits\SearchFilterTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - use \App\Lib\Traits\SearchFilterTrait; - + /** * Perform Cake Model initialization. * @@ -182,7 +183,25 @@ public function initialize(array $config): void { ] ] ]); - + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['Groups', 'GroupMembers', 'GroupNestings'], + // What actions will inlcude the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'Groups' => ['edit', 'view'], + 'GroupMembers' => ['index'], + 'GroupNestings' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['GroupMembers'] + ] + ); + $this->setPermissions([ // XXX update for couAdmins, etc // Actions that operate over an entity (ie: require an $id) diff --git a/app/templates/EnrollmentFlowSteps/fields-nav.inc b/app/templates/EnrollmentFlowSteps/fields-nav.inc index 7eebea083..cc0622828 100644 --- a/app/templates/EnrollmentFlowSteps/fields-nav.inc +++ b/app/templates/EnrollmentFlowSteps/fields-nav.inc @@ -26,6 +26,6 @@ */ $subnav = [ - 'name' => 'enrollment_flow', - 'active' => 'steps' -]; \ No newline at end of file + 'name' => 'plugin', + 'active' => 'properties' +]; diff --git a/app/templates/ExternalIdentitySources/retrieve.php b/app/templates/ExternalIdentitySources/retrieve.php index b7c0cca94..5c63aa59d 100644 --- a/app/templates/ExternalIdentitySources/retrieve.php +++ b/app/templates/ExternalIdentitySources/retrieve.php @@ -34,7 +34,7 @@ ]; // Generate the subnavigation title and tabs - print $this->element('subnavigation', $subnav); + print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); ?>
diff --git a/app/templates/ExternalIdentitySources/search.php b/app/templates/ExternalIdentitySources/search.php index 2f61e4e23..ad6c94708 100644 --- a/app/templates/ExternalIdentitySources/search.php +++ b/app/templates/ExternalIdentitySources/search.php @@ -28,11 +28,23 @@ // Include the plugin subnavigation $subnav = [ 'name' => 'plugin', - 'active' => 'search' + 'active' => 'search', + 'topLinks' => [ + [ + 'label' => __d('operation', 'ExternalIdentitySources.search'), + 'link' => [ + 'plugin' => null, + 'controller' => 'external-identity-sources', + 'action' => 'search', + $this->request->getParam('pass')[0] + ], + 'class' => '' + ] + ] ]; // Generate the subnavigation title and tabs - print $this->element('subnavigation', $subnav); + print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); ?>
diff --git a/app/templates/GroupMembers/columns.inc b/app/templates/GroupMembers/columns.inc index abd2f1c7f..512771713 100644 --- a/app/templates/GroupMembers/columns.inc +++ b/app/templates/GroupMembers/columns.inc @@ -91,11 +91,6 @@ $topLinks = [ $bulkActions = [BulkActionEnum::Delete]; -$subnav = [ - 'name' => 'group', - 'active' => 'members' -]; - // Page options $peoplePicker = [ 'label' => __d('operation','add.member'), diff --git a/app/templates/GroupNestings/columns.inc b/app/templates/GroupNestings/columns.inc index 8be567c4c..671f2b2f9 100644 --- a/app/templates/GroupNestings/columns.inc +++ b/app/templates/GroupNestings/columns.inc @@ -42,7 +42,3 @@ $bulkActions = [ ]; */ -$subnav = [ - 'name' => 'group', - 'active' => 'nestings' -]; \ No newline at end of file diff --git a/app/templates/GroupNestings/fields-nav.inc b/app/templates/GroupNestings/fields-nav.inc index 1374feaae..045ffafc4 100644 --- a/app/templates/GroupNestings/fields-nav.inc +++ b/app/templates/GroupNestings/fields-nav.inc @@ -27,8 +27,3 @@ // XXX: if CFM-218 (Make fields.inc configuration only) is accepted, move the contents of this file into fields.inc $topLinks = []; - -$subnav = [ - 'name' => 'group', - 'active' => 'nestings' -]; \ No newline at end of file diff --git a/app/templates/Groups/fields-nav.inc b/app/templates/Groups/fields-nav.inc index 12310d062..9067e576d 100644 --- a/app/templates/Groups/fields-nav.inc +++ b/app/templates/Groups/fields-nav.inc @@ -150,8 +150,3 @@ if(!$vv_obj->isOwners()) { ] ]; } - -$subnav = [ - 'name' => 'group', - 'active' => 'properties' -]; \ No newline at end of file diff --git a/app/templates/Standard/add-edit-view.php b/app/templates/Standard/add-edit-view.php index 6837d18cf..ae0cb5257 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -65,17 +65,22 @@ $flashArgs['vv_banners'] = $banners; } -// If subnavigation is present a supertitle and the subnavigation will be placed above -// the normal page title. The flash messages will be shown up there as well. -if(!empty($subnav)) { - // Include the $flashArgs for the subnavigation element - $subnav['flashArgs'] = $flashArgs; - if(!empty($topLinks) && ($modelsName == 'People' && $vv_action == 'edit')) { - // We are in Person canvas mode: pass along the top links for building the Actions menu. +// Subnavigation calculations +$fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; +$modelsTable = $this->Common->getModelTableReference($fullModelsName); +if(method_exists($modelsTable, 'getTabsConfig')) { + $tabsConfiguration = $modelsTable->getTabsConfig(); + $subnav = [ + 'flashArgs' => $flashArgs, + ...$tabsConfiguration + ]; + if(!empty($topLinks) && $vv_action == 'edit') { + // Pass along the toplink. These are useful for building the action menu and the plugin sub navigation $subnav['topLinks'] = $topLinks; } - // Generate the subnavigation title and tabs - print $this->element('subnavigation', $subnav); + if(in_array($vv_action, $tabsConfiguration['action'][$modelsName], true)) { + print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); + } } ?> diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index 74f342f49..259ee17b7 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -79,13 +79,18 @@ $flashArgs['vv_banners'] = $banners; } -// If subnavigation is present a supertitle and the subnavigation will be placed above -// the normal page title. The flash messages will be shown up there as well. -if(!empty($subnav)) { - // Include the $flashArgs for the subnavigation element - $subnav['flashArgs'] = $flashArgs; - // Generate the subnavigation title and tabs - print $this->element('subnavigation', $subnav); +// Subnavigation calculations +$fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; +$modelsTable = $this->Common->getModelTableReference($fullModelsName); +if(method_exists($modelsTable, 'getTabsConfig')) { + $tabsConfiguration = $modelsTable->getTabsConfig(); + $subnav = [ + 'flashArgs' => $flashArgs, + ...$tabsConfiguration + ]; + if(in_array($vv_action, $tabsConfiguration['action'][$modelsName], true)) { + print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); + } } ?> @@ -127,9 +132,6 @@ ]; // Check to see if the model names a specific layout - - $fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; - $modelsTable = $this->Common->getModelTableReference($fullModelsName); if(method_exists($modelsTable, 'getLayout')) { $action_configuration['class'] = 'cm-modal-link nospin'; // launch this in a modal $action_configuration['dataAttrs'] = [ diff --git a/app/templates/element/subnavigation.php b/app/templates/element/subnavigation.php deleted file mode 100644 index bb9c56206..000000000 --- a/app/templates/element/subnavigation.php +++ /dev/null @@ -1,489 +0,0 @@ -request->getParam('controller'); -$curAction = $this->request->getParam('action'); -$navController = $curController; - -if(!empty($vv_primary_link) && !empty($this->request->getQuery($vv_primary_link))) { - // This will work for most top-level index views - $curId = $this->request->getQuery($vv_primary_link); - $linkFilter = [$vv_primary_link => $curId]; - // For top-level nav - if(!empty($vv_person_id)) { - $curId = $vv_person_id; - $linkFilter = ['person_id' => $vv_person_id]; - } -} elseif (!empty($vv_obj)) { - // This will work for most top-level edit views - // XXX we might produce the $vv_primary_link for edit views so the approach below could be deprecated - // XXX $vv_primary_link_obj is the equivalent for plugins however - $curId = $vv_obj->id; - if(!empty($tabsId) && !empty($tabsController)) { - // these have been explicitly set in the $subnav array in fields-nav.inc, so just use them. - $curId = $tabsId; - $navController = $tabsController; - } elseif(!empty($vv_person_id)) { - $curId = $vv_person_id; - } elseif( - ($active == 'plugin' || (!empty($vv_primary_link) && $vv_primary_link == 'enrollment_flow_id')) - && !empty($vv_primary_link_obj) - && !empty($vv_primary_link_model) - ) { - $curId = $vv_primary_link_obj->id; - $navController = $vv_primary_link_model; - } elseif(!empty($vv_primary_link_id)) { - $curId = $vv_primary_link_id; - } - - // For top-level nav while in edit pages. - if ($name == 'person') { - $linkFilter = ['person_id' => $curId]; - } elseif ($name == 'group') { - $linkFilter = ['group_id' => $curId]; - } elseif ($name == 'enrollment_flow') { - $linkFilter = ['enrollment_flow_id' => $curId]; - } -} elseif(!empty($vv_bc_title_links)) { - // All else fails? Use the breadcrumb which has figured this out. - // XXX We might just be able to do this and skip all the above after breadcrumbs have been refactored - $curId = end($vv_bc_title_links[0]['target']); -} - -if(!empty($vv_obj)) { - // Set the badge style for Person Status - $statusBadgeClass = 'bg-warning'; - if($vv_obj['status'] == 'A') { - $statusBadgeClass = 'bg-outline-secondary primary'; - } elseif (in_array($vv_obj['status'], ['D','N','S','X','XP'])) { - $statusBadgeClass = 'bg-danger'; - } -} - -$supertitle = __d('information','global.title.none'); -if(!empty($tabsSupertitle)) { - // this has been explicitly set in the $subnav array in fields-nav.inc, so just use it. - $supertitle = $tabsSupertitle; -} elseif($active == 'plugin' && !empty($vv_bc_parent_obj)) { - $supertitle = $vv_bc_parent_obj->$vv_bc_parent_displayfield; -} elseif(!empty($vv_person_name)) { - $supertitle = $vv_person_name->full_name; -} elseif(!empty($vv_supertitle)) { - $supertitle = $vv_supertitle; -} elseif(!empty($vv_obj)) { - $supertitle = $vv_obj->$vv_display_field; -} elseif(!empty($vv_bc_parent_obj)) { - $supertitle = $vv_bc_parent_obj->$vv_bc_parent_displayfield; -} elseif(!empty($vv_primary_link_obj)) { - $supertitle = $vv_primary_link_obj->name; -} elseif(!empty($vv_bc_title_links)) { - // All else fails? Use the breadcrumb which has figured this out. - // XXX We might just be able to do this and skip all the above after breadcrumbs have been refactored - $supertitle = $vv_bc_title_links[0]['label']; -} -?> - - diff --git a/app/templates/element/subnavigation/enrollmentFlow.php b/app/templates/element/subnavigation/enrollmentFlow.php new file mode 100644 index 000000000..1caa46f50 --- /dev/null +++ b/app/templates/element/subnavigation/enrollmentFlow.php @@ -0,0 +1,76 @@ +request->getParam('action'); +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +$curId = $vv_person_id ?? $this->request->getQuery($vv_primary_link) ?? $vv_obj->id ?? end($vv_bc_title_links[0]['target']); +// For top-level nav while in edit pages. +$linkFilter = match ($vv_subnavigation_name) { + 'person' => ['person_id' => $curId], + 'group' => ['group_id' => $curId], + 'enrollment_flow' => ['enrollment_flow_id' => $curId], + default => [$vv_primary_link => $curId] +}; + +?> + + + \ No newline at end of file diff --git a/app/templates/element/subnavigation/person.php b/app/templates/element/subnavigation/person.php new file mode 100644 index 000000000..d5b25078a --- /dev/null +++ b/app/templates/element/subnavigation/person.php @@ -0,0 +1,107 @@ +request->getParam('controller'); +$curAction = $this->request->getParam('action'); +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +$isExternalId = ($vv_primary_link == 'external_identity_id' + || $vv_primary_link == 'external_identity_role_id' + || $vv_primary_link == 'external_identity_source_id' + || ($curController == 'ExternalIdentities' && ($curAction === 'edit' || $curAction === 'view')) + || ($curController == 'ExternalIdentityRoles' && ($curAction === 'edit' || $curAction === 'view'))); +$isPersonRole = ($vv_primary_link === 'person_role_id' + || ($curController === 'PersonRoles' && ($curAction === 'edit' || $curAction === 'view'))); +// Determine active tab. This logic is necessitated by MVEAs (that are displayed in multiple contexts). +$isCanvasTab = ($vv_subnavigation_active === 'canvas'); +$isPersonTab = ($vv_subnavigation_active == 'person' && !$isPersonRole && !$isExternalId); +$isRolesTab = ($vv_subnavigation_active == 'person_roles' || ($isPersonRole && !$isExternalId)); +$isExternalIdentitiesTab = ($vv_subnavigation_active === 'external_identities' || $isExternalId); +$isGroupsTab = $vv_subnavigation_active == 'groups'; + +$vv_subnavigation_flashArgs = $vv_subnavigation_flashArgs ?? []; +$curController = $this->request->getParam('controller'); +$navController = $curController; + +$curId = $vv_person_id + ?? $this->request->getQuery($vv_primary_link) + ?? $vv_obj->id + ?? end($vv_bc_title_links[0]['target']); + +// For top-level nav while in edit pages. +$linkFilter = match ($vv_subnavigation_name) { + 'person' => ['person_id' => $curId], + 'group' => ['group_id' => $curId], + 'enrollment_flow' => ['enrollment_flow_id' => $curId], + default => [$vv_primary_link => $curId] +}; + +?> + + + + \ No newline at end of file diff --git a/app/templates/element/subnavigation/plugin.php b/app/templates/element/subnavigation/plugin.php new file mode 100644 index 000000000..e855ad10f --- /dev/null +++ b/app/templates/element/subnavigation/plugin.php @@ -0,0 +1,109 @@ +request->getParam('controller'); +$curAction = $this->request->getParam('action'); +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +$navController = $curController; +$curId = $this->request->getQuery($vv_primary_link) ?? $vv_obj->id ?? end($vv_bc_title_links[0]['target']); +if( + $vv_subnavigation_active == 'plugin' + && !empty($vv_primary_link_obj) + && !empty($vv_primary_link_model) +) { + $curId = $vv_primary_link_obj->id; + $navController = $vv_primary_link_model; +} elseif(!empty($vv_primary_link_id)) { + $curId = $vv_primary_link_id; +} + +// these have been explicitly set in the $subnav array in fields-nav.inc, so just use them. +if(!empty($vv_subnavigation_tabsId) && !empty($vv_subnavigation_tabsController)) { + $curId = $vv_subnavigation_tabsId; + $navController = $vv_subnavigation_tabsController; +} + +?> + + + + + + 'nav-link active', + default => 'nav-link' + }; + + $navUrl = '/' . \Cake\Utility\Inflector::dasherize($navController) . '/search/' . $curId; + $tabElement = $this->Html->link( + $tabLink['label'], + $tabLink['link'], + ['class' => $linkClass] + ); + ?> + + + diff --git a/app/templates/element/subnavigation/statusBadge.php b/app/templates/element/subnavigation/statusBadge.php new file mode 100644 index 000000000..43e0b310a --- /dev/null +++ b/app/templates/element/subnavigation/statusBadge.php @@ -0,0 +1,48 @@ +request->getParam('controller'); + +if(empty($vv_obj['status']) || $curController !== 'People') { + return; +} + +$statusBadgeClass = match ($vv_obj['status']) { + 'A' => 'bg-outline-secondary primary', + 'D','N','S','X','XP' => 'bg-danger', + default => 'bg-warning' +}; + +?> + + + + diff --git a/app/templates/element/subnavigation/subNavigationChild.php b/app/templates/element/subnavigation/subNavigationChild.php new file mode 100644 index 000000000..318262eb2 --- /dev/null +++ b/app/templates/element/subnavigation/subNavigationChild.php @@ -0,0 +1,111 @@ +request->getParam('controller'); +$curAction = $this->request->getParam('action'); +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +$isExternalId = ($vv_primary_link == 'external_identity_id' + || $vv_primary_link == 'external_identity_role_id' + || $vv_primary_link == 'external_identity_source_id' + || ($curController == 'ExternalIdentities' && ($curAction == 'edit' || $curAction == 'view')) + || ($curController == 'ExternalIdentityRoles' && ($curAction == 'edit' || $curAction == 'view'))); + +if(!$isExternalId) { + return; +} + +$isExternalIdRole = ($vv_primary_link === 'external_identity_role_id' || ($curController === 'ExternalIdentityRoles' && ($curAction === 'edit' || $curAction === 'view'))); + +$curId = $vv_person_id ?? $this->request->getQuery($vv_primary_link) ?? $vv_obj->id ?? end($vv_bc_title_links[0]['target']); +// For top-level nav while in edit pages. +$linkFilter = match ($vv_subnavigation_name) { + 'person' => ['person_id' => $curId], + 'group' => ['group_id' => $curId], + 'enrollment_flow' => ['enrollment_flow_id' => $curId], + default => [$vv_primary_link => $curId] +}; + +// Second Level Subnavigation Links +$parentId = $curId; +$curId = $this->request->getQuery($vv_primary_link); +if(!empty($vv_subnavigation_subTabsId)) { + $curId = $vv_subnavigation_subTabsId; +} elseif(!empty($vv_ei_id)) { + $curId = $vv_ei_id; + $linkFilter = ['external_identity_id' => $curId]; +} elseif(!empty($vv_obj)) { + $curId = $vv_obj->id; + $linkFilter = ['external_identity_id' => $curId]; +} + +?> + + + + \ No newline at end of file diff --git a/app/templates/element/subnavigation/subNavigationRoot.php b/app/templates/element/subnavigation/subNavigationRoot.php new file mode 100644 index 000000000..3f8f6712b --- /dev/null +++ b/app/templates/element/subnavigation/subNavigationRoot.php @@ -0,0 +1,80 @@ +set('vv_sub_nav_attributes', $subNavAttributes); +extract($subNavAttributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +// $this->name = Models +$modelsName = $this->name; +$this->set('vv_subnavigation_modelsName', $modelsName); +// $tablename = models +$tableName = \Cake\Utility\Inflector::tableize(\Cake\Utility\Inflector::singularize($this->name)); +$this->set('vv_subnavigation_tableName', $tableName); +?> + + diff --git a/app/templates/element/subnavigation/supertitle.php b/app/templates/element/subnavigation/supertitle.php new file mode 100644 index 000000000..f138cb812 --- /dev/null +++ b/app/templates/element/subnavigation/supertitle.php @@ -0,0 +1,46 @@ + $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') +}; + +?> + +

diff --git a/app/templates/element/subnavigation/tabList.php b/app/templates/element/subnavigation/tabList.php new file mode 100644 index 000000000..7e144d3ac --- /dev/null +++ b/app/templates/element/subnavigation/tabList.php @@ -0,0 +1,89 @@ +request->getParam('action'); +$curController = $this->request->getParam('controller'); +$entityName = \Cake\Utility\Inflector::singularize($vv_subnavigation_tableName); + +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +$curId = $vv_person_id + ?? $this->request->getQuery($vv_primary_link) + ?? $vv_obj->id + ?? end($vv_bc_title_links[0]['target']); + +// For top-level nav while in edit pages. +$linkFilter = match ($entityName) { + 'person' => ['person_id' => $curId], + 'group' => ['group_id' => $curId], + 'enrollment_flow' => ['enrollment_flow_id' => $curId], + default => [$vv_primary_link => $curId] +}; +?> + + + + \ No newline at end of file diff --git a/app/templates/element/tabs/tabTitleWithCount.php b/app/templates/element/subnavigation/tabTitle.php similarity index 89% rename from app/templates/element/tabs/tabTitleWithCount.php rename to app/templates/element/subnavigation/tabTitle.php index 07845fa6c..94fbbe6bb 100644 --- a/app/templates/element/tabs/tabTitleWithCount.php +++ b/app/templates/element/subnavigation/tabTitle.php @@ -27,13 +27,17 @@ * */ -if(empty($num)) { +if(!isset($num) + && !empty($model) + && !empty($where)) { $num = $this->Common->getModelTotalCount($model, $where); } ?> + + \ No newline at end of file From 474a190097e885fd6a6ee35990e50e93d52eb0af Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 25 Sep 2024 13:58:37 +0300 Subject: [PATCH 02/16] enrollment flows tabs --- .../Model/Table/EnrollmentFlowStepsTable.php | 21 ++++- app/src/Model/Table/EnrollmentFlowsTable.php | 21 ++++- app/src/Model/Table/PetitionsTable.php | 21 ++++- .../element/subnavigation/enrollmentFlow.php | 76 ------------------- 4 files changed, 60 insertions(+), 79 deletions(-) delete mode 100644 app/templates/element/subnavigation/enrollmentFlow.php diff --git a/app/src/Model/Table/EnrollmentFlowStepsTable.php b/app/src/Model/Table/EnrollmentFlowStepsTable.php index 8071e772b..dbfc3df03 100644 --- a/app/src/Model/Table/EnrollmentFlowStepsTable.php +++ b/app/src/Model/Table/EnrollmentFlowStepsTable.php @@ -44,9 +44,10 @@ class EnrollmentFlowStepsTable extends Table { use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PluggableModelTrait; use \App\Lib\Traits\PrimaryLinkTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + /** * Perform Cake Model initialization. * @@ -110,6 +111,24 @@ public function initialize(array $config): void { ] ] ]); + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['EnrollmentFlows', 'EnrollmentFlowSteps', 'Petitions'], + // What actions will inlcude the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'EnrollmentFlows' => ['edit', 'view'], + 'EnrollmentFlowSteps' => ['index'], + 'Petitions' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['EnrollmentFlowSteps', 'Petitions'] + ] + ); } /** diff --git a/app/src/Model/Table/EnrollmentFlowsTable.php b/app/src/Model/Table/EnrollmentFlowsTable.php index 1ef0e2a64..58cc0c743 100644 --- a/app/src/Model/Table/EnrollmentFlowsTable.php +++ b/app/src/Model/Table/EnrollmentFlowsTable.php @@ -47,9 +47,10 @@ class EnrollmentFlowsTable extends Table { use \App\Lib\Traits\CoLinkTrait; use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PrimaryLinkTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + /** * Perform Cake Model initialization. * @@ -121,6 +122,24 @@ public function initialize(array $config): void { ] ] ]); + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['EnrollmentFlows', 'EnrollmentFlowSteps', 'Petitions'], + // What actions will inlcude the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'EnrollmentFlows' => ['edit', 'view'], + 'EnrollmentFlowSteps' => ['index'], + 'Petitions' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['EnrollmentFlowSteps', 'Petitions'] + ] + ); } /** diff --git a/app/src/Model/Table/PetitionsTable.php b/app/src/Model/Table/PetitionsTable.php index becb95aea..e8dff786a 100644 --- a/app/src/Model/Table/PetitionsTable.php +++ b/app/src/Model/Table/PetitionsTable.php @@ -49,9 +49,10 @@ class PetitionsTable extends Table { use \App\Lib\Traits\PermissionsTrait; use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\QueryModificationTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + /** * Perform Cake Model initialization. * @@ -138,6 +139,24 @@ public function initialize(array $config): void { 'index' => ['platformAdmin', 'coAdmin'] ] ]); + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['EnrollmentFlows', 'EnrollmentFlowSteps', 'Petitions'], + // What actions will inlcude the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'EnrollmentFlows' => ['edit', 'view'], + 'EnrollmentFlowSteps' => ['index'], + 'Petitions' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['EnrollmentFlowSteps', 'Petitions'] + ] + ); } /** diff --git a/app/templates/element/subnavigation/enrollmentFlow.php b/app/templates/element/subnavigation/enrollmentFlow.php deleted file mode 100644 index 1caa46f50..000000000 --- a/app/templates/element/subnavigation/enrollmentFlow.php +++ /dev/null @@ -1,76 +0,0 @@ -request->getParam('action'); -extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); - -$curId = $vv_person_id ?? $this->request->getQuery($vv_primary_link) ?? $vv_obj->id ?? end($vv_bc_title_links[0]['target']); -// For top-level nav while in edit pages. -$linkFilter = match ($vv_subnavigation_name) { - 'person' => ['person_id' => $curId], - 'group' => ['group_id' => $curId], - 'enrollment_flow' => ['enrollment_flow_id' => $curId], - default => [$vv_primary_link => $curId] -}; - -?> - - - \ No newline at end of file From f349f86ee9f20e6a1fadc4f1455e066bd0fab1a4 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 25 Sep 2024 17:55:47 +0300 Subject: [PATCH 03/16] Person to tabs --- .../Table/ExtIdentitySourceRecordsTable.php | 34 ++++++ .../Model/Table/ExternalIdentitiesTable.php | 38 +++++- .../Table/ExternalIdentityRolesTable.php | 38 +++++- app/src/Model/Table/PeopleTable.php | 23 +++- app/src/Model/Table/PersonRolesTable.php | 23 +++- .../{CommonHelper.php => TabHelper.php} | 34 +++++- .../ExternalIdentitySources/retrieve.php | 2 +- .../ExternalIdentitySources/search.php | 2 +- app/templates/Standard/add-edit-view.php | 16 ++- app/templates/Standard/index.php | 16 ++- .../element/subnavigation/inlineList.php | 86 ++++++++++++++ .../element/subnavigation/person.php | 107 ----------------- .../subnavigation/subNavigationChild.php | 111 ------------------ .../subnavigation/subNavigationRoot.php | 80 ------------- .../element/subnavigation/tabList.php | 33 ++++-- .../element/subnavigation/tabTitle.php | 2 +- 16 files changed, 316 insertions(+), 329 deletions(-) rename app/src/View/Helper/{CommonHelper.php => TabHelper.php} (62%) create mode 100644 app/templates/element/subnavigation/inlineList.php delete mode 100644 app/templates/element/subnavigation/person.php delete mode 100644 app/templates/element/subnavigation/subNavigationChild.php delete mode 100644 app/templates/element/subnavigation/subNavigationRoot.php diff --git a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php index e4db956b9..f390ea552 100644 --- a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php +++ b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php @@ -50,6 +50,7 @@ class ExtIdentitySourceRecordsTable extends Table { use \App\Lib\Traits\SearchFilterTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; + use \App\Lib\Traits\TabTrait; /** * Perform Cake Model initialization. @@ -102,6 +103,39 @@ public function initialize(array $config): void { ] ]);*/ + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['People', 'PersonRoles', 'ExternalIdentities'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'People' => ['edit', 'view'], + 'PersonRoles' => ['index'], + 'ExternalIdentities' => ['index'], + ], + // What model will have a counter-badge after the tab title +// 'counter' => ['PersonRoles', 'ExternalIdentities'], + 'nested' => [ + // Ordered list of Tabs + 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'ExternalIdentities' => ['edit', 'view'], + 'ExternalIdentityRoles' => ['index'], + 'ExtIdentitySourceRecords' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + ] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) 'entity' => [ diff --git a/app/src/Model/Table/ExternalIdentitiesTable.php b/app/src/Model/Table/ExternalIdentitiesTable.php index 9f945a039..4fb34d0c3 100644 --- a/app/src/Model/Table/ExternalIdentitiesTable.php +++ b/app/src/Model/Table/ExternalIdentitiesTable.php @@ -47,7 +47,8 @@ class ExternalIdentitiesTable extends Table { use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; use \App\Lib\Traits\SearchFilterTrait; - + use \App\Lib\Traits\TabTrait; + /** * Perform Cake Model initialization. * @@ -147,7 +148,40 @@ public function initialize(array $config): void { 'class' => 'StatusEnum' ] ]); - + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['People', 'PersonRoles', 'ExternalIdentities'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'People' => ['edit', 'view'], + 'PersonRoles' => ['index'], + 'ExternalIdentities' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['PersonRoles', 'ExternalIdentities'], + 'nested' => [ + // Ordered list of Tabs + 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'ExternalIdentities' => ['edit', 'view'], + 'ExternalIdentityRoles' => ['index'], + 'ExtIdentitySourceRecords' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + ] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) // See also CFM-126 diff --git a/app/src/Model/Table/ExternalIdentityRolesTable.php b/app/src/Model/Table/ExternalIdentityRolesTable.php index 35a267ba1..b0e0b1611 100644 --- a/app/src/Model/Table/ExternalIdentityRolesTable.php +++ b/app/src/Model/Table/ExternalIdentityRolesTable.php @@ -49,7 +49,8 @@ class ExternalIdentityRolesTable extends Table { use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; use \App\Lib\Traits\SearchFilterTrait; - + use \App\Lib\Traits\TabTrait; + /** * Perform Cake Model initialization. * @@ -118,7 +119,40 @@ public function initialize(array $config): void { 'attribute' => 'PersonRoles.affiliation_type' ] ]); - + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['People', 'PersonRoles', 'ExternalIdentities'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'People' => ['edit', 'view'], + 'PersonRoles' => ['index'], + 'ExternalIdentities' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['PersonRoles', 'ExternalIdentities'], + 'nested' => [ + // Ordered list of Tabs + 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'ExternalIdentities' => ['edit', 'view'], + 'ExternalIdentityRoles' => ['index'], + 'ExtIdentitySourceRecords' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + ] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) // See also CFM-126 diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 9759511c4..e829ecd0b 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -50,9 +50,10 @@ class PeopleTable extends Table { use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\ProvisionableTrait; use \App\Lib\Traits\QueryModificationTrait; + use \App\Lib\Traits\SearchFilterTrait; + use \App\Lib\Traits\TabTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - use \App\Lib\Traits\SearchFilterTrait; /** * Perform Cake Model initialization. @@ -225,7 +226,25 @@ public function initialize(array $config): void { 'order' => 99 ] ]); - + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['People', 'PersonRoles', 'ExternalIdentities'], + // What actions will inlcude the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'People' => ['edit', 'view'], + 'PersonRoles' => ['index'], + 'ExternalIdentities' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['PersonRoles', 'ExternalIdentities'] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) // See also CFM-126 diff --git a/app/src/Model/Table/PersonRolesTable.php b/app/src/Model/Table/PersonRolesTable.php index ac797fee5..69e481101 100644 --- a/app/src/Model/Table/PersonRolesTable.php +++ b/app/src/Model/Table/PersonRolesTable.php @@ -53,7 +53,8 @@ class PersonRolesTable extends Table { use \App\Lib\Traits\TypeTrait; use \App\Lib\Traits\ValidationTrait; use \App\Lib\Traits\SearchFilterTrait; - + use \App\Lib\Traits\TabTrait; + // Default "out of the box" types for this model. Entries here should be // given a default localization in app/resources/locales/*/defaultType.po protected $defaultTypes = [ @@ -163,7 +164,25 @@ public function initialize(array $config): void { 'model' => 'Cous' ] ]); - + + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['People', 'PersonRoles', 'ExternalIdentities'], + // What actions will inlcude the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'People' => ['edit', 'view'], + 'PersonRoles' => ['index'], + 'ExternalIdentities' => ['index'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['PersonRoles', 'ExternalIdentities'] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) // See also CFM-126 diff --git a/app/src/View/Helper/CommonHelper.php b/app/src/View/Helper/TabHelper.php similarity index 62% rename from app/src/View/Helper/CommonHelper.php rename to app/src/View/Helper/TabHelper.php index e19e3dd10..79713c98d 100644 --- a/app/src/View/Helper/CommonHelper.php +++ b/app/src/View/Helper/TabHelper.php @@ -1,6 +1,6 @@ getView()->getPlugin(); + $mainTabList = $this->getView()->get('vv_sub_nav_attributes')['tabs']; + + $fullModelsName = !empty($plugin) ? $plugin . '.' . $tab : $tab; + $modelsTable = TableRegistry::getTableLocator()->get($fullModelsName); + $primary_link = collection($modelsTable->getPrimaryLinks()) + ->filter(function($link) use ($mainTabList) { + $linkToClass = StringUtilities::foreignKeyToClassName($link); + return \in_array($linkToClass, $mainTabList, true); + })->first(); + $foreignKey = StringUtilities::classNameToForeignKey($tab); + + // 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. If not, we will get the primary link directly. + return ($primary_link === 'co_id') + ? [$foreignKey => $curId] + : [$primary_link => $curId]; + } } \ No newline at end of file diff --git a/app/templates/ExternalIdentitySources/retrieve.php b/app/templates/ExternalIdentitySources/retrieve.php index 5c63aa59d..eca31a149 100644 --- a/app/templates/ExternalIdentitySources/retrieve.php +++ b/app/templates/ExternalIdentitySources/retrieve.php @@ -34,7 +34,7 @@ ]; // Generate the subnavigation title and tabs - print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); + print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); ?>
diff --git a/app/templates/ExternalIdentitySources/search.php b/app/templates/ExternalIdentitySources/search.php index ad6c94708..53228f2e1 100644 --- a/app/templates/ExternalIdentitySources/search.php +++ b/app/templates/ExternalIdentitySources/search.php @@ -44,7 +44,7 @@ ]; // Generate the subnavigation title and tabs - print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); + print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); ?>
diff --git a/app/templates/Standard/add-edit-view.php b/app/templates/Standard/add-edit-view.php index ae0cb5257..4a8cfff29 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -67,7 +67,7 @@ // Subnavigation calculations $fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; -$modelsTable = $this->Common->getModelTableReference($fullModelsName); +$modelsTable = $this->Tab->getModelTableReference($fullModelsName); if(method_exists($modelsTable, 'getTabsConfig')) { $tabsConfiguration = $modelsTable->getTabsConfig(); $subnav = [ @@ -78,8 +78,18 @@ // Pass along the toplink. These are useful for building the action menu and the plugin sub navigation $subnav['topLinks'] = $topLinks; } - if(in_array($vv_action, $tabsConfiguration['action'][$modelsName], true)) { - print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); + if ( + ( // Simple use case + isset($tabsConfiguration['action'][$modelsName]) + && in_array($vv_action, $tabsConfiguration['action'][$modelsName], true) + ) + || + ( // Use case with two levels + isset($tabsConfiguration['nested']['action'][$modelsName]) + && in_array($vv_action, $tabsConfiguration['nested']['action'][$modelsName], true) + ) + ) { + print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); } } ?> diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index 259ee17b7..7f475f56a 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -81,15 +81,25 @@ // Subnavigation calculations $fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; -$modelsTable = $this->Common->getModelTableReference($fullModelsName); +$modelsTable = $this->Tab->getModelTableReference($fullModelsName); if(method_exists($modelsTable, 'getTabsConfig')) { $tabsConfiguration = $modelsTable->getTabsConfig(); $subnav = [ 'flashArgs' => $flashArgs, ...$tabsConfiguration ]; - if(in_array($vv_action, $tabsConfiguration['action'][$modelsName], true)) { - print $this->element('subnavigation/subNavigationRoot', ['subNavAttributes' => $subnav]); + if ( + ( // Simple use case + isset($tabsConfiguration['action'][$modelsName]) + && in_array($vv_action, $tabsConfiguration['action'][$modelsName], true) + ) + || + ( // Use case with two levels + isset($tabsConfiguration['nested']['action'][$modelsName]) + && in_array($vv_action, $tabsConfiguration['nested']['action'][$modelsName], true) + ) + ) { + print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); } } ?> diff --git a/app/templates/element/subnavigation/inlineList.php b/app/templates/element/subnavigation/inlineList.php new file mode 100644 index 000000000..2325d8c59 --- /dev/null +++ b/app/templates/element/subnavigation/inlineList.php @@ -0,0 +1,86 @@ +request->getParam('action'); +$curController = $this->request->getParam('controller'); +$entityName = Inflector::singularize($vv_subnavigation_tableName); +$foreignKey = Inflector::underscore($entityName) . '_id'; + +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']); + +$primaryLinkClass = Inflector::camelize(Inflector::pluralize(substr($vv_primary_link, 0, strlen($vv_primary_link)-3))); + +?> + + +
  • + __d('controller', $tabLanguageKey, [99])]; + + $linkFilter = $this->Tab->getLinkFilter($inlineTab, $curId); + $tabToTableName = Inflector::tableize(Inflector::singularize($inlineTab)); + + // Insert Counter Badge if applicable + if(isset($vv_subnavigation_nested['counter']) + && in_array($inlineTab, $vv_subnavigation_nested['counter'], true) + ) { + $tabTitleOptions['model'] = $tabToTableName; + $tabTitleOptions['where'] = $linkFilter; + } + + // Construct Element Title + $title = $this->element('subnavigation/tabTitle', $tabTitleOptions); + + // Construct Target URL + // todo: we do not take into account readonly flag. + $url = [ 'controller' => $inlineTab, 'action' => $vv_subnavigation_nested['action'][$inlineTab][0]]; + if(in_array('index', $vv_subnavigation_nested['action'][$inlineTab], true)) { + $url['?'] = $linkFilter; + } else { + $url[] = $curId; + } + + // Calculate Tab Style Class(es) + $linkClass = ($inlineTab === $curController) ? 'nav-link active' : 'nav-link'; + + // Import element in the DOM + print $this->Html->link($title, $url, ['class' => $linkClass, 'escape' => false]); + ?> +
  • + \ No newline at end of file diff --git a/app/templates/element/subnavigation/person.php b/app/templates/element/subnavigation/person.php deleted file mode 100644 index d5b25078a..000000000 --- a/app/templates/element/subnavigation/person.php +++ /dev/null @@ -1,107 +0,0 @@ -request->getParam('controller'); -$curAction = $this->request->getParam('action'); -extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); - -$isExternalId = ($vv_primary_link == 'external_identity_id' - || $vv_primary_link == 'external_identity_role_id' - || $vv_primary_link == 'external_identity_source_id' - || ($curController == 'ExternalIdentities' && ($curAction === 'edit' || $curAction === 'view')) - || ($curController == 'ExternalIdentityRoles' && ($curAction === 'edit' || $curAction === 'view'))); -$isPersonRole = ($vv_primary_link === 'person_role_id' - || ($curController === 'PersonRoles' && ($curAction === 'edit' || $curAction === 'view'))); -// Determine active tab. This logic is necessitated by MVEAs (that are displayed in multiple contexts). -$isCanvasTab = ($vv_subnavigation_active === 'canvas'); -$isPersonTab = ($vv_subnavigation_active == 'person' && !$isPersonRole && !$isExternalId); -$isRolesTab = ($vv_subnavigation_active == 'person_roles' || ($isPersonRole && !$isExternalId)); -$isExternalIdentitiesTab = ($vv_subnavigation_active === 'external_identities' || $isExternalId); -$isGroupsTab = $vv_subnavigation_active == 'groups'; - -$vv_subnavigation_flashArgs = $vv_subnavigation_flashArgs ?? []; -$curController = $this->request->getParam('controller'); -$navController = $curController; - -$curId = $vv_person_id - ?? $this->request->getQuery($vv_primary_link) - ?? $vv_obj->id - ?? end($vv_bc_title_links[0]['target']); - -// For top-level nav while in edit pages. -$linkFilter = match ($vv_subnavigation_name) { - 'person' => ['person_id' => $curId], - 'group' => ['group_id' => $curId], - 'enrollment_flow' => ['enrollment_flow_id' => $curId], - default => [$vv_primary_link => $curId] -}; - -?> - - - - \ No newline at end of file diff --git a/app/templates/element/subnavigation/subNavigationChild.php b/app/templates/element/subnavigation/subNavigationChild.php deleted file mode 100644 index 318262eb2..000000000 --- a/app/templates/element/subnavigation/subNavigationChild.php +++ /dev/null @@ -1,111 +0,0 @@ -request->getParam('controller'); -$curAction = $this->request->getParam('action'); -extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); - -$isExternalId = ($vv_primary_link == 'external_identity_id' - || $vv_primary_link == 'external_identity_role_id' - || $vv_primary_link == 'external_identity_source_id' - || ($curController == 'ExternalIdentities' && ($curAction == 'edit' || $curAction == 'view')) - || ($curController == 'ExternalIdentityRoles' && ($curAction == 'edit' || $curAction == 'view'))); - -if(!$isExternalId) { - return; -} - -$isExternalIdRole = ($vv_primary_link === 'external_identity_role_id' || ($curController === 'ExternalIdentityRoles' && ($curAction === 'edit' || $curAction === 'view'))); - -$curId = $vv_person_id ?? $this->request->getQuery($vv_primary_link) ?? $vv_obj->id ?? end($vv_bc_title_links[0]['target']); -// For top-level nav while in edit pages. -$linkFilter = match ($vv_subnavigation_name) { - 'person' => ['person_id' => $curId], - 'group' => ['group_id' => $curId], - 'enrollment_flow' => ['enrollment_flow_id' => $curId], - default => [$vv_primary_link => $curId] -}; - -// Second Level Subnavigation Links -$parentId = $curId; -$curId = $this->request->getQuery($vv_primary_link); -if(!empty($vv_subnavigation_subTabsId)) { - $curId = $vv_subnavigation_subTabsId; -} elseif(!empty($vv_ei_id)) { - $curId = $vv_ei_id; - $linkFilter = ['external_identity_id' => $curId]; -} elseif(!empty($vv_obj)) { - $curId = $vv_obj->id; - $linkFilter = ['external_identity_id' => $curId]; -} - -?> - - - - \ No newline at end of file diff --git a/app/templates/element/subnavigation/subNavigationRoot.php b/app/templates/element/subnavigation/subNavigationRoot.php deleted file mode 100644 index 3f8f6712b..000000000 --- a/app/templates/element/subnavigation/subNavigationRoot.php +++ /dev/null @@ -1,80 +0,0 @@ -set('vv_sub_nav_attributes', $subNavAttributes); -extract($subNavAttributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); - -// $this->name = Models -$modelsName = $this->name; -$this->set('vv_subnavigation_modelsName', $modelsName); -// $tablename = models -$tableName = \Cake\Utility\Inflector::tableize(\Cake\Utility\Inflector::singularize($this->name)); -$this->set('vv_subnavigation_tableName', $tableName); -?> - - diff --git a/app/templates/element/subnavigation/tabList.php b/app/templates/element/subnavigation/tabList.php index 7e144d3ac..3e0751d5e 100644 --- a/app/templates/element/subnavigation/tabList.php +++ b/app/templates/element/subnavigation/tabList.php @@ -25,12 +25,13 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ +use \Cake\Utility\Inflector; declare(strict_types = 1); $curAction = $this->request->getParam('action'); $curController = $this->request->getParam('controller'); -$entityName = \Cake\Utility\Inflector::singularize($vv_subnavigation_tableName); +$entityName = Inflector::singularize($vv_subnavigation_tableName); extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); @@ -39,13 +40,6 @@ ?? $vv_obj->id ?? end($vv_bc_title_links[0]['target']); -// For top-level nav while in edit pages. -$linkFilter = match ($entityName) { - 'person' => ['person_id' => $curId], - 'group' => ['group_id' => $curId], - 'enrollment_flow' => ['enrollment_flow_id' => $curId], - default => [$vv_primary_link => $curId] -}; ?> @@ -53,14 +47,17 @@ __d($tabLanguageResource, $tab, [99])]; + $tabLanguageKey = in_array('index', $vv_subnavigation_action[$tab], true) ? $tab : 'Properties'; + $tabTitleOptions = ['title' => __d('controller', $tabLanguageKey, [99])]; + + $linkFilter = $this->Tab->getLinkFilter($tab, $curId); + $tabToTableName = Inflector::tableize(Inflector::singularize($tab)); // Insert Counter Badge if applicable if(isset($vv_subnavigation_counter) && in_array($tab, $vv_subnavigation_counter, true) ) { - $tabTitleOptions['model'] = \Cake\Utility\Inflector::tableize(\Cake\Utility\Inflector::singularize($tab)); + $tabTitleOptions['model'] = $tabToTableName; $tabTitleOptions['where'] = $linkFilter; } @@ -80,7 +77,19 @@ } // Calculate Tab Style Class(es) - $linkClass = ($tab === $curController) ? 'nav-link active' : 'nav-link'; + $linkClass = 'nav-link'; + if( + // First level + $tab === $curController + || + ( + // Second level active + isset($vv_subnavigation_nested['tabs']) + && in_array($tab, $vv_subnavigation_nested['tabs'], true) + ) + ) { + $linkClass = 'nav-link active'; + } // Import element in the DOM print $this->Html->link($title, $url, ['class' => $linkClass, 'escape' => false]); diff --git a/app/templates/element/subnavigation/tabTitle.php b/app/templates/element/subnavigation/tabTitle.php index 94fbbe6bb..7bfabc862 100644 --- a/app/templates/element/subnavigation/tabTitle.php +++ b/app/templates/element/subnavigation/tabTitle.php @@ -30,7 +30,7 @@ if(!isset($num) && !empty($model) && !empty($where)) { - $num = $this->Common->getModelTotalCount($model, $where); + $num = $this->Tab->getModelTotalCount($model, $where); } ?> From 447628c85639376d12e0a8e29b7aacfb5b8acfff Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 25 Sep 2024 18:00:16 +0300 Subject: [PATCH 04/16] Missing tabs code --- app/templates/element/subnavigation/tabList.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/element/subnavigation/tabList.php b/app/templates/element/subnavigation/tabList.php index 3e0751d5e..68aabe8c9 100644 --- a/app/templates/element/subnavigation/tabList.php +++ b/app/templates/element/subnavigation/tabList.php @@ -25,10 +25,10 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -use \Cake\Utility\Inflector; - declare(strict_types = 1); +use \Cake\Utility\Inflector; + $curAction = $this->request->getParam('action'); $curController = $this->request->getParam('controller'); $entityName = Inflector::singularize($vv_subnavigation_tableName); From dc5160dbae5452aa0e35420ce9b35bec1374d4ee Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 25 Sep 2024 19:01:34 +0300 Subject: [PATCH 05/16] Improve Status badge. --- app/src/Lib/Util/StringUtilities.php | 11 +-- .../Table/ExtIdentitySourceRecordsTable.php | 7 +- .../Model/Table/ExternalIdentitiesTable.php | 7 +- .../Table/ExternalIdentityRolesTable.php | 5 +- app/src/View/Helper/TabHelper.php | 62 +++++++++------ .../element/subnavigation/navBar.php | 77 +++++++++++++++++++ .../element/subnavigation/statusBadge.php | 13 +++- 7 files changed, 141 insertions(+), 41 deletions(-) create mode 100644 app/templates/element/subnavigation/navBar.php diff --git a/app/src/Lib/Util/StringUtilities.php b/app/src/Lib/Util/StringUtilities.php index 97437c183..cad2457de 100644 --- a/app/src/Lib/Util/StringUtilities.php +++ b/app/src/Lib/Util/StringUtilities.php @@ -220,12 +220,13 @@ public static function foreignKeyToClassName(string $s): string { /** * Localize a controller name, accounting for plugins. - * - * @since COmanage Registry v5.0.0 - * @param string $controllerName Name of controller to localize - * @param string $pluginName Plugin name, if appropriate - * @param bool $plural Whether to use plural localization + * + * @param string $controllerName Name of controller to localize + * @param string|null $pluginName Plugin name, if appropriate + * @param bool $plural Whether to use plural localization + * * @return string Localized text string + * @since COmanage Registry v5.0.0 */ public static function localizeController(string $controllerName, ?string $pluginName, bool $plural=false): string { diff --git a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php index f390ea552..e16c54f74 100644 --- a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php +++ b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php @@ -117,10 +117,10 @@ public function initialize(array $config): void { 'ExternalIdentities' => ['index'], ], // What model will have a counter-badge after the tab title -// 'counter' => ['PersonRoles', 'ExternalIdentities'], + 'counter' => ['PersonRoles', 'ExternalIdentities'], 'nested' => [ // Ordered list of Tabs - 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles'], // What actions will include the subnavigation header 'action' => [ // If a model renders in a subnavigation mode in edit/view mode, it cannot @@ -128,10 +128,9 @@ public function initialize(array $config): void { // XXX edit should go first. 'ExternalIdentities' => ['edit', 'view'], 'ExternalIdentityRoles' => ['index'], - 'ExtIdentitySourceRecords' => ['index'], ], // What model will have a counter-badge after the tab title - 'counter' => ['ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + 'counter' => ['ExternalIdentityRoles'], ] ] ); diff --git a/app/src/Model/Table/ExternalIdentitiesTable.php b/app/src/Model/Table/ExternalIdentitiesTable.php index 4fb34d0c3..2e6c6bbab 100644 --- a/app/src/Model/Table/ExternalIdentitiesTable.php +++ b/app/src/Model/Table/ExternalIdentitiesTable.php @@ -166,18 +166,17 @@ public function initialize(array $config): void { 'counter' => ['PersonRoles', 'ExternalIdentities'], 'nested' => [ // Ordered list of Tabs - 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles'], // What actions will include the subnavigation header 'action' => [ // If a model renders in a subnavigation mode in edit/view mode, it cannot // render in index mode for the same use case/context // XXX edit should go first. 'ExternalIdentities' => ['edit', 'view'], - 'ExternalIdentityRoles' => ['index'], - 'ExtIdentitySourceRecords' => ['index'], + 'ExternalIdentityRoles' => ['index'] ], // What model will have a counter-badge after the tab title - 'counter' => ['ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + 'counter' => ['ExternalIdentityRoles'], ] ] ); diff --git a/app/src/Model/Table/ExternalIdentityRolesTable.php b/app/src/Model/Table/ExternalIdentityRolesTable.php index b0e0b1611..d67b2f81d 100644 --- a/app/src/Model/Table/ExternalIdentityRolesTable.php +++ b/app/src/Model/Table/ExternalIdentityRolesTable.php @@ -137,7 +137,7 @@ public function initialize(array $config): void { 'counter' => ['PersonRoles', 'ExternalIdentities'], 'nested' => [ // Ordered list of Tabs - 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + 'tabs' => ['ExternalIdentities', 'ExternalIdentityRoles'], // What actions will include the subnavigation header 'action' => [ // If a model renders in a subnavigation mode in edit/view mode, it cannot @@ -145,10 +145,9 @@ public function initialize(array $config): void { // XXX edit should go first. 'ExternalIdentities' => ['edit', 'view'], 'ExternalIdentityRoles' => ['index'], - 'ExtIdentitySourceRecords' => ['index'], ], // What model will have a counter-badge after the tab title - 'counter' => ['ExternalIdentityRoles', 'ExtIdentitySourceRecords'], + 'counter' => ['ExternalIdentityRoles'], ] ] ); diff --git a/app/src/View/Helper/TabHelper.php b/app/src/View/Helper/TabHelper.php index 79713c98d..ac69cde87 100644 --- a/app/src/View/Helper/TabHelper.php +++ b/app/src/View/Helper/TabHelper.php @@ -37,6 +37,35 @@ class TabHelper extends Helper { + /** + * Construct the link filter + * + * @param string $tab + * @param int|string $curId + * + * @return int[]|string[] + */ + public function getLinkFilter(string $tab, int|string $curId): array + { + $plugin = $this->getView()->getPlugin(); + $mainTabList = $this->getView()->get('vv_sub_nav_attributes')['tabs']; + + $fullModelsName = !empty($plugin) ? $plugin . '.' . $tab : $tab; + $modelsTable = TableRegistry::getTableLocator()->get($fullModelsName); + $primary_link = collection($modelsTable->getPrimaryLinks()) + ->filter(function($link) use ($mainTabList) { + $linkToClass = StringUtilities::foreignKeyToClassName($link); + return \in_array($linkToClass, $mainTabList, true); + })->first(); + $foreignKey = StringUtilities::classNameToForeignKey($tab); + + // 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. If not, we will get the primary link directly. + return ($primary_link === 'co_id') + ? [$foreignKey => $curId] + : [$primary_link => $curId]; + } + /** * Get reference to Model Table * @@ -69,31 +98,20 @@ public function getModelTotalCount(string $modelName, array $whereClause): int } /** - * Construct the link filter - * - * @param string $tab - * @param int|string $curId + * Get Person Status by ID + * @param int $personId * - * @return int[]|string[] + * @return string */ - public function getLinkFilter(string $tab, int|string $curId): array + public function getPersonStatus(int $personId): string { - $plugin = $this->getView()->getPlugin(); - $mainTabList = $this->getView()->get('vv_sub_nav_attributes')['tabs']; + $peopleTable = TableRegistry::getTableLocator()->get('people'); + $response = $peopleTable + ->find() + ->select(['status']) + ->where(['id' => $personId]) + ->first(); - $fullModelsName = !empty($plugin) ? $plugin . '.' . $tab : $tab; - $modelsTable = TableRegistry::getTableLocator()->get($fullModelsName); - $primary_link = collection($modelsTable->getPrimaryLinks()) - ->filter(function($link) use ($mainTabList) { - $linkToClass = StringUtilities::foreignKeyToClassName($link); - return \in_array($linkToClass, $mainTabList, true); - })->first(); - $foreignKey = StringUtilities::classNameToForeignKey($tab); - - // 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. If not, we will get the primary link directly. - return ($primary_link === 'co_id') - ? [$foreignKey => $curId] - : [$primary_link => $curId]; + return $response?->status; } } \ No newline at end of file diff --git a/app/templates/element/subnavigation/navBar.php b/app/templates/element/subnavigation/navBar.php new file mode 100644 index 000000000..693e18f71 --- /dev/null +++ b/app/templates/element/subnavigation/navBar.php @@ -0,0 +1,77 @@ +set('vv_sub_nav_attributes', $subNavAttributes); +extract($subNavAttributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +// $this->name = Models +$modelsName = $this->name; +$this->set('vv_subnavigation_modelsName', $modelsName); +// $tablename = models +$tableName = \Cake\Utility\Inflector::tableize(\Cake\Utility\Inflector::singularize($this->name)); +$this->set('vv_subnavigation_tableName', $tableName); +?> + + diff --git a/app/templates/element/subnavigation/statusBadge.php b/app/templates/element/subnavigation/statusBadge.php index 43e0b310a..16a0ae5ab 100644 --- a/app/templates/element/subnavigation/statusBadge.php +++ b/app/templates/element/subnavigation/statusBadge.php @@ -31,11 +31,18 @@ extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); $curController = $this->request->getParam('controller'); -if(empty($vv_obj['status']) || $curController !== 'People') { +if(!in_array('People', $vv_subnavigation_tabs, true)) { return; } -$statusBadgeClass = match ($vv_obj['status']) { +$personId = $vv_person_id + ?? $this->request->getQuery('person_id') + ?? $vv_obj?->person_id + ?? $vv_obj?->id; + +$status = $this->Tab->getPersonStatus((int)$personId); + +$statusBadgeClass = match ($status) { 'A' => 'bg-outline-secondary primary', 'D','N','S','X','XP' => 'bg-danger', default => 'bg-warning' @@ -44,5 +51,5 @@ ?> - + From e8478770346f5b515495eb2fae880268624e207c Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 25 Sep 2024 19:11:43 +0300 Subject: [PATCH 06/16] Typos --- app/src/Model/Table/ExternalIdentitiesTable.php | 1 + app/templates/element/subnavigation/navBar.php | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/Model/Table/ExternalIdentitiesTable.php b/app/src/Model/Table/ExternalIdentitiesTable.php index 2e6c6bbab..dca4900e6 100644 --- a/app/src/Model/Table/ExternalIdentitiesTable.php +++ b/app/src/Model/Table/ExternalIdentitiesTable.php @@ -149,6 +149,7 @@ public function initialize(array $config): void { ] ]); + // All the tabs share the same configuration in the ModelTable file $this->setTabsConfig( [ // Ordered list of Tabs diff --git a/app/templates/element/subnavigation/navBar.php b/app/templates/element/subnavigation/navBar.php index 693e18f71..3ab772e79 100644 --- a/app/templates/element/subnavigation/navBar.php +++ b/app/templates/element/subnavigation/navBar.php @@ -24,15 +24,20 @@ * @since COmanage Registry v5.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * -* The subnavigation is structured in first, second, and third level blocks. -* Only External Identity Roles have a third-level of navigation. * */ +// Return if no configurations found if(empty($subNavAttributes)) { return; } +// We wil not include the subnavigation header if the query paremater co_id exists. This means that +// we want to render everything and not in the context of an association +if($this->request->getQuery('co_id') !== null) { + return; +} + $this->set('vv_sub_nav_attributes', $subNavAttributes); extract($subNavAttributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); @@ -63,7 +68,7 @@ Date: Thu, 26 Sep 2024 10:25:54 +0300 Subject: [PATCH 07/16] Core code should not depend on Plugins for subnavigation --- .../src/Model/Table/FileSourcesTable.php | 19 ++- .../templates/FileSources/fields-nav.inc | 31 ----- .../AttributeCollectors/fields-nav.inc | 14 +- .../Table/ExternalIdentitySourcesTable.php | 19 ++- app/src/View/Helper/TabHelper.php | 131 +++++++++++++++++- .../EnrollmentFlowSteps/fields-nav.inc | 31 ----- .../ExternalIdentitySources/fields-nav.inc | 5 - .../ExternalIdentitySources/retrieve.php | 10 +- .../ExternalIdentitySources/search.php | 22 +-- app/templates/Standard/add-edit-view.php | 8 +- app/templates/Standard/index.php | 8 +- .../element/subnavigation/inlineList.php | 2 - .../element/subnavigation/navBar.php | 11 +- .../element/subnavigation/tabList.php | 52 +------ .../element/subnavigation/tabTitle.php | 30 +++- 15 files changed, 213 insertions(+), 180 deletions(-) delete mode 100644 app/availableplugins/FileConnector/templates/FileSources/fields-nav.inc delete mode 100644 app/templates/EnrollmentFlowSteps/fields-nav.inc diff --git a/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php b/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php index c51a21307..b631cdb0e 100644 --- a/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php +++ b/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php @@ -44,7 +44,8 @@ class FileSourcesTable extends Table { use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + use \App\Lib\Traits\TabTrait; + // Cache of the field configuration protected $fieldCfg = null; @@ -78,6 +79,22 @@ public function initialize(array $config): void { ] ]); + // All the tabs share the same configuration in the ModelTable file + $this->setTabsConfig( + [ + // Ordered list of Tabs + 'tabs' => ['ExternalIdentitySources', 'FileConnector.FileSources'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'ExternalIdentitySources' => ['edit', 'view'], + 'FileConnector.FileSources' => ['edit'], + ], + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) 'entity' => [ diff --git a/app/availableplugins/FileConnector/templates/FileSources/fields-nav.inc b/app/availableplugins/FileConnector/templates/FileSources/fields-nav.inc deleted file mode 100644 index 4228ce579..000000000 --- a/app/availableplugins/FileConnector/templates/FileSources/fields-nav.inc +++ /dev/null @@ -1,31 +0,0 @@ - 'plugin', - 'active' => 'plugin' - ]; \ No newline at end of file diff --git a/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc b/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc index 901bd6e13..45b5bbb81 100644 --- a/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc +++ b/app/plugins/CoreEnroller/templates/AttributeCollectors/fields-nav.inc @@ -33,19 +33,7 @@ $topLinks[] = [ 'plugin' => 'CoreEnroller', 'controller' => 'enrollment_attributes', 'action' => 'index', - '?' => [ - 'attribute_collectors' => $vv_obj['id'], - 'attribute_collectors_id' => $vv_obj['id'], - 'attribute_collector_id' => $vv_obj['id'], - ] + 'attribute_collector_id' => $vv_obj->id ], 'class' => '' ]; - -$subnav = [ - 'name' => 'plugin', - 'active' => 'plugin', -]; - -//https://comanage-ioi-dev.workbench.incommon.org/registry-pe/core-enroller/enrollment-attributes?attribute_collectors=1&attribute_collector_id=1 -//https://comanage-ioi-dev.workbench.incommon.org/registry-pe/core-enroller/enrollment-attributes?attribute_collectors=1 \ No newline at end of file diff --git a/app/src/Model/Table/ExternalIdentitySourcesTable.php b/app/src/Model/Table/ExternalIdentitySourcesTable.php index f50542d44..dff984867 100644 --- a/app/src/Model/Table/ExternalIdentitySourcesTable.php +++ b/app/src/Model/Table/ExternalIdentitySourcesTable.php @@ -48,6 +48,7 @@ class ExternalIdentitySourcesTable extends Table { use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; + use \App\Lib\Traits\TabTrait; // Cache of the EIS configuration, keyed on id protected $eisCache = null; @@ -99,7 +100,23 @@ public function initialize(array $config): void { 'class' => 'SyncModeEnum' ] ]); - + + // All the tabs share the same configuration in the ModelTable file + $this->setTabsConfig( + [ + // Ordered-list of Tabs + 'tabs' => ['ExternalIdentitySources', 'ExternalIdentitySources.Plugin'], + // What actions will include the subnavigation header + 'action' => [ + // If a model renders in a subnavigation mode in edit/view mode, it cannot + // render in index mode for the same use case/context + // XXX edit should go first. + 'ExternalIdentitySources' => ['edit', 'view'], + 'ExternalIdentitySources.Plugin' => ['edit'], + ], + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) 'entity' => [ diff --git a/app/src/View/Helper/TabHelper.php b/app/src/View/Helper/TabHelper.php index ac69cde87..632ec9da6 100644 --- a/app/src/View/Helper/TabHelper.php +++ b/app/src/View/Helper/TabHelper.php @@ -37,6 +37,84 @@ class TabHelper extends Helper { + private ?string $pluginName; + + /** + * 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 + * + * @return array + */ + public function constructLinkUrl(string $tab, string|int $curId): array + { + $vv_subnavigation_action = $this->getView()->get('vv_sub_nav_attributes')['action']; + $linkFilter = $this->getLinkFilter($tab, $curId); + + $modelName = $tab; + $controller = $modelName; + $plugin = null; + + if(str_contains($tab, '.Plugin')) { + $modelName = $this->retrievePluginName($tab, (int)$curId); + [$plugin, $controller] = explode('.', $modelName); + $this->setPluginName($modelName); + } else if (str_contains($tab, '.')) { + // We have a plugin path + [$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' => $vv_subnavigation_action[$tab][0] + ]; + + if(\in_array('index', $vv_subnavigation_action[$tab], true)) { + $url['?'] = $linkFilter; + } else { + $url[] = $curId; + } + + return $url; + } + + /** + * Calculate the link Class + * @param string $tab + * + * @return string + */ + public function getLinkClass(string $tab): string + { + $vv_subnavigation_nested = $this->getView()->get('vv_subnavigation_nested'); + $curController = $this->getView()->getRequest()->getParam('controller'); + + // Calculate Tab Style Class(es) + $linkClass = 'nav-link'; + if( + // First level + ($tab === $curController || $tab === $this->getView()->getPlugin() . '.' . $curController) + || + ( + // Second level active + isset($vv_subnavigation_nested['tabs']) + && in_array($tab, $vv_subnavigation_nested['tabs'], true) + ) + ) { + $linkClass = 'nav-link active'; + } + + return $linkClass; + } + /** * Construct the link filter * @@ -47,17 +125,22 @@ class TabHelper extends Helper */ public function getLinkFilter(string $tab, int|string $curId): array { - $plugin = $this->getView()->getPlugin(); $mainTabList = $this->getView()->get('vv_sub_nav_attributes')['tabs']; + $modelName = $tab; + // We have two use cases. The first one is for the Core models and the second one is for the + // plugins. In case we have a plugin we need to retrieve the name from the database + if(str_contains($tab, '.Plugin')) { + $modelName = $this->retrievePluginName($tab, (int)$curId); + $this->setPluginName($modelName); + } - $fullModelsName = !empty($plugin) ? $plugin . '.' . $tab : $tab; - $modelsTable = TableRegistry::getTableLocator()->get($fullModelsName); + $modelsTable = TableRegistry::getTableLocator()->get($modelName); $primary_link = collection($modelsTable->getPrimaryLinks()) ->filter(function($link) use ($mainTabList) { $linkToClass = StringUtilities::foreignKeyToClassName($link); return \in_array($linkToClass, $mainTabList, true); })->first(); - $foreignKey = StringUtilities::classNameToForeignKey($tab); + $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. If not, we will get the primary link directly. @@ -66,6 +149,46 @@ public function getLinkFilter(string $tab, int|string $curId): array : [$primary_link => $curId]; } + /** + * @return string|null + */ + public function getPluginName(): ?string + { + return $this->pluginName; + } + + /** + * @param string|null $pluginName + * + * @return void + */ + public function setPluginName(?string $pluginName): void + { + $this->pluginName = $pluginName; + } + + /** + * Get the plugin name from the database + * + * @param string $tab + * @param int $curId + * + * @return string + */ + public function retrievePluginName(string $tab, int $curId): string + { + // Get the name of the Core Model + [$coreModel, $dummy] = explode('.', $tab); + $ModelTable = TableRegistry::getTableLocator()->get($coreModel); + $response = $ModelTable + ->find() + ->select(['plugin']) + ->where(['id' => $curId]) + ->first(); + + return $response?->plugin; + } + /** * Get reference to Model Table * diff --git a/app/templates/EnrollmentFlowSteps/fields-nav.inc b/app/templates/EnrollmentFlowSteps/fields-nav.inc deleted file mode 100644 index cc0622828..000000000 --- a/app/templates/EnrollmentFlowSteps/fields-nav.inc +++ /dev/null @@ -1,31 +0,0 @@ - 'plugin', - 'active' => 'properties' -]; diff --git a/app/templates/ExternalIdentitySources/fields-nav.inc b/app/templates/ExternalIdentitySources/fields-nav.inc index 9c6109179..c8b8d351b 100644 --- a/app/templates/ExternalIdentitySources/fields-nav.inc +++ b/app/templates/ExternalIdentitySources/fields-nav.inc @@ -37,8 +37,3 @@ $topLinks = [ 'class' => '' ] ]; - -$subnav = [ - 'name' => 'plugin', - 'active' => 'properties' -]; \ No newline at end of file diff --git a/app/templates/ExternalIdentitySources/retrieve.php b/app/templates/ExternalIdentitySources/retrieve.php index eca31a149..464aff0a6 100644 --- a/app/templates/ExternalIdentitySources/retrieve.php +++ b/app/templates/ExternalIdentitySources/retrieve.php @@ -25,16 +25,10 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -use \Cake\Utility\Inflector; +declare(strict_types = 1); - // Include the plugin subnavigation - $subnav = [ - 'name' => 'plugin', - 'active' => 'search' - ]; +use \Cake\Utility\Inflector; - // Generate the subnavigation title and tabs - print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); ?>
    diff --git a/app/templates/ExternalIdentitySources/search.php b/app/templates/ExternalIdentitySources/search.php index 53228f2e1..97015e10d 100644 --- a/app/templates/ExternalIdentitySources/search.php +++ b/app/templates/ExternalIdentitySources/search.php @@ -25,26 +25,8 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ - // Include the plugin subnavigation - $subnav = [ - 'name' => 'plugin', - 'active' => 'search', - 'topLinks' => [ - [ - 'label' => __d('operation', 'ExternalIdentitySources.search'), - 'link' => [ - 'plugin' => null, - 'controller' => 'external-identity-sources', - 'action' => 'search', - $this->request->getParam('pass')[0] - ], - 'class' => '' - ] - ] - ]; - - // Generate the subnavigation title and tabs - print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); +declare(strict_types = 1); + ?>
    diff --git a/app/templates/Standard/add-edit-view.php b/app/templates/Standard/add-edit-view.php index 4a8cfff29..e8543cc1e 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -80,13 +80,13 @@ } if ( ( // Simple use case - isset($tabsConfiguration['action'][$modelsName]) - && in_array($vv_action, $tabsConfiguration['action'][$modelsName], true) + isset($tabsConfiguration['action'][$fullModelsName]) + && in_array($vv_action, $tabsConfiguration['action'][$fullModelsName], true) ) || ( // Use case with two levels - isset($tabsConfiguration['nested']['action'][$modelsName]) - && in_array($vv_action, $tabsConfiguration['nested']['action'][$modelsName], true) + isset($tabsConfiguration['nested']['action'][$fullModelsName]) + && in_array($vv_action, $tabsConfiguration['nested']['action'][$fullModelsName], true) ) ) { print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index 7f475f56a..674ea7457 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -90,13 +90,13 @@ ]; if ( ( // Simple use case - isset($tabsConfiguration['action'][$modelsName]) - && in_array($vv_action, $tabsConfiguration['action'][$modelsName], true) + isset($tabsConfiguration['action'][$fullModelsName]) + && in_array($vv_action, $tabsConfiguration['action'][$fullModelsName], true) ) || ( // Use case with two levels - isset($tabsConfiguration['nested']['action'][$modelsName]) - && in_array($vv_action, $tabsConfiguration['nested']['action'][$modelsName], true) + isset($tabsConfiguration['nested']['action'][$fullModelsName]) + && in_array($vv_action, $tabsConfiguration['nested']['action'][$fullModelsName], true) ) ) { print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); diff --git a/app/templates/element/subnavigation/inlineList.php b/app/templates/element/subnavigation/inlineList.php index 2325d8c59..bfe2621c8 100644 --- a/app/templates/element/subnavigation/inlineList.php +++ b/app/templates/element/subnavigation/inlineList.php @@ -32,8 +32,6 @@ $curAction = $this->request->getParam('action'); $curController = $this->request->getParam('controller'); -$entityName = Inflector::singularize($vv_subnavigation_tableName); -$foreignKey = Inflector::underscore($entityName) . '_id'; extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); diff --git a/app/templates/element/subnavigation/navBar.php b/app/templates/element/subnavigation/navBar.php index 3ab772e79..7f39ee922 100644 --- a/app/templates/element/subnavigation/navBar.php +++ b/app/templates/element/subnavigation/navBar.php @@ -43,10 +43,7 @@ // $this->name = Models $modelsName = $this->name; -$this->set('vv_subnavigation_modelsName', $modelsName); -// $tablename = models -$tableName = \Cake\Utility\Inflector::tableize(\Cake\Utility\Inflector::singularize($this->name)); -$this->set('vv_subnavigation_tableName', $tableName); +$fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; ?>