diff --git a/app/plugins/CoreEnroller/templates/ApprovalCollectors/dispatch.inc b/app/plugins/CoreEnroller/templates/ApprovalCollectors/dispatch.inc index 62375a0fb..2837b5bdc 100644 --- a/app/plugins/CoreEnroller/templates/ApprovalCollectors/dispatch.inc +++ b/app/plugins/CoreEnroller/templates/ApprovalCollectors/dispatch.inc @@ -63,7 +63,7 @@ $this->Field->enableFormEditMode(); ] ); - print $this->element('form/listItem', [ + print $this->element('CoreEnroller.listItem', [ 'arguments' => [ 'fieldName' => 'comment', 'fieldOptions' => [ diff --git a/app/plugins/CoreEnroller/templates/BasicAttributeCollectors/dispatch.inc b/app/plugins/CoreEnroller/templates/BasicAttributeCollectors/dispatch.inc index 8333415ae..e823825e6 100644 --- a/app/plugins/CoreEnroller/templates/BasicAttributeCollectors/dispatch.inc +++ b/app/plugins/CoreEnroller/templates/BasicAttributeCollectors/dispatch.inc @@ -59,7 +59,7 @@ $this->Field->enableFormEditMode(); element('form/listItem', [ + print $this->element('CoreEnroller.listItem', [ 'arguments' => [ 'fieldName' => 'mail', 'fieldOptions' => [ diff --git a/app/plugins/CoreEnroller/templates/InvitationAccepters/dispatch.inc b/app/plugins/CoreEnroller/templates/InvitationAccepters/dispatch.inc index c3f704581..0e9a2b328 100644 --- a/app/plugins/CoreEnroller/templates/InvitationAccepters/dispatch.inc +++ b/app/plugins/CoreEnroller/templates/InvitationAccepters/dispatch.inc @@ -36,7 +36,7 @@ if($vv_action == 'dispatch') { // Make the Form fields editable $this->Field->enableFormEditMode(); - print $this->element('form/listItem', [ + print $this->element('CoreEnroller.listItem', [ 'arguments' => [ 'fieldName' => 'accepted', 'fieldLabel' => __d('operation','accept.invitation'), diff --git a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php index ce25aa05a..080bd9b6a 100644 --- a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php +++ b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php @@ -72,7 +72,7 @@ print __d('core_enroller', 'information.EmailVerifiers.code_sent', [$vv_verify_address]); -print $this->element('form/listItem', [ +print $this->element('CoreEnroller.listItem', [ 'arguments' => [ 'fieldName' => 'code', 'fieldLabel' => __d('field', 'code'), diff --git a/app/plugins/CoreEnroller/templates/element/field.php b/app/plugins/CoreEnroller/templates/element/field.php index 80a3b8cd4..9166f9ca7 100644 --- a/app/plugins/CoreEnroller/templates/element/field.php +++ b/app/plugins/CoreEnroller/templates/element/field.php @@ -119,5 +119,5 @@ 'formArguments' => $formArguments ]), // Default use case - default => $this->Field->getElementsForDisabledInput('form/listItem', $formArguments) + default => $this->Field->getElementsForDisabledInput('CoreEnroller.listItem', $formArguments) }; diff --git a/app/plugins/CoreEnroller/templates/element/fieldDiv.php b/app/plugins/CoreEnroller/templates/element/fieldDiv.php new file mode 100644 index 000000000..0f1c84764 --- /dev/null +++ b/app/plugins/CoreEnroller/templates/element/fieldDiv.php @@ -0,0 +1,75 @@ + + +
+ element('form/nameDiv'); + + // This configuration isn't necessary anymore. + if(isset($vv_field_arguments['fieldDescription'])) { + unset($vv_field_arguments['fieldDescription']); + $this->set('vv_field_arguments', $vv_field_arguments); + } + + // Info Div + ?> +
+ element('form/infoDiv/withPrefix'); + } elseif(isset($vv_field_arguments['autocomplete'])) { + print $this->element('form/infoDiv/autocomplete'); + } elseif(isset($vv_field_arguments['status'])) { + print $this->element('form/infoDiv/status'); + } elseif(isset($vv_field_arguments['groupedControls'])) { + print $this->element('form/infoDiv/grouped'); + } elseif(isset($vv_field_arguments['entity'])) { + print $this->element('form/infoDiv/source'); + } elseif(isset($vv_field_arguments['groupmember'])) { + print $this->element('form/infoDiv/groupMember'); + } else { + print $this->element('form/infoDiv/default'); + } + + // Insert the afterField supplement: + if(!empty($vv_after_field)) { + print $vv_after_field; + } + ?> +
+
\ No newline at end of file diff --git a/app/plugins/CoreEnroller/templates/element/listItem.php b/app/plugins/CoreEnroller/templates/element/listItem.php new file mode 100644 index 000000000..7c02fd4f3 --- /dev/null +++ b/app/plugins/CoreEnroller/templates/element/listItem.php @@ -0,0 +1,88 @@ +set('fieldName', $arguments['fieldName']); + $fieldName = $arguments['fieldName']; + $this->set('vv_field_arguments', $arguments); + + // Pass along the field supplements if they are configured. + $this->set('vv_before_field', $beforeField ?? ''); + $this->set('vv_after_field', $afterField ?? ''); + + // If an attribute is frozen, inject a special link to unfreeze it, since + // the attribute is read-only and the admin can't simply uncheck the setting + if($fieldName == 'frozen' && $this->Field->getEntity()->frozen) { + $url = [ + 'label' => __d('operation', 'unfreeze'), + 'url' => [ + 'plugin' => null, + 'controller' => \App\Lib\Util\StringUtilities::entityToClassname($this->Field->getEntity()), + 'action' => 'unfreeze', + $this->Field->getEntity()->id + ] + ]; + $arguments = [ + ...$arguments, + 'status' => __d('field', 'frozen'), + 'link' => $url, + ]; + $this->set('vv_field_arguments', $arguments); + } + + // If an attribute is a plugin, return the link to its configuration + if($fieldName == 'plugin' && $vv_action == 'edit') { + $url = [ + 'label' => __d('operation', 'configure.plugin'), + 'url' => [ + 'plugin' => null, + 'controller' => \App\Lib\Util\StringUtilities::entityToClassname($this->Field->getEntity()), + 'action' => 'configure', + $this->Field->getEntity()->id + ] + ]; + $arguments = [ + ...$arguments, + 'status' => $this->Field->getEntity()->$fieldName, + 'link' => $url, + ]; + $this->set('vv_field_arguments', $arguments); + } + +?> + +
  • + element('CoreEnroller.fieldDiv')?> +
  • diff --git a/app/plugins/CoreEnroller/templates/element/unorderedList.php b/app/plugins/CoreEnroller/templates/element/unorderedList.php new file mode 100644 index 000000000..d0633c6f1 --- /dev/null +++ b/app/plugins/CoreEnroller/templates/element/unorderedList.php @@ -0,0 +1,68 @@ + + + + $v) { + print $this->Form->hidden($attr, ['value' => $v]); + } + } \ No newline at end of file diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index 76450899b..a8f59a5e5 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -273,14 +273,10 @@ public function getCOID(): ?int { */ public function getCurrentTable(): \Cake\ORM\Table { - /** @var string $modelsName */ $modelsName = $this->getName(); + $plugin = $this->getPlugin(); - $alias = $this->getPlugin() !== null - ? $this->getPlugin() . '.' . $modelsName - : $modelsName; - - return $this->fetchTable($alias); + return $this->fetchTable(StringUtilities::getQualifiedName($plugin, $modelsName)); } diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index 4a3326b36..d0a13334c 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -686,7 +686,7 @@ public function index() { $this->set('vv_permission_set', $this->RegistryAuth->calculatePermissionsForResultSet($resultSet)); // Default index view title is model name - [$title, , ] = StringUtilities::entityAndActionToTitle($resultSet, $modelsName, 'index'); + [$title, , ] = StringUtilities::entityAndActionToTitle(null, $modelsName, 'index'); $this->set('vv_title', $title); // Let the view render diff --git a/app/src/Controller/StandardEnrollerController.php b/app/src/Controller/StandardEnrollerController.php index c7f6e2124..b95dc8dcd 100644 --- a/app/src/Controller/StandardEnrollerController.php +++ b/app/src/Controller/StandardEnrollerController.php @@ -74,11 +74,45 @@ public function beforeRender(\Cake\Event\EventInterface $event) { if(!empty($link->value)) { $currentTable = $this->getCurrentTable(); - $efsTable = $currentTable->getAssociation('EnrollmentFlowSteps')->getTarget(); - - $this->set('vv_bc_parent_obj', $efsTable->get($link->value)); - $this->set('vv_bc_parent_displayfield', $efsTable->getDisplayField()); - $this->set('vv_bc_parent_primarykey', $efsTable->getPrimaryKey()); + // Not all enroller plugin tables have a direct EnrollmentFlowSteps association + if (method_exists($currentTable, 'hasAssociation') + && $currentTable->hasAssociation('EnrollmentFlowSteps')) { + + $efsTable = $currentTable->getAssociation('EnrollmentFlowSteps')->getTarget(); + + $this->set('vv_bc_parent_obj', $efsTable->get($link->value)); + $this->set('vv_bc_parent_displayfield', $efsTable->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $efsTable->getPrimaryKey()); + } else { + // Two-hop case: foreign key from $link (e.g. attribute_collector_id) + // -> AttributeCollectors + // -> EnrollmentFlowSteps + // Useful for deeply nested enrollment configuration objects. + if (!empty($link->attr)) { + // Derive the table class name from the foreign key name + $fkClassName = StringUtilities::foreignKeyToClassName($link->attr); // e.g. 'AttributeCollectors' + + // Qualify with plugin if present + $tableAlias = !empty($link->plugin) + ? $link->plugin . '.' . $fkClassName // e.g. 'CoreEnroller.AttributeCollectors' + : $fkClassName; + + $attributeCollectorsTable = TableRegistry::getTableLocator()->get($tableAlias); + + // Load the intermediate object (AttributeCollector, in your case) + $collector = $attributeCollectorsTable->get((int)$link->value); + + // From the AttributeCollector, go up to the EnrollmentFlowStep + if (!empty($collector->enrollment_flow_step_id)) { + $efsTable = TableRegistry::getTableLocator()->get('EnrollmentFlowSteps'); + $step = $efsTable->get((int)$collector->enrollment_flow_step_id); + + $this->set('vv_bc_parent_obj', $step); + $this->set('vv_bc_parent_displayfield', $efsTable->getDisplayField()); + $this->set('vv_bc_parent_primarykey', $efsTable->getPrimaryKey()); + } + } + } } return parent::beforeRender($event); diff --git a/app/src/Lib/Util/StringUtilities.php b/app/src/Lib/Util/StringUtilities.php index 0fda489e5..1f24cf1ca 100644 --- a/app/src/Lib/Util/StringUtilities.php +++ b/app/src/Lib/Util/StringUtilities.php @@ -204,6 +204,17 @@ public static function entityToClassName($entity): string { // $classPath will be something like App\Model\Entity\Name, but we want to return "Names" $classPath = get_class($entity); + return self::classPathClassName($classPath); + } + + /** + * Extracts and pluralizes the class name from a fully qualified class path. + * + * @param string $classPath Fully qualified class path (eg: App\Model\Entity\Name) + * @return string Pluralized class name (eg: Names) + * @since COmanage Registry v5.2.0 + */ + public static function classPathClassName(string $classPath): string { return Inflector::pluralize(substr($classPath, strrpos($classPath, '\\')+1)); } @@ -285,13 +296,15 @@ public static function entityAndActionToTitle($entity, [$plugin, $modelsName] = explode('.', $modelPath, 2); } - if($entity == null && !empty($plugin)) { - $count = $action == 'index' ? 99 : 1; - return [__d($domain, "controller.$modelsName", [$count]), '', '']; - } elseif($entity === null) { - $count = $action == 'index' ? 99 : 1; - return [__d($domain, "{$modelPath}.{$action}", [$count]), '', '']; + // Index view → use the controller plural form (token 99 convention) + if($action === 'index') { + if(!empty($plugin)) { + $domain = StringUtilities::pluginToTextDomain($plugin); + return [__d($domain, "controller.$modelsName", [99]), '', '']; + } + return [__d('controller', $modelsName, [99]), '', '']; } + // Base table and default message IDs for translation $linkTable = TableRegistry::getTableLocator()->get($modelPath); $msgId = "{$action}.a"; // eg: "edit.a" @@ -308,25 +321,20 @@ public static function entityAndActionToTitle($entity, // If the entity actually belongs to a different model than the provided $modelsName, // switch to that table and adjust the default message id pattern accordingly. // This is necessary for TAB oriented views - if(Inflector::singularize(self::entityToClassName($entity)) !== Inflector::singularize($modelsName)) { + if( + $entity !== null + && Inflector::singularize(self::entityToClassName($entity)) !== Inflector::singularize($modelsName) + ) { $linkTable = TableRegistry::getTableLocator()->get(self::entityToClassName($entity)); // If modelPath and action are equal, don’t concatenate (preserve legacy behavior) $msgId = $modelPath === $action ? $modelPath : "{$modelPath}.{$action}"; } - // 2) No action → default to the controller label for the model (singular) + // No action → default to the controller label for the model (singular) if($action === null) { return [__d('controller', $modelsName), '', '']; } - // 3) Index view → use the controller plural form (token 99 convention) - if($action === 'index') { - if(!empty($plugin)) { - return [__d($domain, "controller.$modelsName", [99]), '', '']; - } - return [__d('controller', $modelsName, [99]), '', '']; - } - // Add/Edit/View // The MVEA Models have an entityId. The one from the parent model. // We need to have a condition for this and exclude it. @@ -338,7 +346,7 @@ public static function entityAndActionToTitle($entity, $display = $entity->$field ?? null; } - // 6) Edit/View-like case for an existing entity with a usable display + // Edit/View-like case for an existing entity with a usable display // Title: translate with override key first; if not found, fall back to default key. // Super/Sub titles: set to the display (needed for External IDs in UI). if ( @@ -354,7 +362,7 @@ public static function entityAndActionToTitle($entity, return [$title, $supertitle, $subtitle]; } - // 7) Fallbacks: + // Fallbacks: // - New entities (no id), // - Add/Delete actions, // - Or we simply lack a display. @@ -372,7 +380,7 @@ public static function entityAndActionToTitle($entity, * @param string $domain Translation domain to use * @param string $overrideKey Primary translation key to try first * @param string $fallbackKey Fallback translation key if override not found - * @param string $value Value to substitute in translation + * @param string|int $value Value to substitute in translation * @return string Translated string using either override or fallback key * @since COmanage Registry v5.2.0 */ @@ -415,6 +423,22 @@ public static function foreignKeyToController(string $s): string { return Inflector::underscore(Inflector::pluralize(substr($s, 0, strlen($s)-3))); } + + /** + * Get the fully qualified name by combining plugin and name with a dot separator. + * + * @param string|null $plugin Plugin name, or null if no plugin + * @param string $name Base name to qualify + * @return string Qualified name in format "Plugin.Name" or just "Name" if no plugin + * @since COmanage Registry v5.2.0 + */ + public static function getQualifiedName(?string $plugin, string $name): string + { + return $plugin !== null && $plugin !== '' + ? $plugin . '.' . $name + : $name; + } + /** * Localize a controller name, accounting for plugins. * @@ -452,7 +476,7 @@ public static function qualifyModelPath(string $modelPath, ?string $plugin): str if (empty($plugin) || str_starts_with($modelPath, $plugin . '.')) { return $modelPath; } - return $plugin . '.' . $modelPath; + return self::getQualifiedName($plugin, $modelPath);; } /** diff --git a/app/templates/EnrollmentFlows/start.inc b/app/templates/EnrollmentFlows/start.inc index b4941bc5a..6026703ea 100644 --- a/app/templates/EnrollmentFlows/start.inc +++ b/app/templates/EnrollmentFlows/start.inc @@ -38,14 +38,11 @@ $modelTable = $this->Tab->getModelTableReference($modelsName); // For now, we just request the enrollee email. We're only called if // collect_enrollee_email is true, so we don't need to check it, though // if we ever add more fields to the start form we should. -print $this->element('form/listItem', [ - 'arguments' => [ - 'fieldName' => 'enrollee_email', - 'fieldOptions' => [ - 'required' => true - ], +$fields = [ + 'enrollee_email' => [ + 'required' => true, 'fieldType' => 'string' ] -]); +]; // Any hidden fields?? diff --git a/app/templates/EnrollmentFlows/start.php b/app/templates/EnrollmentFlows/start.php index 7dd28ede7..b3ead8908 100644 --- a/app/templates/EnrollmentFlows/start.php +++ b/app/templates/EnrollmentFlows/start.php @@ -27,6 +27,11 @@ declare(strict_types = 1); +// Enrollment Flow Start has its own file of fields +$fields = []; +$modelsName = $this->getName(); +$templatePath = $vv_template_path ?? ROOT . DS . "templates" . DS . $modelsName; +include($templatePath . DS . 'start.inc'); ?>
    @@ -38,8 +43,6 @@ element('flash') // Flash messages ?> set('vv_fields_inc', 'start.inc'); // Set form edit ability $this->set('vv_is_editable', true); @@ -49,7 +52,7 @@ 'type' => 'post', ]); // Form body -print $this->element('form/unorderedList'); +print $this->element('form/unorderedList', ['vv_fields' => $fields]); // Close the Form print $this->Form->end(); diff --git a/app/templates/Standard/dispatch.php b/app/templates/Standard/dispatch.php index 84d2e3352..c69be9295 100644 --- a/app/templates/Standard/dispatch.php +++ b/app/templates/Standard/dispatch.php @@ -81,7 +81,7 @@ // Form body print '
    '; -print $this->element('form/unorderedList'); +print $this->element('CoreEnroller.unorderedList'); print '
    '; // Inject the Petition ID into the form, though it will most likely