diff --git a/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php b/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php index 6861ffc63..c51a21307 100644 --- a/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php +++ b/app/availableplugins/FileConnector/src/Model/Table/FileSourcesTable.php @@ -449,9 +449,11 @@ public function search( while(($data = fgetcsv($handle)) !== false) { // strtolower, previous behavior was full string only so dupe that - $match = array_search(strtolower($searchAttrs['q']), array_map('strtolower', $data)); + $match = collection($data) + ->map(fn($value, $key) => strtolower($value ?? '')) + ->filter(fn($item, $key) => strtolower($searchAttrs['q']) === $item); - if($match !== false) { + if($match->count() > 0) { // $match will be the CSV column that matched, but for now we ignore that // since we just need to know that the row matched somewhere. Note the first // column is always the SORID. diff --git a/app/resources/locales/en_US/information.po b/app/resources/locales/en_US/information.po index 85a1b293f..b661506a9 100644 --- a/app/resources/locales/en_US/information.po +++ b/app/resources/locales/en_US/information.po @@ -61,7 +61,10 @@ msgid "ExternalIdentitySources.records" msgstr "Source Records" msgid "ExternalIdentitySources.retrieve" -msgstr "This is the current record retrieved directly from the source. View the latest record cached by Registry." +msgstr "This is the current record retrieved directly from the source. {0}" + +msgid "ExternalIdentitySources.cached" +msgstr "View the latest record cached by Registry" msgid "ExternalIdentitySources.retrieve.notSynced" msgstr "This is the current record available directly from the source." @@ -70,7 +73,7 @@ msgid "ExternalIdentitySourceRecords.metadata" msgstr "Metadata" msgid "ExternalIdentitySourceRecords.view" -msgstr "This is the latest record from the source as cached by Registry. Retrieve the current record directly from the source." +msgstr "This is the latest record from the source as cached by Registry. Retrieve the current record directly from the source." msgid "ExternalIdentitySources.search.attrs.none" msgstr "The External Identity Source cannot be searched." diff --git a/app/src/Command/TransmogrifyCommand.php b/app/src/Command/TransmogrifyCommand.php index c0cc13df2..a332ce577 100644 --- a/app/src/Command/TransmogrifyCommand.php +++ b/app/src/Command/TransmogrifyCommand.php @@ -1308,8 +1308,8 @@ protected function roleSqlSelect(string $tableName): string { manager_co_person_id, cou_id, CASE - WHEN affiliation IS NULL THEN "member" - WHEN affiliation = "" THEN "member" + WHEN affiliation IS NULL THEN 'member' + WHEN affiliation = '' THEN 'member' ELSE affiliation END as affiliation, title, diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index e190ff1ab..efb407076 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -44,6 +44,7 @@ use Cake\ORM\TableRegistry; use Cake\Utility\Hash; use InvalidArgumentException; +use JsonSchema\Iterator\ObjectIterator; class AppController extends Controller { use \App\Lib\Traits\LabeledLogTrait; @@ -187,12 +188,12 @@ public function getCO(): ?\App\Model\Entity\Co { // We'll return null if no CO, since some contexts may need to know that return $this->cur_co; } - + /** * Get the current CO ID. * + * @return int|null CO ID, or null * @since COmanage Registry v5.0.0 - * @return int CO ID, or null */ public function getCOID(): ?int { @@ -202,7 +203,171 @@ public function getCOID(): ?int { } /** - * Obtain information about the Standard Object's Primary Link, if set. + * @param string $potentialPrimaryLink + * + * @return Object|bool + * @since COmanage Registry v5.0.0 + */ + + protected function primaryLinkOnGet(string $potentialPrimaryLink): Object|bool + { + // $this->name = Models + $modelsName = $this->name; + + // If this action allows unkeyed, asserted primary link IDs, check the query + // string (e.g.: 'add' or 'index' allow matchgrid_id to be passed in) + $actionParam = $this->request->getParam('action'); + $allowsUnkeyed = $this->$modelsName->allowUnkeyedPrimaryLink($actionParam); + $allowsLookup = $this->$modelsName->allowLookupPrimaryLink($actionParam); + $param = (int)$this->request->getParam('pass.0'); + + if($allowsUnkeyed) { + $query = $this->request->getQuery(); + if($query) { + return $this->populatedPrimaryLink($potentialPrimaryLink); + } + } + + if(!$allowsLookup || empty($param)) { + return false; + } + + return $this->$modelsName->findPrimaryLink($param); + } + + /** + * @param string $potentialPrimaryLink + * + * @return Object|bool + * @since COmanage Registry v5.0.0 + * + */ + + protected function primaryLinkOnPost(string $potentialPrimaryLink): Object|bool + { + // $this->name = Models + $modelsName = $this->name; + + // Post = add, where we can have a list of objects and nothing in /objects/{id} + // We don't support different primary links across objects, so we throw an error + // if different parent keys are provided. + // Data in API format | Data in POST format + $reqData = $this->request->getData($modelsName) ?? $this->request->getData() ?? []; + $potentialPrimaryLinkRecords = collection($reqData)->filter(fn($value, $key) => $key === $potentialPrimaryLink); + if($potentialPrimaryLinkRecords->count() > 1) { + // We don't support multiple records with different parents + throw new \InvalidArgumentException(__d('error', 'primary_link.mismatch')); + } + if($potentialPrimaryLinkRecords->count() === 1) { + return $this->populatedPrimaryLink($potentialPrimaryLink); + } + + // If we didn't find the primary link in the submitted form or API + // request, it might be available via the URL. + + return $this->primaryLinkOnPut(); + } + + /** + * Primary link available via the URL. + * + * @return Object|bool + * @since COmanage Registry v5.0.0 + * + */ + + protected function primaryLinkOnPut(): Object|bool + { + // $this->name = Models + $modelsName = $this->name; + $param = (int)$this->request->getParam('pass.0'); + + // Put = edit, so we should look up the parent ID via the object itself + if ( + empty($param) + || + !$this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action')) + ) { + return false; + } + + return $this->$modelsName->findPrimaryLink($param); + } + + /** + * @param string $potentialPrimaryLink + * + * @return Object|\stdClass + */ + protected function populatedPrimaryLink(string $potentialPrimaryLink): Object + { + // $this->name = Models + $modelsName = $this->name; + // $potentialPrimaryLink will be something like 'attribute_collector_id' + // $potentialPrimaryLinkTable will be something like 'CoreEnroller.AttributeCollectors' + $potentialPrimaryLinkTable = $this->$modelsName->getPrimaryLinkTableName($potentialPrimaryLink); + + // 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 = str_contains($potentialPrimaryLinkTable, '.') + ? StringUtilities::pluginPlugin($potentialPrimaryLinkTable) + : null; + + $cur = new \stdClass(); + $cur->value = $this->request->getQuery($potentialPrimaryLink); + // We found a populated primary link. Store the attribute and break the loop. + $cur->attr = $potentialPrimaryLink; + if($potentialPlugin) { + $cur->plugin = $potentialPlugin; + } + + return $cur; + } + + + /** + * Perform primary link lookup. + * + * @throws \RuntimeException Exception thrown if a primary link is empty and it is not allowed + * @since COmanage Registry v5.0.0 + */ + + protected function primaryLinkLookup(): void + { + // $this->name = Models + $modelsName = $this->name; + $availablePrimaryLinks = $this->$modelsName->getPrimaryLinks(); + + // Iterate over all the potential primary links and pick the appropriate one + foreach($availablePrimaryLinks as $potentialPrimaryLink) { + $cur = match(true) { + $this->request->is('get') => $this->primaryLinkOnGet($potentialPrimaryLink), + ($this->request->is('post') && $this->request->getParam('action') != 'delete') => $this->primaryLinkOnPost($potentialPrimaryLink), + ($this->request->is('put') || $this->request->getParam('action') == 'delete') => $this->primaryLinkOnPut(), + default => false, + }; + + if($cur !== false && $cur->value !== null) { + $this->cur_pl = $cur; + $this->set('vv_primary_link', $this->cur_pl->attr); + // Exit the for loop + break; + } + } // foreach + + // At the end we need to have a Primary Link + if(empty($this->cur_pl->value) + && !$this->$modelsName->allowEmptyPrimaryLink($this->request->getParam('action'))) { + throw new \RuntimeException(__d('error', 'primary_link')); + } + } + + /** + * Collect information about the Standard Object's Primary Link, if set. * The $vv_primary_link view variable is also set. * * @since COmanage Registry v5.0.0 @@ -210,185 +375,73 @@ public function getCOID(): ?int { * @return object Object holding the primary link attribute, and optionally its value * @throws \RuntimeException */ - - public function getPrimaryLink(bool $lookup=false) { + + public function getPrimaryLink(bool $lookup=false): Object + { // Did we already figure this out? (But only if $lookup) if($lookup && isset($this->cur_pl->value)) { return $this->cur_pl; } - + // $this->name = Models $modelsName = $this->name; - // $modelName = Model - $modelName = \Cake\Utility\Inflector::singularize($modelsName); $this->cur_pl = new \stdClass(); - + + if(!(method_exists($this->$modelsName, 'getPrimaryLinks') + && $this->$modelsName->getPrimaryLinks()) + ) { + return $this->cur_pl; + } + // PrimaryLinkTrait - if(method_exists($this->$modelsName, "getPrimaryLinks") - && $this->$modelsName->getPrimaryLinks()) { - // Some models, in particular MVEAs, can have multiple potential primary - // links. In these cases, only one primary link is valid at a time, so we - // have to look through the available primary links and find one. - - $availablePrimaryLinks = $this->$modelsName->getPrimaryLinks(); - - 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($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($potentialPrimaryLinkTable); - } - - if($this->request->is('get')) { - // If this action allows unkeyed, asserted primary link IDs, check the query - // string (eg: 'add' or 'index' allow matchgrid_id to be passed in) - if($this->$modelsName->allowUnkeyedPrimaryLink($this->request->getParam('action')) - && $this->request->getQuery()) { - $this->cur_pl->value = $this->request->getQuery($potentialPrimaryLink); - } elseif($this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action'))) { - // Try to map the requested object ID - $param = (int)$this->request->getParam('pass.0'); - - if(!empty($param)) { - $this->cur_pl = $this->$modelsName->findPrimaryLink($param); - // Break the loop here since we also have the link attribute, - // which might not be $potentialPrimaryLink - $this->set('vv_primary_link', $this->cur_pl->attr); - break; - } - } - } elseif($this->request->is('post') && $this->request->getParam('action') != 'delete') { - // Post = add, where we can have a list of objects and nothing in /objects/{id} - // We don't support different primary links across objects, so we throw an error - // if different parent keys are provided. - - $linkValue = null; - - // Data in API format - $reqData = $this->request->getData($modelsName); - - if(!$reqData - // Don't create $reqData if the POST data is also empty - && !empty($this->request->getData())) { - // Data in POST format - $reqData[] = $this->request->getData(); - } - - if(!empty($reqData)) { - foreach($reqData as $rec) { - if(!empty($rec[$potentialPrimaryLink])) { - if(!$linkValue) { - // This is the first record we've seen, use this primary link value - $linkValue = $rec[$potentialPrimaryLink]; - } elseif($linkValue != $rec[$potentialPrimaryLink]) { - // We don't support multiple records with different parents - throw new \InvalidArgumentException(__d('error', 'primary_link.mismatch')); - } - } - - $this->cur_pl->value = $linkValue; - } - } - - // If we didn't find the primary link in the submitted form or API - // request, it might be available via the URL. - - if(!$linkValue - && $this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action'))) { - // Try to map the requested object ID (this is probably a delete, so no attribute in post body) - $param = (int)$this->request->getParam('pass.0'); - - if(!empty($param)) { - $this->cur_pl = $this->$modelsName->findPrimaryLink($param); - // Break the loop here since we also have the link attribute, - // which might not be $potentialPrimaryLink - $this->set('vv_primary_link', $this->cur_pl->attr); - break; - } - } - } elseif($this->request->is('put') || $this->request->getParam('action') == 'delete') { - // Put = edit, so we should look up the parent ID via the object itself - if($this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action'))) { - // Try to map the requested object ID (this is probably a delete, so no attribute in post body) - $param = (int)$this->request->getParam('pass.0'); - - if(!empty($param)) { - $this->cur_pl = $this->$modelsName->findPrimaryLink($param); - // Break the loop here since we also have the link attribute, - // which might not be $potentialPrimaryLink - $this->set('vv_primary_link', $this->cur_pl->attr); - break; - } - } - } - - 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; - } - } - - if(empty($this->cur_pl->value) - && !$this->$modelsName->allowEmptyPrimaryLink($this->request->getParam('action'))) { - throw new \RuntimeException(__d('error', 'primary_link')); - } - } - - if(!empty($this->cur_pl->value)) { - // Look up the link value to find the related entity - - $linkTableName = $this->$modelsName->getPrimaryLinkTableName($this->cur_pl->attr); - $linkTable = $this->getTableLocator()->get($linkTableName); - - $this->set('vv_primary_link_model', $linkTableName); - - try { - $plObj = $linkTable->findById($this->cur_pl->value)->firstOrFail(); - - $this->set('vv_primary_link_obj', $plObj); - - // While we're here, note the CO since we'll probably need it soon - if(!empty($plObj->co_id)) { - $this->cur_pl->co_id = $plObj->co_id; - } elseif(method_exists($linkTable, "findCoForRecord")) { - $this->cur_pl->co_id = $linkTable->findCoForRecord((int)$this->cur_pl->value); - } - } - catch(RecordNotFoundException $e) { - $this->llog('error', "Could not find value '" . $this->cur_pl->value . "' for primary link object " . $linkTableName); - // Mask this with a generic UnauthorizedException - throw new UnauthorizedException(__d('error', 'perm')); - } + // Some models, in particular MVEAs, can have multiple potential primary + // links. In these cases, only one primary link is valid at a time, so we + // have to look through the available primary links and find one. + + if($lookup) { + $this->primaryLinkLookup(); + } + + if(empty($this->cur_pl->value)) { + return $this->cur_pl; + } + + // Look up the link value to find the related entity + + $linkTableName = $this->$modelsName->getPrimaryLinkTableName($this->cur_pl->attr); + $linkTable = $this->getTableLocator()->get($linkTableName); + + $this->set('vv_primary_link_model', $linkTableName); + + try { + $plObj = $linkTable->findById($this->cur_pl->value)->firstOrFail(); + + $this->set('vv_primary_link_obj', $plObj); + + // While we're here, note the CO since we'll probably need it soon + if(!empty($plObj->co_id)) { + $this->cur_pl->co_id = $plObj->co_id; + } elseif(method_exists($linkTable, 'findCoForRecord')) { + $this->cur_pl->co_id = $linkTable->findCoForRecord((int)$this->cur_pl->value); } } - + catch(RecordNotFoundException $e) { + $this->llog('error', "Could not find value '" . $this->cur_pl->value . "' for primary link object " . $linkTableName); + // Mask this with a generic UnauthorizedException + throw new UnauthorizedException(__d('error', 'perm')); + } + return $this->cur_pl; } - + /** * Get the redirect goal for this table. * + * @param string $action Action + * + * @return string|null Redirect goal * @since COmanage Registry v5.0.0 - * @param string $action Action - * @return string Redirect goal */ protected function getRedirectGoal(string $action): ?string { diff --git a/app/src/Controller/Component/BreadcrumbComponent.php b/app/src/Controller/Component/BreadcrumbComponent.php index ec500e4af..071733ee8 100644 --- a/app/src/Controller/Component/BreadcrumbComponent.php +++ b/app/src/Controller/Component/BreadcrumbComponent.php @@ -57,11 +57,11 @@ class BreadcrumbComponent extends Component // Don't render the parent links protected $skipParentPaths = []; // Inject parent links (these render before the index link, if set) - // The parent links are constructed as part of the injectPrimaryLink function. This in the StandardController as well - // as in the StandardPluginController, MVEAController, ProvisioningHistoryRecordController, etc. These controllers are - // a descendant from the StandardController we will calculate the Parents twice. In order to avoid duplicates the - // injectParents table has to be an associative array. The uniqueness of the key will preserve the uniqueness of the parent - // path while the order of firing will create the correct breadcumb path order + // The parent links are constructed as part of the injectPrimaryLink function, as well as in the StandardController and + // in the StandardPluginController, MVEAController, ProvisioningHistoryRecordController, etc. These controllers are + // a descendant from the StandardController we will calculate the Parents twice. To avoid duplicates, the + // injectParents table has to be an associative array. The uniqueness of the key will preserve the uniqueness of + // the parent path while the order of firing will create the correct breadcrumb path order protected $injectParents = []; // Inject title links (immediately before the title breadcrumb) protected $injectTitleLinks = []; @@ -111,8 +111,9 @@ public function beforeRender(EventInterface $event) { // Do we have a target model, and if so is it a configuration // model (eg: ApiUsers) or an object model (eg: CoPeople)? - if(isset($controller->$modelsName) // May not be set under certain error conditions - && method_exists($controller->$modelsName, "isConfigurationTable")) { + if(\is_object($controller->$modelsName) + && method_exists($controller->$modelsName, "isConfigurationTable") + ) { $controller->set('vv_bc_configuration_link', $controller->$modelsName->isConfigurationTable()); } else { $controller->set('vv_bc_configuration_link', false); diff --git a/app/src/Controller/ExtIdentitySourceRecordsController.php b/app/src/Controller/ExtIdentitySourceRecordsController.php index 5f07920d1..d7e463c20 100644 --- a/app/src/Controller/ExtIdentitySourceRecordsController.php +++ b/app/src/Controller/ExtIdentitySourceRecordsController.php @@ -39,4 +39,18 @@ class ExtIdentitySourceRecordsController extends StandardController { 'ExtIdentitySourceRecords.id' => 'asc' ] ]; + + /** + * Perform Controller initialization. + * + * @since COmanage Registry v5.0.0 + */ + + public function initialize(): void + { + parent::initialize(); + + // Configure breadcrumb rendering + $this->Breadcrumb->skipParents(['/^\/ext-identity-source-records/']); + } } \ No newline at end of file diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index 3b9c68abe..c6f2c3ef1 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -207,12 +207,12 @@ public function beforeRender(\Cake\Event\EventInterface $event) { // Primarily of interest to detailed record views, if this attribute supports // Pipeline sourcing (ie: has a source_foo_id field) set the name of the source // foreign key into a view var since it's not always calculable. - if(method_exists($table, "sourceForeignKey")) { + if(method_exists($table, 'sourceForeignKey')) { $this->set('vv_source_fk', $table->sourceForeignKey()); } // Check to see if the model names a specific layout - if(method_exists($table, "getLayout")) { + if(method_exists($table, 'getLayout')) { $this->viewBuilder()->setLayout($table->getLayout($this->request->getParam('action'))); } diff --git a/app/src/Lib/Traits/PrimaryLinkTrait.php b/app/src/Lib/Traits/PrimaryLinkTrait.php index f0360853d..41e4751e6 100644 --- a/app/src/Lib/Traits/PrimaryLinkTrait.php +++ b/app/src/Lib/Traits/PrimaryLinkTrait.php @@ -464,7 +464,7 @@ public function setCurCoId(int $coId) { } /** - * Set the primary link attribute. Several formats are acceepted: + * Set the primary link attribute. Several formats are accepted: * * 1: ('person_id'): Primary link is to People * 2: (['person_id', 'group_id']): Primary link can be to People _or_ Groups diff --git a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php index b14d55211..c8356f2f4 100644 --- a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php +++ b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php @@ -49,7 +49,19 @@ class ExtIdentitySourceRecordsTable extends Table { use \App\Lib\Traits\SearchFilterTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; - + + /** + * Provide the default layout + * + * @since COmanage Registry v5.0.0 + * @return string Type of redirect + */ + public function getLayout(string $action = ''): string { + return match($action) { + default => 'iframe' + }; + } + /** * Perform Cake Model initialization. * @@ -71,11 +83,11 @@ public function initialize(array $config): void { $this->setDisplayField('source_key'); - $this->setPrimaryLink(['external_identity_source_id']); + $this->setPrimaryLink(['external_identity_source_id', 'external_identity_id']); $this->setRequiresCO(true); // These are required for the link to work from the Artifacts page - $this->setAllowUnkeyedPrimaryCO(['index']); + $this->setAllowUnkeyedPrimaryCO(['index', 'view']); $this->setAllowEmptyPrimaryLink(['index']); $this->setIndexContains([ diff --git a/app/src/Model/Table/ExternalIdentitiesTable.php b/app/src/Model/Table/ExternalIdentitiesTable.php index 62a7cf945..9f945a039 100644 --- a/app/src/Model/Table/ExternalIdentitiesTable.php +++ b/app/src/Model/Table/ExternalIdentitiesTable.php @@ -117,6 +117,7 @@ public function initialize(array $config): void { 'Addresses', 'AdHocAttributes', 'EmailAddresses', + 'ExtIdentitySourceRecords' => ['ExternalIdentitySources'], 'Identifiers', 'Names', //'ExternalIdentityRoles', diff --git a/app/templates/ExternalIdentities/fields-nav.inc b/app/templates/ExternalIdentities/fields-nav.inc index 628cb7353..fc1fa431e 100644 --- a/app/templates/ExternalIdentities/fields-nav.inc +++ b/app/templates/ExternalIdentities/fields-nav.inc @@ -55,17 +55,24 @@ $topLinks = [ ] ] ], - [ +]; + +if($vv_obj?->ext_identity_source_record?->id !== null) { + $topLinks[] = [ 'icon' => 'visibility', 'order' => 'Default', + 'class' => 'cm-modal-link nospin', // launch this in a modal + 'dataAttrs' => [ + ['data-cm-modal-title',__d('controller', 'ExtIdentitySourceRecords', 1)] + ], 'label' => __d('operation', 'view.a', [__d('controller', 'ExtIdentitySourceRecords', 1)]), 'link' => [ 'controller' => 'ext_identity_source_records', 'action' => 'view', $vv_obj->ext_identity_source_record->id ] - ] -]; + ]; +} // $addMenuLinks is also given slightly different treatment from the typical $topLinks found in most views: // it is a page-global menu used for adding MVEAs and is given special treatment in element/mveaCanvas.php. diff --git a/app/templates/ExternalIdentitySources/retrieve.php b/app/templates/ExternalIdentitySources/retrieve.php index af4155e38..b7c0cca94 100644 --- a/app/templates/ExternalIdentitySources/retrieve.php +++ b/app/templates/ExternalIdentitySources/retrieve.php @@ -71,6 +71,10 @@ $action_args['vv_actions'][] = [ 'order' => 3, 'icon' => 'visibility', + 'class' => 'cm-modal-link nospin', // launch this in a modal + 'dataAttrs' => [ + ['data-cm-modal-title',__d('controller', 'ExtIdentitySourceRecords', 1)] + ], 'url' => [ 'controller' => 'ext-identity-source-records', 'action' => 'view', @@ -90,18 +94,26 @@ id)) { + // Construct the link + $link = $this->Html->link( + __d('information', 'ExternalIdentitySources.cached'), + [ + 'controller' => 'ext-identity-source-records', + 'action' => 'view', + $vv_external_identity_record->id + ], + [ + 'class' => 'cm-modal-link nospin', + 'target' => '_top', + 'data-cm-modal-title' => __d('controller', 'ExtIdentitySourceRecords', 1) + ], + ); + + // Construct the message $noticeText = __d( 'information', 'ExternalIdentitySources.retrieve', - [ - $this->Url->build( - [ - 'controller' => 'ext-identity-source-records', - 'action' => 'view', - $vv_external_identity_record->id - ] - ) - ] + $link ); } ?> diff --git a/app/templates/Standard/add-edit-view.php b/app/templates/Standard/add-edit-view.php index bce04253c..fd4386874 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -134,14 +134,13 @@ } if($perm) { - $action_args['vv_actions'][] = [ - 'order' => $this->Menu->getMenuOrder($t['order']), - 'icon' => $this->Menu->getMenuIcon($t['icon']), - 'url' => $t['link'], - 'label' => $t['label'], - 'class' => !empty($t['class']) ? $t['class'] : '', - 'confirm' => !empty($t['confirm']) ? $t['confirm'] : [] - ]; + $action_args['vv_actions'][] = $t; + $key = array_key_last($action_args['vv_actions']); + $action_args['vv_actions'][$key]['order'] = $this->Menu->getMenuOrder($t['order']); + $action_args['vv_actions'][$key]['icon'] = $this->Menu->getMenuIcon($t['icon']); + $action_args['vv_actions'][$key]['url'] = $t['link'] ?? ''; + $action_args['vv_actions'][$key]['class'] = $t['class'] ?? ''; + $action_args['vv_actions'][$key]['confirm'] = $t['confirm'] ?? ''; } }