Skip to content

Commit

Permalink
Transmogrify IdentifierAssignments
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis committed Feb 18, 2026
1 parent d3de1ea commit 261878d
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
37 changes: 37 additions & 0 deletions app/plugins/Transmogrify/config/schema/tables.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*/

Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
101 changes: 92 additions & 9 deletions app/plugins/Transmogrify/src/Lib/Traits/RowTransformationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -473,14 +473,93 @@ 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.
*
* @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
Expand Down Expand Up @@ -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";
}
Expand Down
Loading

0 comments on commit 261878d

Please sign in to comment.