From 561ebc61009a88430fa8758c58d8f0fb11d9f0da Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 18 Feb 2026 15:55:11 +0000 Subject: [PATCH] Transmogrify IdentifierAssignments --- .../src/Model/Table/FormatAssignersTable.php | 11 +- .../Transmogrify/config/schema/tables.json | 37 +++++++ .../src/Command/TransmogrifyCommand.php | 6 +- .../src/Lib/Traits/CacheTrait.php | 4 +- .../src/Lib/Traits/ManageDefaultsTrait.php | 8 +- .../src/Lib/Traits/RowTransformationTrait.php | 101 ++++++++++++++++-- .../src/Lib/Traits/TypeMapperTrait.php | 46 +++++--- .../src/Lib/Util/RawSqlQueries.php | 6 +- 8 files changed, 180 insertions(+), 39 deletions(-) diff --git a/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php b/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php index 8d7524460..a541c0dfc 100644 --- a/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php +++ b/app/plugins/CoreAssigner/src/Model/Table/FormatAssignersTable.php @@ -341,13 +341,12 @@ protected function selectSequences( * Perform parameter substitution on an identifier format to generate the base * string used in identifier assignment. * - * @since COmanage Registry v5.0.0 - * @param EntityInterface $entity Entity to assign Identifier for - * @param string $format Identifier assignment format - * @param PermittedCharactersEnum $permitted Acceptable characters for substituted parameters - * @param boolean $transliterate Whether to apply transliteration in constructing the identifier base + * @param EntityInterface $entity Entity to assign Identifier for + * @param string $format Identifier assignment format + * @param string $permitted Acceptable characters for substituted parameters + * @param boolean $transliterate Whether to apply transliteration in constructing the identifier base * @return string Identifier with paramaters substituted - * @throws RuntimeException + * @since COmanage Registry v5.0.0 */ protected function substituteParameters( diff --git a/app/plugins/Transmogrify/config/schema/tables.json b/app/plugins/Transmogrify/config/schema/tables.json index c1910e89e..f0836d800 100644 --- a/app/plugins/Transmogrify/config/schema/tables.json +++ b/app/plugins/Transmogrify/config/schema/tables.json @@ -389,6 +389,43 @@ }, "addChangelog": true }, + "identifier_assignments": { + "source": "cm_co_identifier_assignments", + "displayField": "description", + "plugin": null, + "booleans": [ + "login", + "allow_empty" + ], + "cache": ["co_id"], + "postRow": "createIdentifierAssignmentPluginRecord", + "fieldMap": { + "co_group_id": "group_id", + "email_address_type_id": "&mapEmailType", + "identifier_type_id": "&mapIdentifierType", + "plugin": "&mapAlgorithmToPlugin", + "algorithm": null, + "format": null, + "minimum_length": null, + "minimum": null, + "maximum": null, + "transliterate": null, + "permitted": null, + "collision_resolution": null, + "exclusions": null, + "identifier_type": null, + "email_type": null + }, + "addChangelog": true + }, + "format_assigner_sequences": { + "source": "cm_co_sequential_identifier_assignments", + "displayField": "id", + "fieldMap": { + "co_identifier_assignment_id": "format_assigner_id" + }, + "addChangelog": false + }, "__NOTES__": "DATA MIGRATIONS", "authentication_events": { "source": "cm_authentication_events", diff --git a/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php b/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php index 28fa24360..eab8bfacb 100644 --- a/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php +++ b/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php @@ -21,7 +21,7 @@ * * @link https://www.internet2.edu/comanage COmanage Project * @package registry - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ @@ -119,7 +119,7 @@ public function run(array $argv, ConsoleIo $io): int /** * Build an Option Parser. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param ConsoleOptionParser $parser ConsoleOptionParser * @return ConsoleOptionParser ConsoleOptionParser */ @@ -209,7 +209,7 @@ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOption * @param Arguments $args Command Arguments * @param ConsoleIo $io Console IO * @throws Exception - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ public function execute(Arguments $args, ConsoleIo $io): int diff --git a/app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php b/app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php index 34bec3315..8148df656 100644 --- a/app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php +++ b/app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php @@ -119,7 +119,7 @@ protected function cacheFieldById(string $table, array $row, string $field): voi /** * Cache results as configured for the specified table. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param string $table Table to cache * @param array $row Row of table data * @param array $orinRow Original Row of table data @@ -148,7 +148,7 @@ protected function cacheResults(string $table, array $row, array $orinRow): void * @param array $row Row data containing person_id, external_identity_id, group_id etc * @return int Mapped CO ID * @throws \InvalidArgumentException When CO not found - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function findCoId(array $row): int { diff --git a/app/plugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php b/app/plugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php index 95bb2fc07..b65641a71 100644 --- a/app/plugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php +++ b/app/plugins/Transmogrify/src/Lib/Traits/ManageDefaultsTrait.php @@ -38,7 +38,7 @@ trait ManageDefaultsTrait /** * Create an Owners Group for an existing Group. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function createOwnersGroups(): void @@ -190,7 +190,7 @@ protected function mapToDefaultTelephoneNumberTypeId(array $row): ?int /** * Insert default CO Settings for COs that don't have settings. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @return void */ protected function insertDefaultSettings(): void @@ -223,7 +223,7 @@ protected function insertDefaultSettings(): void /** * Insert default Pronoun types for all COs. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @return void */ protected function insertPronounTypes(): void @@ -266,7 +266,7 @@ protected function insertPronounTypes(): void * * @param array $row Row of table data * @return string Default value CANE - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function populateCoSettingsPhone(array $row): string diff --git a/app/plugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php b/app/plugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php index 4aee6e751..2e46be62c 100644 --- a/app/plugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php +++ b/app/plugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php @@ -90,7 +90,7 @@ protected function applyCheckGroupNameARRule(array $origRow, array &$row): void /** * Check if a group membership is actually asserted, and reassign ownerships. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param array $origRow Row of table data (original data) * @param array $row Row of table data (post fixes) * @throws InvalidArgumentException @@ -139,7 +139,7 @@ protected function reconcileGroupMembershipOwnership(array $origRow, array &$row /** * Filter Jobs. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param array $origRow Row of table data (original data) * @param array $row Row of table data (post fixes) * @throws InvalidArgumentException @@ -161,7 +161,7 @@ protected function validateJobIsTransmogrifiable(array $origRow, array &$row): v /** * Translate booleans to string literals to work around DBAL Postgres boolean handling. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param string $table Table Name * @param array $row Row of attributes, fixed in place */ @@ -192,7 +192,7 @@ protected function normalizeBooleanFieldsForDb(string $table, array &$row): void /** * Populate empty Changelog data from legacy records * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param string $table Table Name * @param array $row Row of attributes, fixed in place * @param bool $force If true, always create keys @@ -224,7 +224,7 @@ protected function populateChangelogDefaults(string $table, array &$row, bool $f * before it is removed. * In general, keep all the `unset`, the keys with value null, at the bottom of the tables.json configuration * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param string $table Table Name * @param array $row Row of attributes, fixed in place * @throws InvalidArgumentException @@ -266,7 +266,7 @@ protected function mapLegacyFieldNames(string $table, array &$row): void /** * Process Extended Attributes by converting them to Ad Hoc Attributes. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function migrateExtendedAttributesToAdHocAttributes(): void { @@ -473,6 +473,85 @@ protected function createEnrollmentFlowStep(array $origRow, array $row): void { } } + /** + * Post-row hook: create the FormatAssigner plugin record for an identifier assignment. + * + * Called after the base identifier_assignments row has been inserted. + * Reads format-related fields from the original v4 row and inserts a + * corresponding format_assigners record linked to the newly created + * identifier_assignment_id. + * + * Only runs for algorithm R (Random) or S (Sequential); skipped otherwise. + * + * @param array $originRow Original v4 row from cm_co_identifier_assignments + * @param array $row Mapped v5 row (post field mapping), must contain 'id' + * @return void + * @since COmanage Registry v5.2.0 + */ + protected function createIdentifierAssignmentPluginRecord(array $originRow, array $row): void + { + $algorithm = $originRow['algorithm'] ?? null; + + // Only FormatAssigners are handled here; Plugin algorithm is out of scope + if (!in_array($algorithm, ['R', 'S'], true)) { + return; + } + + $identifierAssignmentId = $row['id'] ?? null; + + if ($identifierAssignmentId === null) { + throw new \InvalidArgumentException( + 'createIdentifierAssignmentPluginRecord: missing id in mapped row' + ); + } + + // Manual normalization because 'format_assigners' is not in tables.json['booleans'] + $transliterate = !empty($originRow['transliterate']); + + // Convert PHP boolean to DB-friendly string + if ($this->outconn->isMySQL()) { + $transliterateVal = ($transliterate ? '1' : '0'); + } else { + $transliterateVal = ($transliterate ? 't' : 'f'); + } + + // Map timestamps to preserve history + $created = $originRow['created'] ?? $this->mapNow([]); + $modified = $originRow['modified'] ?? $this->mapNow([]); + + $formatAssignerRow = [ + 'identifier_assignment_id' => (int)$identifierAssignmentId, + 'format' => $originRow['format'] ?? null, + 'minimum_length' => isset($originRow['minimum_length']) + ? (int)$originRow['minimum_length'] + : null, + 'minimum' => isset($originRow['minimum']) + ? (int)$originRow['minimum'] + : null, + 'maximum' => isset($originRow['maximum']) + ? (int)$originRow['maximum'] + : null, + 'collision_mode' => $algorithm, + 'permitted_characters' => $originRow['permitted'] ?? null, + 'enable_transliteration' => $transliterateVal, + 'created' => $created, + 'modified' => $modified + ]; + + $this->populateChangelogDefaults('format_assigners', $formatAssignerRow, true); + $this->normalizeBooleanFieldsForDb('format_assigners', $formatAssignerRow); + + $qualifiedTableName = $this->outconn->qualifyTableName('format_assigners'); + + try { + $this->outconn->beginTransaction(); + $this->outconn->insert($qualifiedTableName, $formatAssignerRow); + $this->outconn->commit(); + } catch (\Throwable $e) { + $this->outconn->rollBack(); + throw new \RuntimeException("Failed to create Format Assigner record for IdentifierAssignment $identifierAssignmentId: " . $e->getMessage()); + } + } /** * Split an External Identity into an External Identity Role. @@ -480,7 +559,7 @@ protected function createEnrollmentFlowStep(array $origRow, array $row): void { * @param array $origRow Row of table data (original data) * @param array $row Row of table data (post fixes) * @throws Exception - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapExternalIdentityToExternalIdentityRole(array $origRow, array $row): void @@ -597,8 +676,12 @@ private function performFunctionMapping(array &$row, string $oldname, string $fu 'mapCoIdFromApiUserId', ]; - // The pipelines table allows the identifier and email types to be null - if ($table === "pipelines") { + // Tables that allow null types + $tablesWithNullableTypes = [ + 'pipelines', + 'identifier_assignments', + ]; + if (in_array($table, $tablesWithNullableTypes, true)) { $nullableFuncs[] = "mapIdentifierType"; $nullableFuncs[] = "mapEmailType"; } diff --git a/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php b/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php index 6a4b2b794..bba1f973c 100644 --- a/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php +++ b/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php @@ -161,7 +161,7 @@ protected function findAdoptedPersonByLinkedOrgIdentityId(array &$origRow, array * * @param array $row Row data containing address type * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapAddressType(array $row): ?int { @@ -183,7 +183,7 @@ protected function mapAddressType(array $row): ?int * @param array $row Row data containing affiliation * @param int|null $coId CO Id or null if not known * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapAffiliationType(array $row, ?int $coId = null): ?int { @@ -352,7 +352,7 @@ protected function mapAuthenticatorPlugin(array $row): ?string * * @param array $row Row data containing email type * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapEmailType(array $row): ?int { @@ -379,7 +379,7 @@ protected function mapEmailType(array $row): ?int /** * Map an Extended Type attribute name for model name changes. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param array $row Row of table data * @return string Updated attribute name */ @@ -447,7 +447,7 @@ protected function mapHistoricPetitionViewerId(array $row): ?int * * @param array $row Row data containing identifier type * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapIdentifierType(array $row): ?int { @@ -513,7 +513,7 @@ protected function mapMessageTemplateContext(array $row): ?string /** * Map login identifiers, in accordance with the configuration. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param array $origRow Row of table data (original data) * @param array $row Row of table data (post fixes) * @throws \InvalidArgumentException @@ -604,7 +604,7 @@ protected function mapLoginIdentifiers(array $origRow, array &$row): void { * * @param array $row Row data containing name type * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapNameType(array $row): ?int { @@ -624,7 +624,7 @@ protected function mapNameType(array $row): ?int /** * Return a timestamp equivalent to now. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param array $row Row of table data (ignored) * @return string Timestamp */ @@ -644,7 +644,7 @@ protected function mapNow(array $row) { * @param array $row Row of Org Identity table data * @return int|null CO Person ID * @throws Exception - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapOrgIdentityCoPersonId(array $row): ?int { @@ -886,7 +886,7 @@ protected function mapServerTypeToPlugin(array $row): ?string * * @param array $row Row data containing telephone type * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapTelephoneType(array $row): ?int { @@ -903,6 +903,28 @@ protected function mapTelephoneType(array $row): ?int ); } + /** + * Map v4 identifier assignment algorithm to v5 plugin name. + * + * v4 algorithm codes: + * R (Random) -> CoreAssigner.FormatAssigners + * S (Sequential) -> CoreAssigner.FormatAssigners + * P (Plugin) -> not handled here; requires separate plugin resolution + * + * @param array $row Row data containing 'algorithm' from cm_co_identifier_assignments + * @return string|null Fully qualified v5 plugin name, or null for Plugin algorithm + * @since COmanage Registry v5.2.0 + */ + protected function mapAlgorithmToPlugin(array $row): ?string + { + $algorithm = $row['algorithm'] ?? null; + + return match ($algorithm) { + 'R', 'S' => 'CoreAssigner.FormatAssigners', + default => null, + }; + } + /** * Map a type value to its corresponding ID * @@ -912,7 +934,7 @@ protected function mapTelephoneType(array $row): ?int * @param string $attr Attribute name in row data * @return int|null Mapped type ID * @throws \InvalidArgumentException When type not found - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapType(array $row, string $type, int $coId, string $attr = 'type'): ?int { @@ -942,7 +964,7 @@ protected function mapType(array $row, string $type, int $coId, string $attr = ' * * @param array $row Row data containing URL type * @return int|null Mapped type ID - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 */ protected function mapUrlType(array $row): ?int { diff --git a/app/plugins/Transmogrify/src/Lib/Util/RawSqlQueries.php b/app/plugins/Transmogrify/src/Lib/Util/RawSqlQueries.php index d716c5736..191386d39 100644 --- a/app/plugins/Transmogrify/src/Lib/Util/RawSqlQueries.php +++ b/app/plugins/Transmogrify/src/Lib/Util/RawSqlQueries.php @@ -21,7 +21,7 @@ * * @link https://www.internet2.edu/comanage COmanage Project * @package registry - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ @@ -165,7 +165,7 @@ public static function setSequenceId( /** * Return SQL used to select COUs from inbound database. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param string $tableName Name of the SQL table * @param bool $isMySQL Whether the database is MySQL * @return string SQL string to select rows from inbound database @@ -209,7 +209,7 @@ public static function jobHistoryRecordsSqlSelect(string $tableName, bool $isMyS /** * Return SQL used to select COUs from inbound database. * - * @since COmanage Registry v5.0.0 + * @since COmanage Registry v5.2.0 * @param string $tableName Name of the SQL table * @param bool $isMySQL Whether the database is MySQL * @return string SQL string to select rows from inbound database