Skip to content

Commit

Permalink
MVEA for person and orgidentity.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis committed Nov 7, 2025
1 parent a07caee commit 4cac25f
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 45 deletions.
67 changes: 37 additions & 30 deletions app/plugins/Transmogrify/config/schema/tables.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,40 +188,19 @@
"source": "cm_names",
"displayField": "id",
"booleans": ["primary_name"],
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_person_id": "person_id",
"org_identity_id": "external_identity_id",
"type_id": "&mapNameType",
"type": null
}
},
"ad_hoc_attributes": {
"source": "cm_ad_hoc_attributes",
"displayField": "id",
"fieldMap": {
"co_person_role_id": "person_role_id",
"org_identity_id": "external_identity_id",
"co_department_id": null,
"organization_id": null
},
"postTable": "migrateExtendedAttributesToAdHocAttributes"
},
"addresses": {
"source": "cm_addresses",
"displayField": "id",
"fieldMap": {
"co_person_role_id": "person_role_id",
"org_identity_id": "external_identity_id",
"type_id": "&mapAddressType",
"type": null,
"co_department_id": null,
"organization_id": null
}
},
"email_addresses": {
"source": "cm_email_addresses",
"displayField": "id",
"booleans": ["verified"],
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_person_id": "person_id",
"org_identity_id": "external_identity_id",
Expand All @@ -235,6 +214,7 @@
"source": "cm_identifiers",
"displayField": "id",
"booleans": ["login"],
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_group_id": "group_id",
"co_person_id": "person_id",
Expand All @@ -247,25 +227,52 @@
},
"preRow": "mapLoginIdentifiers"
},
"telephone_numbers": {
"source": "cm_telephone_numbers",
"urls": {
"source": "cm_urls",
"displayField": "id",
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_person_id": "person_id",
"org_identity_id": "external_identity_id",
"type_id": "&mapUrlType",
"type": null,
"co_department_id": null,
"organization_id": null
}
},
"ad_hoc_attributes": {
"source": "cm_ad_hoc_attributes",
"displayField": "id",
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_person_role_id": "person_role_id",
"org_identity_id": "external_identity_id",
"type_id": "&mapTelephoneType",
"co_department_id": null,
"organization_id": null
},
"postTable": "migrateExtendedAttributesToAdHocAttributes"
},
"addresses": {
"source": "cm_addresses",
"displayField": "id",
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_person_role_id": "person_role_id",
"org_identity_id": "external_identity_id",
"type_id": "&mapAddressType",
"type": null,
"co_department_id": null,
"organization_id": null
}
},
"urls": {
"source": "cm_urls",
"telephone_numbers": {
"source": "cm_telephone_numbers",
"displayField": "id",
"sqlSelect": "mveaSqlSelect",
"fieldMap": {
"co_person_id": "person_id",
"co_person_role_id": "person_role_id",
"org_identity_id": "external_identity_id",
"type_id": "&mapUrlType",
"type_id": "&mapTelephoneType",
"type": null,
"co_department_id": null,
"organization_id": null
Expand Down
6 changes: 5 additions & 1 deletion app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ protected function findCoId(array $row): int
return $coId;
}

throw new \InvalidArgumentException('CO not found for record');
// For the multiple value attributes we are going to get a lot of misses
// because we only move one org identity and only the latest one. No revisions.
// Which means that all the values that belong to deleted org identities are going
// to be misses.
throw new \InvalidArgumentException("CO not found for record");
}

/**
Expand Down
90 changes: 76 additions & 14 deletions app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
namespace Transmogrify\Lib\Traits;

use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Transmogrify\Lib\Util\RawSqlQueries;
use Cake\Utility\Inflector;
use Transmogrify\Lib\Traits\CacheTrait;
Expand Down Expand Up @@ -71,10 +72,10 @@ trait TypeMapperTrait
* Map address type to corresponding type ID
*
* @param array $row Row data containing address type
* @return int Mapped type ID
* @return int|null Mapped type ID
* @since COmanage Registry v5.0.0
*/
protected function mapAddressType(array $row): int
protected function mapAddressType(array $row): ?int
{
return $this->mapType($row, 'Addresses.type', $this->findCoId($row));
}
Expand All @@ -101,10 +102,10 @@ protected function mapAffiliationType(array $row, ?int $coId = null): ?int
* Map email type to corresponding type ID
*
* @param array $row Row data containing email type
* @return int Mapped type ID
* @return int|null Mapped type ID
* @since COmanage Registry v5.0.0
*/
protected function mapEmailType(array $row): int
protected function mapEmailType(array $row): ?int
{
return $this->mapType($row, 'EmailAddresses.type', $this->findCoId($row));
}
Expand Down Expand Up @@ -136,22 +137,84 @@ protected function mapExtendedType(array $row): string
* Map identifier type to corresponding type ID
*
* @param array $row Row data containing identifier type
* @return int Mapped type ID
* @return int|null Mapped type ID
* @since COmanage Registry v5.0.0
*/
protected function mapIdentifierType(array $row): int
protected function mapIdentifierType(array $row): ?int
{
return $this->mapType($row, 'Identifiers.type', $this->findCoId($row));
}

/**
* Map login identifiers, in accordance with the configuration.
*
* @since COmanage Registry v5.0.0
* @param array $origRow Row of table data (original data)
* @param array $row Row of table data (post fixes)
* @throws \InvalidArgumentException
*/
protected function mapLoginIdentifiers(array $origRow, array &$row): void {
// There might be multiple reasons to copy the row, but we only want to
// copy it once.
$copyRow = false;

if(!empty($origRow['org_identity_id'])) {
if($this->args->getOption('login-identifier-copy')
&& $origRow['login']) {
$copyRow = true;
}

// Note the argument here is the old v4 string (eg "eppn") and not the
// PE foreign key
if($this->args->getOption('login-identifier-type')
&& $origRow['type'] == $this->args->getOption('login-identifier-type')) {
$copyRow = true;
}

// Identifiers attached to External Identities do not have login flags in PE
$row['login'] = false;
}

if($copyRow) {
// Find the Person ID associated with this External Identity ID

if(!empty($this->cache['external_identities']['id'][ $origRow['org_identity_id'] ]['person_id'])) {
// Insert a new row attached to the Person, leave the original record
// (ie: $row) untouched

$copiedRow = [
'person_id' => $this->map_org_identity_co_person_id(['id' => $origRow['org_identity_id']]),
'identifier' => $origRow['identifier'],
'type_id' => $this->map_identifier_type($origRow),
'status' => $origRow['status'],
'login' => true,
'created' => $origRow['created'],
'modified' => $origRow['modified']
];

// Set up changelog and fix booleans
$this->fixChangelog('identifiers', $copiedRow, true);
$this->fixBooleans('identifiers', $copiedRow);

try {
$tableName = 'identifiers';
$qualifiedTableName = $this->outconn->qualifyTableName($tableName);
$this->outconn->insert($qualifiedTableName, $copiedRow);
} catch (UniqueConstraintViolationException $e) {
$this->io->warning("record already exists: " . print_r($copiedRow, true));
}
}
}
}

/**
* Map name type to corresponding type ID
*
* @param array $row Row data containing name type
* @return int Mapped type ID
* @return int|null Mapped type ID
* @since COmanage Registry v5.0.0
*/
protected function mapNameType(array $row): int
protected function mapNameType(array $row): ?int
{
return $this->mapType($row, 'Names.type', $this->findCoId($row));
}
Expand Down Expand Up @@ -207,7 +270,7 @@ protected function mapOrgIdentitycoPersonId(array $row): ?int
$this->cache['org_identities']['co_people'] = [];

// Build cache on first use
$this->io->info('Populating org identity map...');
$this->io->verbose('Populating org identity map...');

$tableName = 'cm_co_org_identity_links';
$changelogFK = 'co_org_identity_link_id';
Expand Down Expand Up @@ -249,7 +312,6 @@ protected function mapOrgIdentitycoPersonId(array $row): ?int
}

