From 9dcf3788cda9e074d9de118fb68f014605b7051c Mon Sep 17 00:00:00 2001
From: Ioannis Igoumenos <ioigoume@gmail.com>
Date: Thu, 3 Oct 2024 17:02:37 +0300
Subject: [PATCH] Subnavigation fixes

---
 .../src/Controller/FileSourcesController.php  |  23 +++
 .../AttributeCollectorsController.php         |  23 +++
 .../EnrollmentAttributesController.php        |   8 +
 .../Model/Table/AttributeCollectorsTable.php  |   1 -
 .../Model/Table/EnrollmentAttributesTable.php |   4 +-
 .../EnrollmentAttributes/columns.inc          |   1 +
 app/resources/locales/en_US/operation.po      |  12 ++
 .../EnrollmentFlowStepsController.php         |  25 +++
 .../ExternalIdentitiesController.php          |  28 ++-
 app/src/Controller/GroupMembersController.php |   1 +
 .../Controller/GroupNestingsController.php    |   1 +
 .../JobHistoryRecordsController.php           |   1 +
 app/src/Controller/PetitionsController.php    |  25 ++-
 .../Controller/StandardPluginController.php   |   1 +
 app/src/Lib/Util/StringUtilities.php          |   6 +-
 .../Model/Table/EnrollmentFlowStepsTable.php  |   1 -
 .../Table/ExternalIdentityRolesTable.php      |   6 +-
 app/src/Model/Table/PersonRolesTable.php      |   2 +-
 app/src/View/Helper/TabHelper.php             | 183 ++++++++++++++----
 app/templates/EnrollmentFlowSteps/columns.inc |   4 -
 app/templates/Standard/add-edit-view.php      |  23 ++-
 .../element/subnavigation/inlineList.php      |   5 +-
 .../element/subnavigation/supertitle.php      |  70 ++++++-
 .../element/subnavigation/tabList.php         |   2 +-
 .../element/subnavigation/tabTitle.php        |  14 +-
 25 files changed, 392 insertions(+), 78 deletions(-)

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