diff --git a/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php b/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php index c51a21307..0e26f851a 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,23 @@ 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', 'ExternalIdentitySources@action.search'], + // 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', 'search'], + 'FileConnector.FileSources' => ['edit'], + 'ExternalIdentitySources@action.search' => [], + ], + ] + ); + $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/src/Model/Table/AttributeCollectorsTable.php b/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php index 902419507..2c1699ddf 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php @@ -42,6 +42,7 @@ class AttributeCollectorsTable 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; @@ -74,6 +75,40 @@ public function initialize(array $config): void { $this->setRequiresCO(true); $this->setAllowLookupPrimaryLink(['dispatch', 'display']); + // All the tabs share the same configuration in the ModelTable file + $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'], + 'nested' => [ + // Ordered list of Tabs + 'tabs' => ['EnrollmentFlowSteps', 'CoreEnroller.AttributeCollectors', 'CoreEnroller.EnrollmentAttributes'], + // 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. + 'EnrollmentFlowSteps' => ['edit', 'view'], + 'CoreEnroller.AttributeCollectors' => ['edit'], + 'CoreEnroller.EnrollmentAttributes' => ['index'] + ], + // What model will have a counter-badge after the tab title + 'counter' => ['CoreEnroller.EnrollmentAttributes'], + ] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) 'entity' => [ diff --git a/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php b/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php index fb0bbad24..d37681bc0 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php @@ -46,6 +46,7 @@ class EnrollmentAttributesTable extends Table { use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; + use \App\Lib\Traits\TabTrait; /** * Perform Cake Model initialization. @@ -157,6 +158,40 @@ public function initialize(array $config): void { ] ]); + // All the tabs share the same configuration in the ModelTable file + $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'], + 'nested' => [ + // Ordered list of Tabs + 'tabs' => ['EnrollmentFlowSteps', 'CoreEnroller.AttributeCollectors', 'CoreEnroller.EnrollmentAttributes'], + // 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. + 'EnrollmentFlowSteps' => ['edit', 'view'], + 'CoreEnroller.AttributeCollectors' => ['edit'], + 'CoreEnroller.EnrollmentAttributes' => ['index'] + ], + // What model will have a counter-badge after the tab title + 'counter' => ['CoreEnroller.EnrollmentAttributes'], + ] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) 'entity' => [ 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/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/templates/element/tabs/tabTitleWithCount.php b/app/src/Lib/Traits/TabTrait.php similarity index 69% rename from app/templates/element/tabs/tabTitleWithCount.php rename to app/src/Lib/Traits/TabTrait.php index 07845fa6c..0144c11e3 100644 --- a/app/templates/element/tabs/tabTitleWithCount.php +++ b/app/src/Lib/Traits/TabTrait.php @@ -1,6 +1,6 @@ Common->getModelTotalCount($model, $where); -} +declare(strict_types = 1); + +namespace App\Lib\Traits; + +trait TabTrait +{ + /** + * Tabs configuration + * + * @var array + */ + private array $tabsConfig = []; + + /** + * @return array + */ + public function getTabsConfig(): array + { + return $this->tabsConfig; + } -?> + /** + * @param array $tabsConfig + * + * @return void + */ + public function setTabsConfig(array $tabsConfig): void + { + $this->tabsConfig = $tabsConfig; + } - - - - \ No newline at end of file +} \ No newline at end of file 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/EnrollmentFlowStepsTable.php b/app/src/Model/Table/EnrollmentFlowStepsTable.php index 8071e772b..d550ddc97 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,41 @@ 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'], + 'nested' => [ + // Ordered list of Tabs + 'tabs' => ['EnrollmentFlowSteps', 'EnrollmentFlowSteps.Plugin', 'EnrollmentFlowSteps.Hierarchy'], + // 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. + 'EnrollmentFlowSteps' => ['edit', 'view'], + 'EnrollmentFlowSteps.Plugin' => ['edit'], + // This means that we are looking at the plugins associated model + // EnrollmentFlowSteps -> plugin -> @plugin + 'EnrollmentFlowSteps.Hierarchy' => ['index'] + ], + // What model will have a counter-badge after the tab title + 'counter' => ['EnrollmentFlowSteps.Hierarchy'], + ] + ] + ); } /** 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/ExtIdentitySourceRecordsTable.php b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php index e4db956b9..e16c54f74 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,38 @@ 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'], + // 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'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['ExternalIdentityRoles'], + ] + ] + ); + $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..dca4900e6 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' ] ]); - + + // All the tabs share the same configuration in the ModelTable file + $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'], + // 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'] + ], + // What model will have a counter-badge after the tab title + 'counter' => ['ExternalIdentityRoles'], + ] + ] + ); + $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..d67b2f81d 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,39 @@ 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'], + // 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'], + ], + // What model will have a counter-badge after the tab title + 'counter' => ['ExternalIdentityRoles'], + ] + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) // See also CFM-126 diff --git a/app/src/Model/Table/ExternalIdentitySourcesTable.php b/app/src/Model/Table/ExternalIdentitySourcesTable.php index f50542d44..45312a0c4 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,24 @@ 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', 'ExternalIdentitySources@action.search'], + // 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', 'search'], + 'ExternalIdentitySources.Plugin' => ['edit'], + 'ExternalIdentitySources@action.search' => [], + ], + ] + ); + $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) 'entity' => [ 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/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/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/src/View/Helper/TabHelper.php b/app/src/View/Helper/TabHelper.php new file mode 100644 index 000000000..22227dc11 --- /dev/null +++ b/app/src/View/Helper/TabHelper.php @@ -0,0 +1,358 @@ +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); + + $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')) { + $modelName = $this->retrievePluginName($tab, (int)$curId); + [$plugin, ] = explode('.', $modelName); + foreach ($this->getHasManyAssociationModels($modelName) as $association) { + [$plugin, $controller] = explode('.', $association); + break; + } + } else if (str_contains($tab, '@action')) { + // We have a plugin path + [$controller,] = explode('@', $modelName); + [, $action] = explode('.', $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' => $action + ]; + + if(\in_array('index', $vv_subnavigation_action[$tab], true)) { + $url['?'] = $linkFilter; + } else { + $url[] = $curId; + } + + return $url; + } + + /** + * Calculate the link Class + * + * @param string $tab + * @param bool $isNested + * + * @return string + */ + public function getLinkClass(string $tab, bool $isNested = false): string + { + $vv_sub_nav_attributes = $this->getView()->get('vv_sub_nav_attributes'); + + $curController = $this->getView()->getRequest()->getParam('controller'); + $curAction = $this->getView()->getRequest()->getParam('action'); + $plugin = $this->getView()->getPlugin(); + $fullModelName = $curController; + 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 + $parentModelForNested = $vv_sub_nav_attributes['nested']['tabs'][0] ?? 0; + + // Calculate Tab Style Class(es) + return match(true) { + // Matches the tab to the current controller. It addresses the simple subnavigation + // The fullModelName can either be a simple Model or a Plugin with the path. + $tab === $fullModelName && \in_array($curAction, ['index', 'edit', 'view']), + // Always mark active the parent Tab + !$isNested && $parentModelForNested !== null && $tab === $parentModelForNested, + // Matches the action tab links, e.g. FileSource/search + $tab === "{$curController}@action.{$curAction}" => 'nav-link active', + default => 'nav-link' + }; + } + + /** + * Check the belongsTo tree hierarchy + * + * @param string $tab + * @param string $modelFullName + * @param int $depth + * + * @return bool + */ + 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. + $associations = $model->associations(); + + $depth++; + foreach($associations->getByType(['belongsTo', 'belongsToMany']) as $ta) { + if($ta->getClassName() === $tab) { + return true; + } + return $this->tabBelongsToModelPath($tab, $ta->getClassName(), $depth); + } + + return false; + } + + /** + * Check the belongsTo tree hierarchy + * + * @param string $modelName + * + * @return \Generator + */ + public function getHasManyAssociationModels(string $modelName): \Generator + { + $model = TableRegistry::getTableLocator()->get($modelName); + // We'll start by obtaining the set of models directly associated with the CO model. + $associations = $model->associations(); + + foreach($associations->getByType(['hasMany', 'hasOne']) as $ta) { + $this>$this->setAssociation($ta->getClassName()); + yield $ta->getClassName(); + } + } + + /** + * Construct the link filter + * + * @param string $tab + * @param int|string $curId + * + * @return int[]|string[] + */ + public function getLinkFilter(string $tab, int|string $curId): array + { + $mainTabList = $this->getView()->get('vv_sub_nav_attributes')['tabs']; + $fullModelsName = $tab; + $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 = $modelName; + } else if (str_contains($tab, '.Hierarchy')) { + $modelName = $this->retrievePluginName($tab, (int)$curId); + [$plugin, ] = explode('.', $modelName); + foreach ($this->getHasManyAssociationModels($modelName) as $association) { + $fullModelsName = $association; + break; + } + } else if(str_contains($tab, '@action')) { + [$modelName, ] = explode('@', $tab); + $fullModelsName = $modelName; + } else if(str_contains($tab, '.')) { + [$plugin, $modelName] = explode('.', $tab); + } + + $modelsTable = TableRegistry::getTableLocator()->get($fullModelsName); + $primary_link_list = $modelsTable->getPrimaryLinks(); + $primary_link = null; + if(count($primary_link_list) > 1) { + $primary_link = collection($primary_link_list) + ->filter(function($link) use ($mainTabList) { + $linkToClass = StringUtilities::foreignKeyToClassName($link); + return \in_array($linkToClass, $mainTabList, true); + })->first(); + } else if (\is_array($primary_link_list) && !empty($primary_link_list)) { + $primary_link = $primary_link_list[0]; + } + + $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. + return ($primary_link === 'co_id') + ? [$foreignKey => $curId] + : [$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 + * + * @param string $modelsName + * + * @return Table + */ + public function getModelTableReference(string $modelsName): Table + { + return TableRegistry::getTableLocator()->get($modelsName); + } + + /** + * Select count(*) + * + * @param string $modelName Model name in `group_members` format + * @param array $whereClause where clause array + * + * @return int + */ + public function getModelTotalCount(string $modelName, array $whereClause): int + { + $modelsName = Inflector::camelize($modelName); + $ModelTable = TableRegistry::getTableLocator()->get($modelsName); + $count = $ModelTable->find() + ->where($whereClause) + ->count(); + + return $count; + } + + /** + * Get Person Status by ID + * @param int $personId + * + * @return string + */ + public function getPersonStatus(int $personId): string + { + $peopleTable = TableRegistry::getTableLocator()->get('people'); + $response = $peopleTable + ->find() + ->select(['status']) + ->where(['id' => $personId]) + ->first(); + + return $response?->status; + } + + /** + * @return string|null + */ + public function getAssociation(): ?string + { + return $this->association; + } + + /** + * @param string|null $association + * + * @return void + */ + public function setAssociation(?string $association): void + { + $this->association = $association; + } +} \ No newline at end of file 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 b7c0cca94..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', $subnav); ?>
diff --git a/app/templates/ExternalIdentitySources/search.php b/app/templates/ExternalIdentitySources/search.php index 2f61e4e23..fb943f460 100644 --- a/app/templates/ExternalIdentitySources/search.php +++ b/app/templates/ExternalIdentitySources/search.php @@ -25,14 +25,14 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ - // Include the plugin subnavigation - $subnav = [ - 'name' => 'plugin', - 'active' => 'search' - ]; - - // Generate the subnavigation title and tabs - print $this->element('subnavigation', $subnav); +declare(strict_types = 1); + +// Required by the subnavigation partial +$modelsName = $this->name; +// Subnavigation calculations +if(file_exists(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc')) { + include(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc'); +} ?>
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..9626ae1a4 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -65,17 +65,9 @@ $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. - $subnav['topLinks'] = $topLinks; - } - // Generate the subnavigation title and tabs - print $this->element('subnavigation', $subnav); +// Subnavigation +if(file_exists(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc')) { + include(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc'); } ?> diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index 74f342f49..114141198 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -79,13 +79,9 @@ $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 +if(file_exists(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc')) { + include(ROOT . DS . 'templates' . DS . 'Standard/subnavigation.inc'); } ?> @@ -127,9 +123,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/Standard/subnavigation.inc b/app/templates/Standard/subnavigation.inc new file mode 100644 index 000000000..13d3460e5 --- /dev/null +++ b/app/templates/Standard/subnavigation.inc @@ -0,0 +1,57 @@ +getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; +$modelsTable = $this->Tab->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; + } + if ( + ( // Simple use case + isset($tabsConfiguration['action'][$fullModelsName]) + && in_array($vv_action, $tabsConfiguration['action'][$fullModelsName], true) + ) + || + ( // Use case with two levels + isset($tabsConfiguration['nested']['action'][$fullModelsName]) + && in_array($vv_action, $tabsConfiguration['nested']['action'][$fullModelsName], true) + ) + ) { + print $this->element('subnavigation/navBar', ['subNavAttributes' => $subnav]); + } +} \ No newline at end of file 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/src/View/Helper/CommonHelper.php b/app/templates/element/subnavigation/inlineList.php similarity index 53% rename from app/src/View/Helper/CommonHelper.php rename to app/templates/element/subnavigation/inlineList.php index e19e3dd10..0d0e52374 100644 --- a/app/src/View/Helper/CommonHelper.php +++ b/app/templates/element/subnavigation/inlineList.php @@ -1,6 +1,6 @@ request->getParam('action'); +$curController = $this->request->getParam('controller'); +$isNested = true; -class CommonHelper extends Helper -{ - /** - * Get reference to Model Table - * - * @param string $modelsName - * - * @return Table - */ - public function getModelTableReference(string $modelsName): Table - { - return TableRegistry::getTableLocator()->get($modelsName); - } +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); - /** - * Select count(*) - * - * @param string $modelName Model name in `group_members` format - * @param array $whereClause where clause array - * - * @return int - */ - public function getModelTotalCount(string $modelName, array $whereClause): int - { - $modelsName = Inflector::camelize($modelName); - $ModelTable = TableRegistry::getTableLocator()->get($modelsName); - $count = $ModelTable->find() - ->where($whereClause) - ->count(); +$curId = $this->request->getQuery($vv_primary_link) + ?? $vv_obj->id + ?? end($vv_bc_title_links[0]['target']); +?> - return $count; - } -} \ No newline at end of file + +
  • + element('subnavigation/tabTitle', compact('tab', 'curId', 'isNested')); + // Construct Target URL + $url = $this->Tab->constructLinkUrl($tab, $curId, $isNested); + // Calculate Tab Style Class(es) + $linkClass = $this->Tab->getLinkClass($tab, $isNested); + // 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/navBar.php b/app/templates/element/subnavigation/navBar.php new file mode 100644 index 000000000..7f39ee922 --- /dev/null +++ b/app/templates/element/subnavigation/navBar.php @@ -0,0 +1,79 @@ +request->getQuery('co_id') !== null) { + return; +} + +$this->set('vv_sub_nav_attributes', $subNavAttributes); +extract($subNavAttributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); + +// $this->name = Models +$modelsName = $this->name; +$fullModelsName = !empty($this->getPlugin()) ? $this->getPlugin() . '.' . $modelsName : $modelsName; +?> + + diff --git a/app/templates/EnrollmentFlowSteps/fields-nav.inc b/app/templates/element/subnavigation/statusBadge.php similarity index 60% rename from app/templates/EnrollmentFlowSteps/fields-nav.inc rename to app/templates/element/subnavigation/statusBadge.php index 7eebea083..16a0ae5ab 100644 --- a/app/templates/EnrollmentFlowSteps/fields-nav.inc +++ b/app/templates/element/subnavigation/statusBadge.php @@ -1,6 +1,6 @@ 'enrollment_flow', - 'active' => 'steps' -]; \ No newline at end of file + +declare(strict_types = 1); + +extract($vv_sub_nav_attributes, EXTR_PREFIX_ALL, 'vv_subnavigation'); +$curController = $this->request->getParam('controller'); + +if(!in_array('People', $vv_subnavigation_tabs, true)) { + return; +} + +$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' +}; + +?> + + + + 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..b2eb6d65e --- /dev/null +++ b/app/templates/element/subnavigation/tabList.php @@ -0,0 +1,57 @@ +request->getParam('action'); +$curController = $this->request->getParam('controller'); +$isNested = false; + +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']); + +?> + + +
    + + diff --git a/app/templates/element/subnavigation/tabTitle.php b/app/templates/element/subnavigation/tabTitle.php new file mode 100644 index 000000000..13d2e507f --- /dev/null +++ b/app/templates/element/subnavigation/tabTitle.php @@ -0,0 +1,93 @@ +Tab->getLinkFilter($tab, $curId); + +// Simple use case +$tabLanguageKey = in_array('index', $navigation_action[$tab], true) ? $tab : 'Properties'; +$title = __d('controller', $tabLanguageKey, [99]); +$tabToTableName = Inflector::tableize(Inflector::singularize($tab)); + +// 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 + [$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 + $fullModelName = $this->Tab->getAssociation(); + [$plugin, $modelName] = explode('.', $fullModelName); + $poFile = Inflector::underscore($plugin); + $title = __d($poFile, 'controller.' . $modelName, [99]); + $tabToTableName = Inflector::tableize(Inflector::singularize($fullModelName)); +} else if(str_contains($tabLanguageKey, '.')) { // Simple Plugin Plugin.Model + [$plugin, $modelName] = explode('.', $tabLanguageKey); + $poFile = Inflector::underscore($plugin); + $title = __d($poFile, 'controller.' . $modelName, [99]); + $tabToTableName = Inflector::tableize(Inflector::singularize($tabLanguageKey)); +} + +// Insert Counter Badge if applicable +if(isset($tab_counter) + && in_array($tab, $tab_counter, true) +) { + $model = $tabToTableName; + $where = $linkFilter; +} + +if(!isset($num) + && !empty($model) + && !empty($where)) { + $num = $this->Tab->getModelTotalCount($model, $where); +} + +?> + + + + + + + \ No newline at end of file diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index 66cfcd8c8..076e00d75 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -715,6 +715,9 @@ ul.form-list li.alert-banner .co-alert { } .cm-subnav-links .nav-link { color: var(--cmg-color-link) !important; + display: flex; + flex-direction: row-reverse; + align-items: center; } .cm-subnav-tabs .nav-link.active, .cm-subnav-links .nav-link.active {