diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index 5dcc26e72..0b57e8ad6 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -90,9 +90,6 @@ msgstr "Edit" msgid "edit.a" msgstr "Edit {0}" -msgid "edit.ai" -msgstr "Edit {0}" - msgid "ExternalIdentitySourceRecords.retrieve" msgstr "Retrieve from External Identity Source" @@ -186,6 +183,3 @@ msgstr "View" msgid "view.a" msgstr "View {0}" -msgid "view.ai" -msgstr "View {0}" - diff --git a/app/src/Controller/ApiUsersController.php b/app/src/Controller/ApiUsersController.php index 51ef711cd..25a690757 100644 --- a/app/src/Controller/ApiUsersController.php +++ b/app/src/Controller/ApiUsersController.php @@ -29,6 +29,8 @@ namespace App\Controller; +use App\Lib\Util\StringUtilities; + class ApiUsersController extends StandardController { public $paginate = [ 'order' => [ @@ -56,7 +58,10 @@ public function generate(string $id) { // Let the view render, but tell it to use a different fields file $this->set('vv_fields_inc', 'fields-generate.inc'); - $this->set('vv_title', __d('operation', 'api.key.generate')); + [$title, , ] = StringUtilities::entityAndActionToTitle(null, + 'api.key', + $this->request->getParam('action')); + $this->set('vv_title', $title); $this->render('/Standard/add-edit-view'); } diff --git a/app/src/Controller/Component/BreadcrumbComponent.php b/app/src/Controller/Component/BreadcrumbComponent.php index 7bd0399e5..9e960f6a1 100644 --- a/app/src/Controller/Component/BreadcrumbComponent.php +++ b/app/src/Controller/Component/BreadcrumbComponent.php @@ -186,6 +186,20 @@ public function injectPrimaryLink(object $link, bool $index=true, string $linkLa // Construct the getContains function name $requestAction = $this->getController()->getRequest()->getParam('action'); + // 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 fallback to + if(!in_array($requestAction, [ + 'index', 'view', 'delete', 'add', 'edit' + ])) { + $permissionsArray = $this->getController()->RegistryAuth->calculatePermissionsForView($requestAction); + $id = $this->getController()->getRequest()->getParam('pass')[0]; + if (isset($id)) { + $requestAction = ( isset($permissionsArray['edit']) && $permissionsArray['edit'] ) ? 'edit' : 'view'; + } else { + $requestAction = 'index'; + } + } $containsList = "get" . ucfirst($requestAction) . "Contains"; $linkTable = TableRegistry::getTableLocator()->get($modelPath); @@ -226,21 +240,7 @@ public function injectPrimaryLink(object $link, bool $index=true, string $linkLa 'edit'; // The action in the following injectParents dictates the action here - // XXX This is a duplicate from StandardController. - if(method_exists($linkTable, 'generateDisplayField')) { - // We don't use a trait for this since each table will implement different logic - - $label = __d('operation', "{$breadcrumbAction}.ai", $linkTable->generateDisplayField($linkObj)); - } else { - // Default view title is edit object display field - $field = $linkTable->getDisplayField(); - - if(!empty($obj->$field)) { - $label = __d('operation', "{$breadcrumbAction}.ai", $obj->$field); - } else { - $label = __d('operation', "{$breadcrumbAction}.ai", __d('controller', $modelsName, [1])); - } - } + [$title,,] = StringUtilities::entityAndActionToTitle($linkObj, $modelPath, $breadcrumbAction); $this->injectParents[ $linkTable->getTable() . $linkObj->id ] = [ 'target' => [ @@ -249,7 +249,7 @@ public function injectPrimaryLink(object $link, bool $index=true, string $linkLa 'action' => $breadcrumbAction, $linkObj->id ], - 'label' => $linkLabel ?? $label + 'label' => $linkLabel ?? $title ]; } diff --git a/app/src/Controller/DashboardsController.php b/app/src/Controller/DashboardsController.php index 5c66ae07e..916433b76 100644 --- a/app/src/Controller/DashboardsController.php +++ b/app/src/Controller/DashboardsController.php @@ -30,6 +30,7 @@ namespace App\Controller; // XXX not doing anything with Log yet +use App\Lib\Util\StringUtilities; use Cake\Log\Log; use Cake\ORM\TableRegistry; use Cake\Utility\Hash; @@ -66,8 +67,12 @@ public function initialize(): void { public function artifacts() { $cur_co = $this->getCO(); - - $this->set('vv_title', __d('menu', 'artifacts', [$cur_co->name])); + + [$title, , ] = StringUtilities::entityAndActionToTitle($cur_co, + 'artifacts', + $this->request->getParam('action'), + 'menu'); + $this->set('vv_title', $title); // Construct the set of primary registry objects, which // we want to order by the localized text string. @@ -102,8 +107,12 @@ public function artifacts() { public function configuration() { $cur_co = $this->getCO(); - - $this->set('vv_title', __d('operation', 'dashboard.configuration', $cur_co->name)); + + [$title, , ] = StringUtilities::entityAndActionToTitle($cur_co, + 'dashboard', + $this->request->getParam('action'), + ); + $this->set('vv_title', $title); // Construct the set of configuration items. For everything except CO Settings // we want to order by the localized text string. @@ -213,8 +222,12 @@ public function dashboard(?int $id=null) { public function registries() { $cur_co = $this->getCO(); - - $this->set('vv_title', __d('menu', 'registries', [$cur_co->name])); + + [$title, , ] = StringUtilities::entityAndActionToTitle($cur_co, + 'registries', + $this->request->getParam('action'), + 'menu'); + $this->set('vv_title', $title); // Construct the set of primary registry objects, which // we want to order by the localized text string. @@ -431,6 +444,12 @@ public function search() { } $this->set('vv_results', $results); - $this->set('vv_title', __d('result', 'search.results')); + // XXX The action is search and the result is not a modelPath. In this use the pattern is reversed. We should + // probably reconsider the po naming for the result domain + [$title, , ] = StringUtilities::entityAndActionToTitle(null, + 'search', + 'results', + 'result'); + $this->set('vv_title', $title); } } \ No newline at end of file diff --git a/app/src/Controller/ExternalIdentitySourcesController.php b/app/src/Controller/ExternalIdentitySourcesController.php index 53ab3bcc9..f32718045 100644 --- a/app/src/Controller/ExternalIdentitySourcesController.php +++ b/app/src/Controller/ExternalIdentitySourcesController.php @@ -30,6 +30,7 @@ namespace App\Controller; // XXX not doing anything with Log yet +use App\Lib\Util\StringUtilities; use Cake\Log\Log; use Cake\ORM\TableRegistry; @@ -109,15 +110,19 @@ public function retrieve(string $id) { $this->set('vv_eis_record', $this->ExternalIdentitySources->retrieve((int)$id, $source_key)); - $this->set('vv_external_identity_record', $this->ExternalIdentitySources - ->ExtIdentitySourceRecords - ->find() - ->where(['ExtIdentitySourceRecords.source_key' => $source_key, - 'ExtIdentitySourceRecords.external_identity_source_id' => $id]) - ->contain(['ExternalIdentities']) - ->first()); - - $this->set('vv_title', __d('operation', 'view.a', [$source_key])); + $externalIdentityRecordObj = $this->ExternalIdentitySources + ->ExtIdentitySourceRecords + ->find() + ->where(['ExtIdentitySourceRecords.source_key' => $source_key, + 'ExtIdentitySourceRecords.external_identity_source_id' => $id]) + ->contain(['ExternalIdentities']) + ->first(); + $this->set('vv_external_identity_record', $externalIdentityRecordObj); + + [$title, , ] = StringUtilities::entityAndActionToTitle($externalIdentityRecordObj, + StringUtilities::entityToClassName($externalIdentityRecordObj), + 'view'); + $this->set('vv_title', $title); } catch(\Exception $e) { $this->Flash->error($e->getMessage()); @@ -152,7 +157,10 @@ public function search(string $id) { $this->set('vv_search_attrs', $this->ExternalIdentitySources->searchableAttributes((int)$id)); - $this->set('vv_title', __d('operation', 'ExternalIdentitySources.search')); + [$title, , ] = StringUtilities::entityAndActionToTitle(null, + $this->getName(), + $this->request->getParam('action')); + $this->set('vv_title', $title); } /** @@ -167,9 +175,8 @@ public function sync(string $id) { $source_key = $this->request->getQuery('source_key'); $this->ExternalIdentitySources->sync((int)$id, $source_key); + // XXX Sync does not have a view. We do not need to set a title. Yet need to fetch the updated Identity $this->set('vv_eis_record', $this->ExternalIdentitySources->retrieve((int)$id, $source_key)); - - $this->set('vv_title', __d('operation', 'view.a', [$source_key])); $this->Flash->success(__d('result', 'ExternalIdentitySources.synced')); } diff --git a/app/src/Controller/ProvisioningTargetsController.php b/app/src/Controller/ProvisioningTargetsController.php index 0f9f8fef2..27633787f 100644 --- a/app/src/Controller/ProvisioningTargetsController.php +++ b/app/src/Controller/ProvisioningTargetsController.php @@ -30,6 +30,8 @@ namespace App\Controller; // XXX not doing anything with Log yet +use App\Lib\Util\StringUtilities; +use Cake\Utility\Inflector; use Cake\Log\Log; use Cake\ORM\TableRegistry; @@ -85,17 +87,21 @@ public function status() { // PrimaryLinkTrait - Look up our primary link to see which object type we're // working with, an also get our CO ID $link = $this->getPrimaryLink(true); - - if($link->attr == 'person_id') { - $statuses = $this->ProvisioningTargets->status(coId: $link->co_id, personId: (int)$link->value); - } elseif($link->attr == 'group_id') { - $statuses = $this->ProvisioningTargets->status(coId: $link->co_id, groupId: (int)$link->value); - } + // Use argument unpacking operator with names parameters in order to make the call more dynamic + $statusCalculateParams = [ + 'coId' => $link->co_id, + // Currently supported function parameters are personId, groupId + Inflector::variable($link->attr) => (int)$link->value + ]; + $statuses = $this->ProvisioningTargets->status(...$statusCalculateParams); $this->set('vv_provisioning_statuses', $statuses); if(!$this->request->is('restful')) { - $this->set('vv_title', __d('operation', 'provisioning.status')); + [$title, , ] = StringUtilities::entityAndActionToTitle(null, + 'provisioning', + $this->request->getParam('action')); + $this->set('vv_title', $title); } } } \ No newline at end of file diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index c2ec6813a..808f6214b 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -52,6 +52,8 @@ public function add() { $table = $this->$modelsName; // $tableName = models $tableName = $table->getTable(); + // Create an empty entity for FormHelper + $obj = $table->newEmptyEntity(); if($this->request->is('post')) { try { @@ -100,14 +102,10 @@ public function add() { $this->Flash->error($e->getMessage()); } - - // Pass $obj as context so the view can render validation errors - $this->set('vv_obj', $obj); - } else { - // Create an empty entity for FormHelper - - $this->set('vv_obj', $table->newEmptyEntity()); } + + // Pass $obj as context so the view can render validation errors + $this->set('vv_obj', $obj); // PrimaryLinkTrait, via AppController $this->getPrimaryLink(); @@ -116,13 +114,10 @@ public function add() { $this->populateAutoViewVars(); // Default title is add new object - $this->set('vv_title', __d('operation', 'add.a', __d('controller', $modelsName, [1]))); - - // Supertitle is normally the display name of the parent object when subnavigation exists. - // Set this here as the fallback default. This value is overriden in MVEAController to hold the - // name of the parent object, not the model name of the current object. - // TODO: set this to a better value for other kinds of child objects (e.g. Group member) - $this->set('vv_supertitle', __d('controller', $modelsName, [1])); + [$title, $supertitle, $subtitle] = StringUtilities::entityAndActionToTitle($obj, $modelsName, 'add'); + $this->set('vv_title', $title); + $this->set('vv_supertitle', $supertitle); + $this->set('vv_subtitle', $subtitle); // Let the view render $this->render('/Standard/add-edit-view'); @@ -411,24 +406,13 @@ public function edit(string $id) { // AutoViewVarsTrait $this->populateAutoViewVars($obj); - - if(method_exists($table, 'generateDisplayField')) { - // We don't use a trait for this since each table will implement different logic - - $this->set('vv_title', __d('operation', 'edit.ai', $table->generateDisplayField($obj))); - $this->set('vv_supertitle', $table->generateDisplayField($obj)); - // Pass the display field also into subtitle for dealing with External IDs - $this->set('vv_subtitle', $table->generateDisplayField($obj)); - } else { - // Default view title is edit object display field - $field = $table->getDisplayField(); - - if(!empty($obj->$field)) { - $this->set('vv_title', __d('operation', 'edit.ai', $obj->$field)); - } else { - $this->set('vv_title', __d('operation', 'edit.ai', __d('controller', $modelsName, [1]))); - } - } + + // Calculate and set title, supertitle and subtitle + [$title, $supertitle, $subtitle] = StringUtilities::entityAndActionToTitle($obj, $modelsName, 'edit'); + + $this->set('vv_title', $title); + $this->set('vv_supertitle', $supertitle); + $this->set('vv_subtitle', $subtitle); // Let the view render $this->render('/Standard/add-edit-view'); @@ -646,7 +630,8 @@ public function index() { $this->set('vv_permission_set', $this->RegistryAuth->calculatePermissionsForResultSet($resultSet)); // Default index view title is model name - $this->set('vv_title', __d('controller', $modelsName, [99])); + [$title, , ] = StringUtilities::entityAndActionToTitle($resultSet, $modelsName, 'index'); + $this->set('vv_title', $title); // Let the view render $this->render('/Standard/index'); @@ -915,24 +900,13 @@ public function view($id = null) { // AutoViewVarsTrait // We still used this in view() to map select values $this->populateAutoViewVars($obj); - - if(method_exists($table, 'generateDisplayField')) { - // We don't use a trait for this since each table will implement different logic - - $this->set('vv_title', __d('operation', 'view.ai', $table->generateDisplayField($obj))); - $this->set('vv_supertitle', $table->generateDisplayField($obj)); - // Pass the display field also into subtitle for dealing with External IDs - $this->set('vv_subtitle', $table->generateDisplayField($obj)); - } else { - // Default view title is the object display field - $field = $table->getDisplayField(); - - if(!empty($obj->$field)) { - $this->set('vv_title', __d('operation', 'view.ai', $obj->$field)); - } else { - $this->set('vv_title', __d('operation', 'view.ai', __d('controller', $modelsName, [1]))); - } - } + + // Calculate and set title, supertitle and subtitle + [$title, $supertitle, $subtitle] = StringUtilities::entityAndActionToTitle($obj, $modelsName, 'view'); + + $this->set('vv_title', $title); + $this->set('vv_supertitle', $supertitle); + $this->set('vv_subtitle', $subtitle); // Let the view render $this->render('/Standard/add-edit-view'); diff --git a/app/src/Controller/StandardPluginController.php b/app/src/Controller/StandardPluginController.php index 4a5553948..b15b29a9a 100644 --- a/app/src/Controller/StandardPluginController.php +++ b/app/src/Controller/StandardPluginController.php @@ -98,7 +98,8 @@ public function beforeRender(\Cake\Event\EventInterface $event) { // Override the title set in StandardController. Since that was set in edit() // which is called before the rendering hooks, this title will take precedence. - $this->set('vv_title', __d('operation', 'configure.a', $parentObj->$parentDisplayField)); + [$title, , ] = StringUtilities::entityAndActionToTitle($parentObj, $parentClassName, 'configure'); + $this->set('vv_title', $title); } return parent::beforeRender($event); diff --git a/app/src/Lib/Util/StringUtilities.php b/app/src/Lib/Util/StringUtilities.php index b4f959ee4..5b9249846 100644 --- a/app/src/Lib/Util/StringUtilities.php +++ b/app/src/Lib/Util/StringUtilities.php @@ -29,6 +29,7 @@ namespace App\Lib\Util; +use Cake\ORM\TableRegistry; use \Cake\Utility\Inflector; class StringUtilities { @@ -125,6 +126,81 @@ public static function entityToForeignKey($entity): string { return Inflector::underscore(Inflector::singularize(substr($classPath, strrpos($classPath, '\\')+1))) . "_id"; } + /** + * Construct the title, supertitle and subtitle for a given Model and action + * + * - if the Entity is null then we construct the message ID by concatenating the modelPath and the action + * - if the action is null then the message ID is the modelsName + * - if the action is the Index View then the message ID is the modelsName + "others", which in this case is the plural of the name + * - in all other cases the message id is constructed by the displayField. Either it is defined or dynamically + * constructed + * + * @param Entity|null $entity Entity object + * @param string $modelPath The path of the Model, from core Models it is the Model Name. For plugins it is the Plugin.ModelName + * @param string|null $action Request Action + * @param string $domain The po file the message ID is located in + * + * @return array List of title, supertitle, subtitle + */ + public static function entityAndActionToTitle($entity, + string $modelPath, + ?string $action, + string $domain='operation'): array { + $supertitle = ''; + $subtitle = ''; + $title = ''; + + if($entity === null) { + return [__d($domain, "{$modelPath}.{$action}"), '', '']; + } + + $plugin = ''; + $modelsName = $modelPath; + if(str_contains($modelPath, '.')) { + [$plugin, $modelsName] = explode('.', $modelPath, 2); + } + + $linkTable = TableRegistry::getTableLocator()->get($modelPath); + $msgId = "{$action}.a"; + + if(Inflector::singularize(self::entityToClassName($entity)) !== Inflector::singularize($modelsName)) { + $linkTable = TableRegistry::getTableLocator()->get(self::entityToClassName($entity)); + // if the modelPath and the action are equal then we skip the concatenation + $msgId = $modelPath === $action ? $modelPath : "{$modelPath}.{$action}"; + } + + if($action === null) { + return [__d('controller', $modelsName), '', '']; + } + + // Index view + if($action === 'index') { + // 99 is the default for plural + return [__d('controller', $modelsName, [99]), '', '']; + } + + // Add/Edit/View + if(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)); + $supertitle = $linkTable->generateDisplayField($entity); + // Pass the display field also into subtitle for dealing with External IDs + $subtitle = $linkTable->generateDisplayField($entity); + } else { + // Default view title is edit object display field + $field = $linkTable->getDisplayField(); + + if(!empty($entity->$field)) { + $title = __d($domain, $msgId, $entity->$field); + } else { + $title = __d($domain, $msgId, __d('controller', $modelsName, [1])); + } + } + + return [$title, $supertitle, $subtitle]; + } + /** * Determine the class name from a foreign key (eg: report_id -> Reports). *