From 90ad78b62dff2cb25a2ac17103e32d26512d9791 Mon Sep 17 00:00:00 2001 From: Benn Oshrin Date: Tue, 2 Jan 2024 20:36:37 -0500 Subject: [PATCH] Miscellaneous changes, in particular for Breadcrumbs (CFM-274) --- app/src/Controller/AppController.php | 16 ++- .../Component/BreadcrumbComponent.php | 124 ++++++++++++------ app/src/Controller/StandardController.php | 12 +- app/src/Lib/Traits/PrimaryLinkTrait.php | 4 +- 4 files changed, 106 insertions(+), 50 deletions(-) diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index 4e49f846f..f2128d9d2 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -219,20 +219,21 @@ public function getPrimaryLink(bool $lookup=false) { if($lookup) { foreach($availablePrimaryLinks as $potentialPrimaryLink) { + // $potentialPrimaryLink will be something like 'attribute_collector_id' + // $potentialPrimaryLinkTable will be something like 'CoreEnroller.AttributeCollectors' + $potentialPrimaryLinkTable = $this->$modelsName->getPrimaryLinkTableName($potentialPrimaryLink); + $potentialPlugin = null; + // Try to find a value - if(strstr($potentialPrimaryLink, '.')) { + if(strstr($potentialPrimaryLinkTable, '.')) { // For looking up values in records here, we want only the attribute // itself and not the plugin name (used for hacky notation by // PrimaryLinkTrait::setPrimaryLink(). Note this is a field and not // a model, but pluginModel() gets us the bit we need. // Store the plugin for possible later reference. - $potentialPlugin = StringUtilities::pluginPlugin($potentialPrimaryLink); - - // We clobber $potentialPrimaryLink to avoid rewriting a bunch of code, - // but probably we should rewrite it. - $potentialPrimaryLink = StringUtilities::pluginModel($potentialPrimaryLink); + $potentialPlugin = StringUtilities::pluginPlugin($potentialPrimaryLinkTable); } if($this->request->is('get')) { @@ -321,6 +322,9 @@ public function getPrimaryLink(bool $lookup=false) { if(!empty($this->cur_pl->value)) { // We found a populated primary link. Store the attribute and break the loop. $this->cur_pl->attr = $potentialPrimaryLink; + if($potentialPlugin) { + $this->cur_pl->plugin = $potentialPlugin; + } $this->set('vv_primary_link', $this->cur_pl->attr); break; } diff --git a/app/src/Controller/Component/BreadcrumbComponent.php b/app/src/Controller/Component/BreadcrumbComponent.php index d78e27d22..2b01f506e 100644 --- a/app/src/Controller/Component/BreadcrumbComponent.php +++ b/app/src/Controller/Component/BreadcrumbComponent.php @@ -48,6 +48,8 @@ class BreadcrumbComponent extends Component protected $injectParents = []; // Inject title links (immediately before the title breadcrumb) protected $injectTitleLinks = []; + // Whether parent links are frozen + protected $parentsFrozen = false; /** * Callback run prior to rendering the view. @@ -117,7 +119,7 @@ public function beforeRender(EventInterface $event) { if($action != 'index') { $target = [ - 'plugin' => null, + 'plugin' => $primaryLink->plugin ?? null, 'controller' => $modelsName, 'action' => 'index' ]; @@ -126,8 +128,12 @@ public function beforeRender(EventInterface $event) { $target['?'] = [$primaryLink->attr => $primaryLink->value]; } + $label = (!empty($primaryLink->plugin) + ? __d(Inflector::underscore($primaryLink->plugin), 'controller.'.$modelsName, [99]) + : __d('controller', $modelsName, [99])); + $parents[] = [ - 'label' => __d('controller', $modelsName, [99]), + 'label' => $label, 'target' => $target ]; } @@ -140,65 +146,74 @@ public function beforeRender(EventInterface $event) { } /** - * Inject a title link based on the display field of an entity into the breadcrumb set. + * Prevent any additional parent links from being added. Intended primarily for + * Controllers that extend StandardController but do not want the standard behavior. * * @since COmanage Registry v5.0.0 - * @param Table $table Table for $entity - * @param Entity $entity Entity to generate title link for - * @param string $action Action to link to - * @param string $label If set, use this label instead of the entity's displayField */ - - public function injectTitleLink( - $table, - $entity, - string $action='edit', - ?string $label=null - ) { - $displayField = $table->getDisplayField(); - $this->injectTitleLinks[] = [ - 'target' => [ - 'plugin' => null, - 'controller' => $table->getTable(), - 'action' => $action, - $entity->id - ], - 'label' => $label ?: $entity->$displayField - ]; + public function freezeParents() { + $this->parentsFrozen = true; } /** * Inject the primary link into the breadcrumb path. * * @since COmanage Registry v5.0.0 - * @param object link Primary Link (as returned by getPrimaryLink()) + * @param object $link Primary Link (as returned by getPrimaryLink()) + * @param bool $index Include link to parent index + * @param string $linkLabel Label to use for Primary Link instead of displayField */ - public function injectPrimaryLink(object $link) { + public function injectPrimaryLink(object $link, bool $index=true, $linkLabel=null) { + if($this->parentsFrozen) { + return; + } + // eg: "People" $modelsName = StringUtilities::foreignKeyToClassName($link->attr); + $modelPath = $modelsName; + + if(!empty($link->plugin)) { + // eg: "CoreEnroller.AttributeCollectors" + $modelPath = $link->plugin . "." . $modelsName; + } $contain = []; $primaryName = null; - $linkTable = TableRegistry::getTableLocator()->get($modelsName); + $linkTable = TableRegistry::getTableLocator()->get($modelPath); $linkObj = $linkTable->get($link->value, ['contain' => $contain]); $displayField = $linkTable->getDisplayField(); - $this->injectParents[] = [ - 'target' => [ - 'plugin' => null, - 'controller' => $modelsName, - 'action' => 'index', - '?' => [ - 'co_id' => $link->co_id - ] - ], - 'label' => __d('controller', $modelsName, [99]) - ]; + if($index) { + // We need to determine the primary link of the parent, which might or might + // not be co_id + + if(method_exists($linkTable, "findPrimaryLink")) { + // If findPrimaryLink doesn't exist, we're probably working with CosTable + + $parentLink = $linkTable->findPrimaryLink($linkObj->id); + + $this->injectParents[] = [ + 'target' => [ + 'plugin' => $parentLink->plugin ?? null, + 'controller' => $modelsName, + 'action' => 'index', + '?' => [ + $parentLink->attr => $parentLink->value + ] + ], + 'label' => StringUtilities::localizeController( + controllerName: $modelsName, + pluginName: $link->plugin ?? null, + plural: true + ) + ]; + } + } - $label = $linkObj->$displayField; + $label = $linkLabel ?? $linkObj->$displayField; if($modelsName == 'People' || $modelsName == 'ExternalIdentities') { // We need the Primary Name (or first name found) to render it @@ -218,7 +233,7 @@ public function injectPrimaryLink(object $link) { $this->injectParents[] = [ 'target' => [ - 'plugin' => null, + 'plugin' => $link->plugin ?? null, 'controller' => $modelsName, 'action' => 'edit', $linkObj->id @@ -227,6 +242,35 @@ public function injectPrimaryLink(object $link) { ]; } + /** + * Inject a title link based on the display field of an entity into the breadcrumb set. + * + * @since COmanage Registry v5.0.0 + * @param Table $table Table for $entity + * @param Entity $entity Entity to generate title link for + * @param string $action Action to link to + * @param string $label If set, use this label instead of the entity's displayField + */ + + public function injectTitleLink( + $table, + $entity, + string $action='edit', + ?string $label=null + ) { + $displayField = $table->getDisplayField(); + + $this->injectTitleLinks[] = [ + 'target' => [ + 'plugin' => null, + 'controller' => $table->getTable(), + 'action' => $action, + $entity->id + ], + 'label' => $label ?: $entity->$displayField + ]; + } + /** * Set the set of paths that should be skipped when rendering breadcrumbs. * Paths are specified as regular expressions, eg: '/^\/cos\/select/' diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index a6bc19b6d..c2ec6813a 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -506,8 +506,12 @@ public function generateRedirect($entity) { if(!empty($link->attr) && !empty($link->value)) { $redirect['?'] = [$link->attr => $link->value]; } + + if(!empty($this->getPlugin())) { + $redirect['plugin'] = $this->getPlugin(); + } } - + return $this->redirect($redirect); } @@ -671,7 +675,7 @@ protected function populateAutoViewVars(object $obj=null) { case 'array': // Use the provided array of values. By default, we use the values // for the keys as well, to generate HTML along the lines of - // + // . (See also 'hash'.) $this->set($vvar, array_combine($avv['array'], $avv['array'])); break; case 'enum': @@ -684,6 +688,10 @@ protected function populateAutoViewVars(object $obj=null) { } $this->set($vvar, $class::getLocalizedConsts()); break; + case 'hash': + // Like 'array' but we assume we are passed key/value pairs + $this->set($vvar, $avv['hash']); + break; // "auxiliary" and "select" do basically the same thing, but the former // returns the full object and the latter just returns a hash suitable // for a select. "type" is a shorthand for "select" for type_id. diff --git a/app/src/Lib/Traits/PrimaryLinkTrait.php b/app/src/Lib/Traits/PrimaryLinkTrait.php index 7c2ba6f68..42e4b92e1 100644 --- a/app/src/Lib/Traits/PrimaryLinkTrait.php +++ b/app/src/Lib/Traits/PrimaryLinkTrait.php @@ -180,11 +180,11 @@ public function findFilterPrimaryLink(\Cake\ORM\Query $query, array $options) { * @since COmanage Registry v5.0.0 * @param int $id Object ID * @param bool $archived Whether to retrieve archived (deleted) records - * @return Entity Primary Link (as an Entity) + * @return object Primary Link information (as an object) * @throws \InvalidArgumentException */ - public function findPrimaryLink(int $id, bool $archived=false) { + public function findPrimaryLink(int $id, bool $archived=false): object { $obj = $this->get($id, ['archived' => $archived]); //->firstOrFail(); // We might have multiple primary link keys (eg for MVEAs), but only one