if (!empty($this->cache['org_identities']['co_people'][$rowId])) {
// XXX OrgIdentities with no org identity link are not supported in v5
// Return the record with the highest revision number
$revisions = $this->cache['org_identities']['co_people'][$rowId];
$rev = max(array_keys($revisions));
Expand Down Expand Up @@ -277,10 +339,10 @@ protected function mapServerTypeToPlugin(array $row): ?string
* Map telephone type to corresponding type ID
*
* @param array $row Row data containing telephone type
* @return int Mapped type ID
* @return int|null Mapped type ID
* @since COmanage Registry v5.0.0
*/
protected function mapTelephoneType(array $row): int
protected function mapTelephoneType(array $row): ?int
{
return $this->mapType($row, 'TelephoneNumbers.type', $this->findCoId($row));
}
Expand Down Expand Up @@ -323,10 +385,10 @@ protected function mapType(array $row, string $type, int $coId, string $attr = '
* Map URL type to corresponding type ID
*
* @param array $row Row data containing URL type
* @return int Mapped type ID
* @return int|null Mapped type ID
* @since COmanage Registry v5.0.0
*/
protected function mapUrlType(array $row): int
protected function mapUrlType(array $row): ?int
{
return $this->mapType($row, 'Urls.type', $this->findCoId($row));
}
Expand Down
48 changes: 48 additions & 0 deletions app/plugins/Transmogrify/src/Lib/Util/RawSqlQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ public static function roleSqlSelect(string $tableName, bool $isMySQL): string {
return RawSqlQueries::ROLE_SQL_SELECT;
}


/**
* Builds SQL query to select Multiple Value Element Attributes (MVEAs) for valid org identities
*
* @param string $tableName Name of the database table containing MVEAs
* @param bool $isMySQL Whether the target database is MySQL (true) or PostgreSQL (false)
* @return string SQL query string to select MVEA rows that are linked to valid org identities
* @since COmanage Registry v5.2.0
*/
public static function mveaSqlSelect(string $tableName, bool $isMySQL): string {
return str_replace('{table}', $tableName, RawSqlQueries::MVEA_SQL_SELECT);
}

/**
* Return SQL used to select Organization Identities from inbound database.
*
Expand Down Expand Up @@ -632,4 +645,39 @@ public static function orgidentitiesSqlSelect(string $tableName, bool $isMySQL):
FROM cm_co_groups
ORDER BY reason;
SQL;


/**
* SQL template for selecting Multiple Value Element Attributes (MVEAs) that are linked to valid org identities
* Filters records to only include those with:
* - Valid person or org identity references
* - Org identities that have valid non-historical links to people
*
* @since COmanage Registry v5.2.0
*/
final const MVEA_SQL_SELECT = <<<SQL
SELECT *
FROM {table} AS n
WHERE
(n.co_person_id IS NOT NULL OR n.org_identity_id IS NOT NULL)
AND (
n.org_identity_id IS NULL
OR EXISTS (
SELECT 1
FROM cm_org_identities AS oi
WHERE oi.id = n.org_identity_id
AND EXISTS (
SELECT 1
FROM cm_co_org_identity_links AS coil
JOIN cm_co_people AS p
ON p.id = coil.co_person_id
WHERE coil.org_identity_id = oi.id
AND coil.co_org_identity_link_id IS NULL
AND coil.co_person_id IS NOT NULL
)
)
)
ORDER BY n.id;
SQL;

}

0 comments on commit 4cac25f

Please sign in to comment.