From 6841bca0703b49ec478e25a90e1160560394c54b Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Mon, 7 Sep 2026 10:43:20 +0000 Subject: [PATCH 1/4] Transmogrify Departments from v4 to v5 --- .../Transmogrify/config/schema/tables.json | 41 +++++++++++-------- .../src/Command/TransmogrifyCommand.php | 16 ++++++++ .../src/Lib/Traits/CacheTrait.php | 2 +- .../src/Lib/Traits/ManageDefaultsTrait.php | 15 +++++++ .../src/Lib/Traits/TypeMapperTrait.php | 24 +++++++++++ app/resources/locales/en_US/command.po | 3 ++ app/src/Command/UpgradeCommand.php | 23 +++++++++-- 7 files changed, 104 insertions(+), 20 deletions(-) diff --git a/app/availableplugins/Transmogrify/config/schema/tables.json b/app/availableplugins/Transmogrify/config/schema/tables.json index a9276684f..0d1bc889e 100644 --- a/app/availableplugins/Transmogrify/config/schema/tables.json +++ b/app/availableplugins/Transmogrify/config/schema/tables.json @@ -605,6 +605,20 @@ }, "dependencies": ["groups", "people", "group_nestings"] }, + "departments": { + "source": "cm_co_departments", + "displayField": "name", + "fieldMap": { + "co_department_id": "department_id", + "leadership_co_group_id": "leadership_group_id", + "administrative_co_group_id": "administrative_group_id", + "support_co_group_id": "support_group_id", + "type_id": "&mapDepartmentType", + "introduction": "public_description", + "type": null + }, + "dependencies": ["co", "cous", "groups", "types"] + }, "names": { "source": "cm_names", "displayField": "id", @@ -627,11 +641,10 @@ "type_id": "&mapEmailType", "co_person_id": "person_id", "org_identity_id": "external_identity_id", - "co_department_id": null, - "organization_id": null, + "co_department_id": "department_id", "type": null }, - "dependencies": ["people", "external_identities", "types"] + "dependencies": ["people", "external_identities", "types", "departments"] }, "identifiers": { "source": "cm_identifiers", @@ -644,13 +657,12 @@ "co_group_id": "group_id", "co_person_id": "person_id", "org_identity_id": "external_identity_id", - "co_department_id": null, + "co_department_id": "department_id", "co_provisioning_target_id": null, - "organization_id": null, "type": null, "language": null }, - "dependencies": ["groups", "people", "external_identities", "types"] + "dependencies": ["groups", "people", "external_identities", "types", "departments"] }, "urls": { "source": "cm_urls", @@ -660,12 +672,11 @@ "type_id": "&mapUrlType", "co_person_id": "person_id", "org_identity_id": "external_identity_id", - "co_department_id": null, - "organization_id": null, + "co_department_id": "department_id", "type": null, "language": null }, - "dependencies": ["people", "external_identities", "types"] + "dependencies": ["people", "external_identities", "types", "departments"] }, "addresses": { "source": "cm_addresses", @@ -675,11 +686,10 @@ "type_id": "&mapAddressType", "co_person_role_id": "person_role_id", "org_identity_id": "external_identity_id", - "co_department_id": null, - "organization_id": null, + "co_department_id": "department_id", "type": null }, - "dependencies": ["person_roles", "external_identities", "types"] + "dependencies": ["person_roles", "external_identities", "types", "departments"] }, "telephone_numbers": { "source": "cm_telephone_numbers", @@ -689,11 +699,10 @@ "type_id": "&mapTelephoneType", "co_person_role_id": "person_role_id", "org_identity_id": "external_identity_id", - "co_department_id": null, - "organization_id": null, + "co_department_id": "department_id", "type": null }, - "dependencies": ["person_roles", "external_identities", "types"] + "dependencies": ["person_roles", "external_identities", "types", "departments"] }, "ad_hoc_attributes": { "source": "cm_ad_hoc_attributes", @@ -702,7 +711,7 @@ "fieldMap": { "co_person_role_id": "person_role_id", "org_identity_id": "external_identity_id", - "co_department_id": null, + "co_department_id": "department_id", "organization_id": null }, "postTable": "migrateExtendedAttributesToAdHocAttributes", diff --git a/app/availableplugins/Transmogrify/src/Command/TransmogrifyCommand.php b/app/availableplugins/Transmogrify/src/Command/TransmogrifyCommand.php index bc7a75215..2a2743f3a 100644 --- a/app/availableplugins/Transmogrify/src/Command/TransmogrifyCommand.php +++ b/app/availableplugins/Transmogrify/src/Command/TransmogrifyCommand.php @@ -583,6 +583,22 @@ public function execute(Arguments $args, ConsoleIo $io): int $this->cmdPrinter->out('Running assignUuids task via UpgradeCommand...'); $this->executeCommand(UpgradeCommand::class, ['-D', '-X', '-t', 'assignUuids'], $this->io); + // Install default Mostly Static Pages + $this->cmdPrinter->out('Running installMostlyStaticPages task via UpgradeCommand...'); + $this->executeCommand(UpgradeCommand::class, ['-D', '-X', '-t', 'installMostlyStaticPages'], $this->io); + + // Rebuild Group Tree hierarchy (lft/rght bounds) + $this->cmdPrinter->out('Running buildGroupTree task via UpgradeCommand...'); + $this->executeCommand(UpgradeCommand::class, ['-D', '-X', '-t', 'buildGroupTree'], $this->io); + + // Create missing default groups (Approvers, MFA Exempt, etc.) + $this->cmdPrinter->out('Running createDefaultGroups task via UpgradeCommand...'); + $this->executeCommand(UpgradeCommand::class, ['-D', '-X', '-P', '-t', 'createDefaultGroups'], $this->io); + + // Initialize default normalizations + $this->cmdPrinter->out('Running setupNormalizations task via UpgradeCommand...'); + $this->executeCommand(UpgradeCommand::class, ['-D', '-X', '-t', 'setupNormalizations'], $this->io); + // Display total execution time $executionTime = microtime(true) - $this->startTime; diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php index 198c51f85..60b8f0365 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php @@ -123,7 +123,7 @@ protected function cacheFieldById(string $table, array $row, string $field): voi * @param string $table Table to cache * @param array $row Row of table data * @param array $orinRow Original Row of table data - * @param array $cacheConfig Optional cache configuration (overrides tables.json) + * @param array|null $cacheConfig Optional cache configuration (overrides tables.json) */ protected function cacheResults(string $table, array $row, array $orinRow, ?array $cacheConfig = []): void diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php index a346cd548..26e3bca1e 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php @@ -286,6 +286,21 @@ protected function insertDefaultSettings(): void } } + /** + * Insert default types for all COs. + * + * @since COmanage Registry v5.3.0 + * @return void + */ + protected function insertDefaultTypes(): void + { + $Types = TableRegistry::getTableLocator()->get('Types'); + + foreach(array_keys($this->cache['cos']['id']) as $coId) { + $Types->addDefaults($coId); + } + } + /** * Insert default Pronoun types for all COs. * diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php index 67f76f5bb..6ab4d3e78 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php @@ -643,6 +643,30 @@ protected function mapNameType(array $row): ?int ); } + /** + * Map department type to corresponding type ID + * + * @param array $row Row data containing department type + * @return int|null Mapped type ID + * @since COmanage Registry v5.3.0 + */ + protected function mapDepartmentType(array $row): ?int + { + $type = 'type'; + if (isset($row['department_type'])) { + $type = 'department_type'; + } + + $row[$type] = strtolower($row[$type]); + + return $this->mapType( + $row, + 'Departments.type', + $this->findCoId($row), + $type + ); + } + /** * Return a timestamp equivalent to now. * diff --git a/app/resources/locales/en_US/command.po b/app/resources/locales/en_US/command.po index 724744a39..14c5c05fc 100644 --- a/app/resources/locales/en_US/command.po +++ b/app/resources/locales/en_US/command.po @@ -201,6 +201,9 @@ msgstr "Test to perform" msgid "opt.upgrade.forcecurrent" msgstr "Force the specified current version -- ADVANCED USERS ONLY" +msgid "opt.upgrade.skipprovisioning" +msgstr "Skip provisioning -- ADVANCED USERS ONLY" + msgid "opt.upgrade.task" msgstr "Upgrade task to perform -- ADVANCED USERS ONLY" diff --git a/app/src/Command/UpgradeCommand.php b/app/src/Command/UpgradeCommand.php index 9925fd8ea..f29e2828d 100644 --- a/app/src/Command/UpgradeCommand.php +++ b/app/src/Command/UpgradeCommand.php @@ -45,6 +45,7 @@ class UpgradeCommand extends BaseCommand { use \Cake\ORM\Locator\LocatorAwareTrait; + protected ?Arguments $args = null; protected $io = null; /** @@ -172,6 +173,14 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => __d('command', 'opt.upgrade.version'), 'required' => false ] + )->addOption( + 'skipprovisioning', + [ + 'short' => 'P', + 'help' => __d('command', 'opt.upgrade.skipprovisioning'), + 'required' => false, + 'boolean' => true + ] ); return $parser; @@ -190,6 +199,7 @@ public function execute(Arguments $args, ConsoleIo $io) global $argv; $this->io = $io; + $this->args = $args; // Are we being asked to run a specific task? $task = $args->getOption('task'); @@ -528,8 +538,8 @@ protected function checkGroupNames() { * Create Approver and MFA Exemption Groups. * * @since COmanage Registry v5.2.0 - * @param int $coId CO ID - * @param int $couId COU ID + * @param int $coId CO ID + * @param int|null $couId COU ID */ protected function createDefaultGroups(int $coId, ?int $couId=null) { @@ -537,7 +547,14 @@ protected function createDefaultGroups(int $coId, ?int $couId=null) { // Technically this will try to add all the default Groups, which is fine since // it will skip the ones that already exist. - $GroupsTable->addDefaults($coId, $couId); + $skipProvisioning = (bool)($this->args?->getOption('skipprovisioning') ?? false); + + // Pass provision: false to prevent triggering provisioning targets + $GroupsTable->addDefaults( + coId: $coId, + couId: $couId, + provision: !$skipProvisioning + ); } /** From b7e4f8df9ef27225dbe07d7cca65be9461f2089f Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Mon, 7 Sep 2026 17:42:42 +0000 Subject: [PATCH 2/4] transmogrify contacts and organizations. --- .../Transmogrify/config/schema/tables.json | 71 +++++++++++++++--- .../src/Lib/Traits/CacheTrait.php | 57 +++++++++++++++ .../src/Lib/Traits/HookRunnersTrait.php | 2 +- .../src/Lib/Traits/ManageDefaultsTrait.php | 20 +++++- .../src/Lib/Traits/RowTransformationTrait.php | 8 ++- .../src/Lib/Traits/TypeMapperTrait.php | 72 +++++++++++++++++-- .../src/Lib/Util/RawSqlQueries.php | 3 +- app/src/Lib/Traits/TypeTrait.php | 19 +++-- app/src/Model/Table/TypesTable.php | 6 +- 9 files changed, 233 insertions(+), 25 deletions(-) diff --git a/app/availableplugins/Transmogrify/config/schema/tables.json b/app/availableplugins/Transmogrify/config/schema/tables.json index 0d1bc889e..5141eaa33 100644 --- a/app/availableplugins/Transmogrify/config/schema/tables.json +++ b/app/availableplugins/Transmogrify/config/schema/tables.json @@ -46,7 +46,7 @@ "types": { "source": "cm_co_extended_types", "displayField": "display_name", - "postTable": "insertPronounTypes", + "postTable": "insertDefaultTypes", "fieldMap": { "attribute": "&mapExtendedType", "name": "value", @@ -108,6 +108,15 @@ "sqlSelect": "couSqlSelect", "dependencies": ["cos"] }, + "organizations": { + "source": "cm_organizations", + "displayField": "name", + "cache": ["id", "co_id"], + "fieldMap": { + "type_id": "&mapOrganizationType", + "type": null + } + }, "servers": { "source": "cm_servers", "plugin": "CoreServer", @@ -237,7 +246,7 @@ "booleans": ["hash_source_record"], "cache": ["co_id"], "fieldMap": { - "plugin": "&mapExternalIdentitySourcePlugin", + "plugin": "&mapSourcePlugin", "co_pipeline_id": "pipeline_id", "org_identity_source_id": "external_identity_source_id", "status": "&mapStatusAndSyncToStatus", @@ -310,6 +319,30 @@ }, "dependencies": ["external_identity_sources", "types"] }, + "organization_sources": { + "source": "cm_organization_sources", + "displayField": "description", + "plugin": "FederationConnector", + "fieldMap": { + "plugin": "&mapSourcePlugin", + "sync_mode": null + }, + "dependencies": ["co_id"] + }, + "federation_sources": { + "source": "cm_federation_sources", + "displayField": "id", + "plugin": "FederationConnector", + "fieldMap": {}, + "dependencies": ["co_id", "organization_sources", "organizations"] + }, + "organization_source_records": { + "source": "cm_organization_source_records", + "displayField": "id", + "plugin": "FederationConnector", + "fieldMap": {}, + "dependencies": ["co_id", "organization_sources", "organizations"] + }, "api_sources": { "source": "cm_api_sources", "plugin": "ApiConnector", @@ -608,6 +641,7 @@ "departments": { "source": "cm_co_departments", "displayField": "name", + "cache": ["id", "co_id"], "fieldMap": { "co_department_id": "department_id", "leadership_co_group_id": "leadership_group_id", @@ -642,6 +676,7 @@ "co_person_id": "person_id", "org_identity_id": "external_identity_id", "co_department_id": "department_id", + "organization_id": "organization_id", "type": null }, "dependencies": ["people", "external_identities", "types", "departments"] @@ -658,11 +693,12 @@ "co_person_id": "person_id", "org_identity_id": "external_identity_id", "co_department_id": "department_id", - "co_provisioning_target_id": null, + "organization_id": "organization_id", + "co_provisioning_target_id": "provisioning_target_id", "type": null, "language": null }, - "dependencies": ["groups", "people", "external_identities", "types", "departments"] + "dependencies": ["groups", "people", "external_identities", "types", "departments", "organizations"] }, "urls": { "source": "cm_urls", @@ -673,10 +709,11 @@ "co_person_id": "person_id", "org_identity_id": "external_identity_id", "co_department_id": "department_id", + "organization_id": "organization_id", "type": null, "language": null }, - "dependencies": ["people", "external_identities", "types", "departments"] + "dependencies": ["people", "external_identities", "types", "departments", "organizations"] }, "addresses": { "source": "cm_addresses", @@ -687,9 +724,10 @@ "co_person_role_id": "person_role_id", "org_identity_id": "external_identity_id", "co_department_id": "department_id", + "organization_id": "organization_id", "type": null }, - "dependencies": ["person_roles", "external_identities", "types", "departments"] + "dependencies": ["person_roles", "external_identities", "types", "departments", "organizations"] }, "telephone_numbers": { "source": "cm_telephone_numbers", @@ -700,9 +738,10 @@ "co_person_role_id": "person_role_id", "org_identity_id": "external_identity_id", "co_department_id": "department_id", + "organization_id": "organization_id", "type": null }, - "dependencies": ["person_roles", "external_identities", "types", "departments"] + "dependencies": ["person_roles", "external_identities", "types", "departments", "organizations"] }, "ad_hoc_attributes": { "source": "cm_ad_hoc_attributes", @@ -712,10 +751,24 @@ "co_person_role_id": "person_role_id", "org_identity_id": "external_identity_id", "co_department_id": "department_id", - "organization_id": null + "organization_id": "organization_id" }, "postTable": "migrateExtendedAttributesToAdHocAttributes", - "dependencies": ["person_roles", "external_identities"] + "dependencies": ["person_roles", "external_identities", "organizations", "departments"] + }, + "contacts": { + "source": "cm_contacts", + "displayField": "id", + "booleans": [ + "frozen" + ], + "fieldMap": { + "type_id": "&mapContactType", + "organization_id": "organization_id", + "co_department_id": "department_id", + "type": null + }, + "dependencies": ["organizations", "departments"] }, "notifications": { "source": "cm_co_notifications", diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php index 60b8f0365..a9e7220d3 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/CacheTrait.php @@ -188,6 +188,18 @@ protected function findCoId(array $row): int isset($row['co_person_role_id']) => $this->getCoIdFromPersonRoleId((int)$row['co_person_role_id']), + isset($row['person_role_id']) => $this->getCoIdFromPersonRoleId((int)$row['person_role_id']), + + isset($row['organization_id']) => $this->getCoIdFromOrganizationId((int)$row['organization_id']), + + isset($row['department_id']) => $this->getCoIdFromDepartmentId((int)$row['department_id']), + + isset($row['co_department_id']) => $this->getCoIdFromDepartmentId((int)$row['co_department_id']), + + isset($row['provisioning_target_id']) => $this->getCoIdFromProvisioningTargetId((int)$row['provisioning_target_id']), + + isset($row['co_provisioning_target_id']) => $this->getCoIdFromProvisioningTargetId((int)$row['co_provisioning_target_id']), + default => null, }; @@ -312,4 +324,49 @@ private function getCoIdFromPersonRoleId(int $personRoleId): ?int } return null; } + + /** + * Resolve a CO ID from an Organization ID via cache. + * + * @param int $organizationId Organization ID to resolve + * @return int|null CO ID if found, null otherwise + * @since COmanage Registry v5.3.0 + */ + private function getCoIdFromOrganizationId(int $organizationId): ?int + { + if (isset($this->cache['organizations']['id'][$organizationId]['co_id'])) { + return (int)$this->cache['organizations']['id'][$organizationId]['co_id']; + } + return null; + } + + /** + * Resolve a CO ID from a Department ID via cache. + * + * @param int $departmentId Department ID to resolve + * @return int|null CO ID if found, null otherwise + * @since COmanage Registry v5.3.0 + */ + private function getCoIdFromDepartmentId(int $departmentId): ?int + { + if (isset($this->cache['departments']['id'][$departmentId]['co_id'])) { + return (int)$this->cache['departments']['id'][$departmentId]['co_id']; + } + return null; + } + + /** + * Resolve a CO ID from a Provisioning Target ID via cache. + * + * @param int $provisioningTargetId Provisioning Target ID to resolve + * @return int|null CO ID if found, null otherwise + * @since COmanage Registry v5.3.0 + */ + private function getCoIdFromProvisioningTargetId(int $provisioningTargetId): ?int + { + if (isset($this->cache['provisioning_targets']['id'][$provisioningTargetId]['co_id'])) { + return (int)$this->cache['provisioning_targets']['id'][$provisioningTargetId]['co_id']; + } + return null; + } } \ No newline at end of file diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/HookRunnersTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/HookRunnersTrait.php index 8ddf6c05c..895a1b237 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/HookRunnersTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/HookRunnersTrait.php @@ -135,7 +135,7 @@ private function runSqlSelectHook(string $table, string $qualifiedTableName): st 'co_group_id', 'co_department_id', 'co_provisioning_target_id', - 'organization_id' + 'organization_id' // XXX The name in v4 and v5 is the same. As a result this needs to exist in the table.json!!! ]; $fieldMap = $this->tables[$table]['fieldMap'] ?? []; $presentFks = array_values(array_intersect($fkCandidates, array_keys($fieldMap))); diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php index 26e3bca1e..6c0e3aa5d 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php @@ -296,9 +296,27 @@ protected function insertDefaultTypes(): void { $Types = TableRegistry::getTableLocator()->get('Types'); - foreach(array_keys($this->cache['cos']['id']) as $coId) { + foreach (array_keys($this->cache['cos']['id']) as $coId) { $Types->addDefaults($coId); } + + // Populate cache with all default and extended types + $allTypes = $Types->find() + ->where([ + 'co_id IN' => array_keys($this->cache['cos']['id']), + ]) + ->all(); + + foreach ($allTypes as $type) { + $row = [ + 'id' => $type->id, + 'co_id' => $type->co_id, + 'attribute' => $type->attribute, + 'value' => $type->value, + ]; + + $this->cacheCompositeKey('types', $row, ['co_id', 'attribute', 'value']); + } } /** diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php index fb0736bb2..70619efe1 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php @@ -631,8 +631,8 @@ protected function mapExternalIdentityToExternalIdentityRole(array $origRow, arr if ( isset($origRow['co_id']) && (!isset($this->cache['cos'][$origRow['co_id']]) - || ($this->cache['cos'][$origRow['co_id']]['status']) - && $this->cache['cos'][$origRow['co_id']]['status'] == 'TR') + || (($this->cache['cos'][$origRow['co_id']]['status']) + && $this->cache['cos'][$origRow['co_id']]['status'] == 'TR')) ) { // This CO has been deleted, so we can't map the type. We will return null $roleRow['affiliation_type_id'] = null; @@ -735,6 +735,10 @@ private function applyDefaultIfNull(array &$row, string $oldname, string $defaul */ private function renameField(array &$row, string $oldname, string $newname): void { + if ($oldname === $newname) { + return; + } + // Only copy if the old field exists to avoid notices if (array_key_exists($oldname, $row)) { $row[$newname] = $row[$oldname]; diff --git a/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php b/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php index 6ab4d3e78..aa08a3c2e 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php +++ b/app/availableplugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php @@ -255,7 +255,11 @@ protected function mapApiIdFromCache(array $row): ?int { } $parts = explode('.', $key); - return (int)$parts[1] ?? null; + if (!isset($parts[1]) || !is_numeric($parts[1])) { + return null; + } + + return (int)$parts[1]; } @@ -330,13 +334,14 @@ protected function mapPlugin(array $row, string $suffix, string $context): ?stri * Examples: * EnvSource -> EnvSource.EnvSources * FileSource -> FileConnector.FileSources + * FederationSource -> FederationConnector.FederationSources * * @param array $row A row from cm_org_identity_sources containing 'plugin' * @return string|null */ - protected function mapExternalIdentitySourcePlugin(array $row): ?string + protected function mapSourcePlugin(array $row): ?string { - return $this->mapPlugin($row, 'Source', 'External Identity Source'); + return $this->mapPlugin($row, 'Source', 'External (Identity) Source'); } @@ -529,7 +534,13 @@ protected function mapMatchAttributeTypeId(array $row): ?int */ protected function mapMessageTemplateContext(array $row): ?string { - return (string)self::MESSAGE_TEMPLATE_CONTEXT_MAP[$row['context']] ?? null; + $context = $row['context'] ?? null; + + if ($context === null || !array_key_exists($context, self::MESSAGE_TEMPLATE_CONTEXT_MAP)) { + return null; + } + + return self::MESSAGE_TEMPLATE_CONTEXT_MAP[$context]; } /** @@ -667,6 +678,51 @@ protected function mapDepartmentType(array $row): ?int ); } + /** + * Map Organziation type to corresponding type ID + * + * @param array $row Row data containing name type + * @return int|null Mapped type ID + * @since COmanage Registry v5.2.0 + */ + protected function mapOrganizationType(array $row): ?int + { + $type = 'type'; + if (isset($row['organization_type'])) { + $type = 'organization_type'; + } + + return $this->mapType( + $row, + 'Organizations.type', + $this->findCoId($row), + $type + ); + } + + + /** + * Map Organziation type to corresponding type ID + * + * @param array $row Row data containing name type + * @return int|null Mapped type ID + * @since COmanage Registry v5.2.0 + */ + protected function mapContactType(array $row): ?int + { + $type = 'type'; + if (isset($row['contact_type'])) { + $type = 'contact_type'; + } + + return $this->mapType( + $row, + 'Contacts.type', + $this->findCoId($row), + $type + ); + } + /** * Return a timestamp equivalent to now. * @@ -924,7 +980,13 @@ protected function mapStatusAndSyncToStatus(array $row): ?string */ protected function mapServerTypeToPlugin(array $row): ?string { - return (string)self::SERVER_TYPE_MAP[$row['server_type']] ?? null; + $serverType = $row['server_type'] ?? null; + + if ($serverType === null || !isset(self::SERVER_TYPE_MAP[$serverType])) { + return null; + } + + return self::SERVER_TYPE_MAP[$serverType]; } /** diff --git a/app/availableplugins/Transmogrify/src/Lib/Util/RawSqlQueries.php b/app/availableplugins/Transmogrify/src/Lib/Util/RawSqlQueries.php index 191386d39..eae1f4a07 100644 --- a/app/availableplugins/Transmogrify/src/Lib/Util/RawSqlQueries.php +++ b/app/availableplugins/Transmogrify/src/Lib/Util/RawSqlQueries.php @@ -224,6 +224,7 @@ public static function roleSqlSelect(string $tableName, bool $isMySQL): string { * * @param string $tableName Name of the database table containing MVEAs * @param bool $isMySQL Whether the target database is MySQL (true) or PostgreSQL (false) + * @param array $fkColumns List of foreign key column names * @return string SQL query string to select MVEA rows that are linked to valid org identities * @since COmanage Registry v5.2.0 */ @@ -242,7 +243,7 @@ public static function mveaSqlSelect(string $tableName, bool $isMySQL, array $fk ]; // In full mode, treat all as supported; otherwise split into supported/unsupported - if (false /* $fullMode */) { + if (true /* $fullMode */) { $supportedInUse = array_values($fkColumns); $unsupportedInUse = []; } else { diff --git a/app/src/Lib/Traits/TypeTrait.php b/app/src/Lib/Traits/TypeTrait.php index 3c7c625ff..a73b7def8 100644 --- a/app/src/Lib/Traits/TypeTrait.php +++ b/app/src/Lib/Traits/TypeTrait.php @@ -34,26 +34,35 @@ trait TypeTrait { use \Cake\ORM\Locator\LocatorAwareTrait; - + /** * Obtain the available types for this model/attribute, within the requested CO * * @since COmanage Registry v5.0.0 * @param int $coId CO ID * @param string $attribute Attribute to obtain available types for + * @param string|null $status * @return array Array of available types */ - public function availableTypes(int $coId, string $attribute) { + public function availableTypes(int $coId, string $attribute, ?string $status = SuspendableStatusEnum::Active): array + { $Types = $this->getTableLocator()->get("Types"); + + $conditions = [ + 'co_id' => $coId, + 'attribute' => $attribute, + ]; + + if(!empty($status)) { + $conditions['status'] = $status; + } $query = $Types->find('list', keyField: 'value', valueField: 'display_name', ) - ->where(['co_id' => $coId, - 'attribute' => $attribute, - 'status' => SuspendableStatusEnum::Active]) + ->where($conditions) ->orderBy(['Types.display_name' => 'ASC']); return $query->toArray(); diff --git a/app/src/Model/Table/TypesTable.php b/app/src/Model/Table/TypesTable.php index e874da3d2..b1ec97f58 100644 --- a/app/src/Model/Table/TypesTable.php +++ b/app/src/Model/Table/TypesTable.php @@ -183,7 +183,7 @@ public function addDefault(int $coId, string $attribute) { $table = TableRegistry::getTableLocator()->get($attr[0]); // The current set of types for this model, of the form value => display_name - $current = $table->availableTypes($coId, $attribute); + $current = $table->availableTypes($coId, $attribute, null); // The default types for this model, of the same form $modelDefault = $table->defaultTypes($attr[1]); @@ -202,6 +202,10 @@ public function addDefault(int $coId, string $attribute) { ]; } } + + if(empty($records)) { + return true; + } // Convert the arrays to entities $entities = $this->newEntities($records); From 49d97f1d5770e453e7429dcc2e4ba17e1b9211a4 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 8 Sep 2026 07:20:40 +0000 Subject: [PATCH 3/4] fix dependency list --- app/availableplugins/Transmogrify/config/schema/tables.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/availableplugins/Transmogrify/config/schema/tables.json b/app/availableplugins/Transmogrify/config/schema/tables.json index 5141eaa33..2ada6ed64 100644 --- a/app/availableplugins/Transmogrify/config/schema/tables.json +++ b/app/availableplugins/Transmogrify/config/schema/tables.json @@ -679,7 +679,7 @@ "organization_id": "organization_id", "type": null }, - "dependencies": ["people", "external_identities", "types", "departments"] + "dependencies": ["people", "external_identities", "types", "departments", "organizations"] }, "identifiers": { "source": "cm_identifiers", From d7f3eff78cdf471c5d322fbfd9d68d4d2dbc6504 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Sun, 13 Sep 2026 07:32:05 +0000 Subject: [PATCH 4/4] Fix review comments --- app/src/Lib/Traits/TypeTrait.php | 8 ++++---- app/src/Model/Table/TypesTable.php | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/Lib/Traits/TypeTrait.php b/app/src/Lib/Traits/TypeTrait.php index a73b7def8..fef38a04f 100644 --- a/app/src/Lib/Traits/TypeTrait.php +++ b/app/src/Lib/Traits/TypeTrait.php @@ -41,11 +41,11 @@ trait TypeTrait { * @since COmanage Registry v5.0.0 * @param int $coId CO ID * @param string $attribute Attribute to obtain available types for - * @param string|null $status + * @param bool $includeSuspended * @return array Array of available types */ - public function availableTypes(int $coId, string $attribute, ?string $status = SuspendableStatusEnum::Active): array + public function availableTypes(int $coId, string $attribute, bool $includeSuspended = false): array { $Types = $this->getTableLocator()->get("Types"); @@ -54,8 +54,8 @@ public function availableTypes(int $coId, string $attribute, ?string $status = S 'attribute' => $attribute, ]; - if(!empty($status)) { - $conditions['status'] = $status; + if(!$includeSuspended) { + $conditions['status'] = SuspendableStatusEnum::Active; } $query = $Types->find('list', diff --git a/app/src/Model/Table/TypesTable.php b/app/src/Model/Table/TypesTable.php index b1ec97f58..b6a3b7673 100644 --- a/app/src/Model/Table/TypesTable.php +++ b/app/src/Model/Table/TypesTable.php @@ -183,7 +183,11 @@ public function addDefault(int $coId, string $attribute) { $table = TableRegistry::getTableLocator()->get($attr[0]); // The current set of types for this model, of the form value => display_name - $current = $table->availableTypes($coId, $attribute, null); + $current = $table->availableTypes( + coId: $coId, + attribute: $attribute, + includeSuspended: true + ); // The default types for this model, of the same form $modelDefault = $table->defaultTypes($attr[1]);