From 3a1f8092052a105febbdcf924247d6aba49feceb Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 27 Aug 2026 12:32:29 +0300 Subject: [PATCH 1/3] Include TermsAndConditions and MostlyStaticPages in People provisioning data contain and fix T&C inflection rules --- app/config/bootstrap.php | 2 +- app/src/Model/Table/PeopleTable.php | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/config/bootstrap.php b/app/config/bootstrap.php index f4dcc6ecc..0f4c9f185 100644 --- a/app/config/bootstrap.php +++ b/app/config/bootstrap.php @@ -253,7 +253,7 @@ \Cake\Utility\Inflector::rules('uninflected', ['cous' => 'cous']); \Cake\Utility\Inflector::rules('irregular', ['meta' => 'meta']); // \Cake\Utility\Inflector::rules('irregular', ['terms_and_condition' => 'terms_and_conditions']); -\Cake\Utility\Inflector::rules('uninflected', ['terms_and_conditions' => 'terms_and_conditions']); +\Cake\Utility\Inflector::rules('uninflected', ['TermsAndConditions', 'terms_and_conditions']); /* * Define some constants diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 4d45589a2..80f08c4dd 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -506,7 +506,13 @@ public function marshalProvisioningData(int $id): array { 'Types' ], 'Pronouns', - 'TAndCAgreements', + 'TAndCAgreements' => [ + 'TermsAndConditions' => [ + // Pull the backing document for provisioning (T&C uses either a URL + // or a Mostly Static Page) + 'MostlyStaticPages', + ] + ], 'TelephoneNumbers' => [ 'Types' ], 'Urls' => [ 'Types' ] ] From d0ed0a2bcc3004de96978c4ad7657c2ad38b2a65 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 1 Sep 2026 17:30:27 +0300 Subject: [PATCH 2/3] Resolve Terms and Conditions URLs via entity virtual fields and streamline provisioning --- .../src/Model/Table/SqlProvisionersTable.php | 63 ++++++------------- app/src/Model/Entity/TAndCAgreement.php | 51 +++++++++++++++ app/src/Model/Entity/TermsAndConditions.php | 31 +++++++++ app/src/Model/Table/PeopleTable.php | 13 ++-- 4 files changed, 108 insertions(+), 50 deletions(-) diff --git a/app/availableplugins/SqlConnector/src/Model/Table/SqlProvisionersTable.php b/app/availableplugins/SqlConnector/src/Model/Table/SqlProvisionersTable.php index cad696d51..1151e215e 100644 --- a/app/availableplugins/SqlConnector/src/Model/Table/SqlProvisionersTable.php +++ b/app/availableplugins/SqlConnector/src/Model/Table/SqlProvisionersTable.php @@ -29,6 +29,7 @@ namespace SqlConnector\Model\Table; +use App\Model\Entity\ProvisioningTarget; use Cake\Datasource\ConnectionManager; use Cake\ORM\RulesChecker; use Cake\ORM\Table; @@ -230,8 +231,7 @@ class SqlProvisionersTable extends Table { 'source' => 'TermsAndConditions', 'source_table' => 'terms_and_conditions', 'related' => [], - 'archives' => true, - 'callback' => 'processTAndC' + 'archives' => true ], 'Types' => [ 'table' => 'types', @@ -372,39 +372,16 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour return true; } - /** - * Process T&C for export, by converting Mostly Static Pages to their URLs. - * - * @since COmanage Registry v5.3.0 - * @param array $row Result row from query - */ - - protected function processTAndC(array $row) { - if(!empty($row['mostly_static_page_id']) - // If both a URL and MSP are specified, the URL takes precedence - && empty($row['url'])) { - // Map the MSP to its URL and include that instead - $MSPTable = TableRegistry::getTableLocator()->get("MostlyStaticPages"); - - $msp = $MSPTable->get($row['mostly_static_page_id']); - - $row['url'] = $msp->url; - } - - return $row; - } - /** * Provision object data to the provisioning target. - * - * @since COmanage Registry v5.0.0 - * @param ProvisioningTarget $provisioningTarget SqlProvisioner configuration - * @param string $className Class name of primary object being provisioned - * @param object $data Provisioning data in Entity format (eg: \App\Model\Entity\Person) - * @param ProvisioningEligibilityEnum $eligibility Provisioning Eligibility Enum + * + * @param ProvisioningTarget $provisioningTarget SqlProvisioner configuration + * @param string $className Class name of primary object being provisioned + * @param object $data Provisioning data in Entity format (eg: \App\Model\Entity\Person) + * @param string $eligibility Provisioning Eligibility Enum * @return array Array of status, comment, and optional identifier + * @since COmanage Registry v5.0.0 */ - public function provision( \App\Model\Entity\ProvisioningTarget $provisioningTarget, string $className, @@ -494,12 +471,12 @@ public function status( * Sync an entity to the target database schema. * * @since COmanage Registry v5.0.0 - * @param SqlProvisioner $SqlProvisioner SqlProvisioner configuration - * @param string $entityName Entity name of primary object being provisioned - * @param object $data Provisioning data in Entity format (eg: \App\Model\Entity\Person) - * @param ProvisioningEligibilityEnum $eligibility Provisioning Eligibility Enum - * @param string $dataSource Datasource to provision to - * @return array Array of status, comment, and optional identifier + * @param \SqlConnector\Model\Entity\SqlProvisioner $SqlProvisioner SqlProvisioner configuration + * @param string $entityName Entity name of primary object being provisioned + * @param object $data Provisioning data in Entity format (eg: \App\Model\Entity\Person) + * @param string $eligibility Provisioning Eligibility Enum + * @param string $dataSource Datasource to provision to + * @return array Array of status, comment, and optional identifier */ protected function syncEntity( @@ -759,12 +736,12 @@ public function syncReferenceData(int $id, string $dataSource='targetdb') { * Sync related entities to the target database schema. * * @since COmanage Registry v5.0.0 - * @param SqlProvisioner $SqlProvisioner SqlProvisioner configuration - * @param string $parentEntityName Entity name of primary object being provisioned - * @param string $relatedEntityName Entity name of related object being provisioned - * @param object $parentData Provisioning data in Entity format (eg: \App\Model\Entity\Person) for parent - * @param ProvisioningEligibilityEnum $eligibility Provisioning Eligibility Enum - * @param string $dataSource Datasource to provision to + * @param \SqlConnector\Model\Entity\SqlProvisioner $SqlProvisioner SqlProvisioner configuration + * @param string $parentEntityName Entity name of primary object being provisioned + * @param string $relatedEntityName Entity name of related object being provisioned + * @param object $parentData Provisioning data in Entity format (eg: \App\Model\Entity\Person) for parent + * @param string $eligibility Provisioning Eligibility Enum + * @param string $dataSource Datasource to provision to */ protected function syncRelatedEntities( diff --git a/app/src/Model/Entity/TAndCAgreement.php b/app/src/Model/Entity/TAndCAgreement.php index bd2044691..6eb2f35a4 100644 --- a/app/src/Model/Entity/TAndCAgreement.php +++ b/app/src/Model/Entity/TAndCAgreement.php @@ -30,6 +30,7 @@ namespace App\Model\Entity; use Cake\ORM\Entity; +use Cake\ORM\TableRegistry; class TAndCAgreement extends Entity { use \App\Lib\Traits\EntityMetaTrait; @@ -39,4 +40,54 @@ class TAndCAgreement extends Entity { 'id' => false, 'slug' => false, ]; + + /** + * Get the URL for this Terms and Conditions Agreement. + * + * @since COmanage Registry v5.3.0 + * @return string|null URL + */ + + protected function _getUrl(): ?string { + // If the associated TermsAndConditions is already loaded, check it first + if(!empty($this->terms_and_conditions)) { + if(!empty($this->terms_and_conditions->url)) { + return $this->terms_and_conditions->url; + } + + if(!empty($this->terms_and_conditions->mostly_static_page?->url)) { + return $this->terms_and_conditions->mostly_static_page->url; + } + + if(!empty($this->terms_and_conditions->mostly_static_page_id)) { + try { + $MSPTable = TableRegistry::getTableLocator()->get('MostlyStaticPages'); + $msp = $MSPTable->get($this->terms_and_conditions->mostly_static_page_id); + return $msp->url; + } catch(\Exception $e) { + return null; + } + } + } + + // Otherwise look up the Terms and Conditions if terms_and_conditions_id is present + if(!empty($this->terms_and_conditions_id)) { + try { + $TermsAndConditions = TableRegistry::getTableLocator()->get('TermsAndConditions'); + $tandc = $TermsAndConditions->get($this->terms_and_conditions_id, contain: ['MostlyStaticPages']); + + if(!empty($tandc->url)) { + return $tandc->url; + } + + if(!empty($tandc->mostly_static_page?->url)) { + return $tandc->mostly_static_page->url; + } + } catch(\Exception $e) { + return null; + } + } + + return null; + } } \ No newline at end of file diff --git a/app/src/Model/Entity/TermsAndConditions.php b/app/src/Model/Entity/TermsAndConditions.php index 0cd04e432..5cb760e6d 100644 --- a/app/src/Model/Entity/TermsAndConditions.php +++ b/app/src/Model/Entity/TermsAndConditions.php @@ -30,6 +30,7 @@ namespace App\Model\Entity; use Cake\ORM\Entity; +use Cake\ORM\TableRegistry; class TermsAndConditions extends Entity { use \App\Lib\Traits\EntityMetaTrait; @@ -39,4 +40,34 @@ class TermsAndConditions extends Entity { 'id' => false, 'slug' => false, ]; + + /** + * Get the URL for this Terms and Conditions as a virtual field. + * + * @since COmanage Registry v5.3.0 + * @param string|null $url Existing URL field value + * @return string|null URL + */ + + protected function _getUrl(?string $url = null): ?string { + if(!empty($url)) { + return $url; + } + + if(!empty($this->mostly_static_page?->url)) { + return $this->mostly_static_page->url; + } + + if(!empty($this->mostly_static_page_id)) { + try { + $MSPTable = TableRegistry::getTableLocator()->get('MostlyStaticPages'); + $msp = $MSPTable->get($this->mostly_static_page_id); + return $msp->url; + } catch(\Exception $e) { + return null; + } + } + + return null; + } } \ No newline at end of file diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 80f08c4dd..990e32fe3 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -506,13 +506,12 @@ public function marshalProvisioningData(int $id): array { 'Types' ], 'Pronouns', - 'TAndCAgreements' => [ - 'TermsAndConditions' => [ - // Pull the backing document for provisioning (T&C uses either a URL - // or a Mostly Static Page) - 'MostlyStaticPages', - ] - ], + // TAndCAgreements are provisionable sub-objects, but TermsAndConditions + // and MostlyStaticPages are configuration and are not provisionable. + // We do not contain TermsAndConditions => MostlyStaticPages here to maintain + // loose coupling and avoid synchronization/ownership issues; provisioners + // can resolve the T&C URL via the TAndCAgreement virtual field or reference data. + 'TAndCAgreements', 'TelephoneNumbers' => [ 'Types' ], 'Urls' => [ 'Types' ] ] From 8ca7711390127e49ef88caf40444372ce0654226 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 2 Sep 2026 10:36:18 +0300 Subject: [PATCH 3/3] Simplify T&C URL resolution across entities and clean up obsolete vv_base_url --- .../TermsAndConditionsController.php | 16 --------- app/src/Model/Entity/TAndCAgreement.php | 35 ++++++------------- app/src/Model/Entity/TermsAndConditions.php | 16 ++++++--- app/templates/TermsAndConditions/columns.inc | 9 +---- 4 files changed, 23 insertions(+), 53 deletions(-) diff --git a/app/src/Controller/TermsAndConditionsController.php b/app/src/Controller/TermsAndConditionsController.php index 4bdb8de6c..d30814b4d 100644 --- a/app/src/Controller/TermsAndConditionsController.php +++ b/app/src/Controller/TermsAndConditionsController.php @@ -99,22 +99,6 @@ public function agree(string $id) { return $this->redirect(['action' => 'review', '?' => ['co_id' => $this->getCOID()]]); } - /** - * Callback run prior to the request render. - * - * @since COmanage Registry v5.1.0 - * @param EventInterface $event Cake Event - * @return \Cake\Http\Response HTTP Response - */ - - public function beforeRender(\Cake\Event\EventInterface $event) { - $this->set('vv_base_url', \Cake\Routing\Router::url( - url: "/" . $this->getCOID(), - full: true - )); - - return parent::beforeRender($event); - } /** * Proxy an Agreement on behalf of a Person. diff --git a/app/src/Model/Entity/TAndCAgreement.php b/app/src/Model/Entity/TAndCAgreement.php index 6eb2f35a4..dbc2ba0b5 100644 --- a/app/src/Model/Entity/TAndCAgreement.php +++ b/app/src/Model/Entity/TAndCAgreement.php @@ -29,12 +29,14 @@ namespace App\Model\Entity; +use Cake\Core\Configure; use Cake\ORM\Entity; use Cake\ORM\TableRegistry; class TAndCAgreement extends Entity { use \App\Lib\Traits\EntityMetaTrait; - + use \App\Lib\Traits\LabeledLogTrait; + protected array $_accessible = [ '*' => true, 'id' => false, @@ -51,39 +53,22 @@ class TAndCAgreement extends Entity { protected function _getUrl(): ?string { // If the associated TermsAndConditions is already loaded, check it first if(!empty($this->terms_and_conditions)) { - if(!empty($this->terms_and_conditions->url)) { - return $this->terms_and_conditions->url; - } - - if(!empty($this->terms_and_conditions->mostly_static_page?->url)) { - return $this->terms_and_conditions->mostly_static_page->url; - } - - if(!empty($this->terms_and_conditions->mostly_static_page_id)) { - try { - $MSPTable = TableRegistry::getTableLocator()->get('MostlyStaticPages'); - $msp = $MSPTable->get($this->terms_and_conditions->mostly_static_page_id); - return $msp->url; - } catch(\Exception $e) { - return null; - } - } + return $this->terms_and_conditions->url; } // Otherwise look up the Terms and Conditions if terms_and_conditions_id is present if(!empty($this->terms_and_conditions_id)) { try { $TermsAndConditions = TableRegistry::getTableLocator()->get('TermsAndConditions'); - $tandc = $TermsAndConditions->get($this->terms_and_conditions_id, contain: ['MostlyStaticPages']); - if(!empty($tandc->url)) { - return $tandc->url; + return $TermsAndConditions->get($this->terms_and_conditions_id)->url; + } catch(\Exception $e) { + // We will rethrow here to facilitate debugging + if (Configure::read('debug')) { + throw $e; } - if(!empty($tandc->mostly_static_page?->url)) { - return $tandc->mostly_static_page->url; - } - } catch(\Exception $e) { + $this->llog('error', $e->getMessage()); return null; } } diff --git a/app/src/Model/Entity/TermsAndConditions.php b/app/src/Model/Entity/TermsAndConditions.php index 5cb760e6d..a992791e5 100644 --- a/app/src/Model/Entity/TermsAndConditions.php +++ b/app/src/Model/Entity/TermsAndConditions.php @@ -29,12 +29,14 @@ namespace App\Model\Entity; +use Cake\Core\Configure; use Cake\ORM\Entity; use Cake\ORM\TableRegistry; class TermsAndConditions extends Entity { use \App\Lib\Traits\EntityMetaTrait; - + use \App\Lib\Traits\LabeledLogTrait; + protected array $_accessible = [ '*' => true, 'id' => false, @@ -44,11 +46,11 @@ class TermsAndConditions extends Entity { /** * Get the URL for this Terms and Conditions as a virtual field. * - * @since COmanage Registry v5.3.0 - * @param string|null $url Existing URL field value + * @param string|null $url Existing URL field value * @return string|null URL + * @throws \Exception + * @since COmanage Registry v5.3.0 */ - protected function _getUrl(?string $url = null): ?string { if(!empty($url)) { return $url; @@ -64,6 +66,12 @@ protected function _getUrl(?string $url = null): ?string { $msp = $MSPTable->get($this->mostly_static_page_id); return $msp->url; } catch(\Exception $e) { + // We will rethrow here to facilitate debugging + if (Configure::read('debug')) { + throw $e; + } + + $this->llog('error', $e->getMessage()); return null; } } diff --git a/app/templates/TermsAndConditions/columns.inc b/app/templates/TermsAndConditions/columns.inc index 620e141ba..f1248e8fd 100644 --- a/app/templates/TermsAndConditions/columns.inc +++ b/app/templates/TermsAndConditions/columns.inc @@ -49,13 +49,6 @@ $rowActions = [ [ 'icon' => 'arrow_outward', 'label' => __d('operation', 'visit.tandc'), - 'callbackUrl' => function($entity) use ($vv_base_url) { - // There should always be either a Mostly Static Page or a URL to reference. - if(!empty($entity->mostly_static_page->name)) { - // The T&C is a mostly static page. Provide a link to it. - return $vv_base_url . '/' . $entity->mostly_static_page->name; - } - return $entity->url; - } + 'callbackUrl' => static fn($entity) => $entity->url ] ];