Skip to content

Commit

Permalink
Moved Pipeline logic to the PipelineTrait to simplify the code.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis committed Sep 1, 2025
1 parent c1a8f72 commit ace8fb7
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 216 deletions.
226 changes: 226 additions & 0 deletions app/src/Lib/Traits/PipelineTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<?php

/**
* COmanage Registry Pipeline Trait
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.2.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

declare(strict_types = 1);

namespace App\Lib\Traits;

use App\Lib\Enum\ExternalIdentityStatusEnum;
use Cake\Utility\Inflector;

trait PipelineTrait {
/**
* Copy the data from an entity and filter metadata, returning an array
* suitable for creating a new entity. Related models are also removed.
*
* @since COmanage Registry v5.0.0
* @param Entity $entity Entity to copy
* @return array Array of filtered entity data
*/

protected function duplicateFilterEntityData($entity): array {
// There's some overlap with TableMetaTrait::filterMetadataFields...

$newdata = $entity->toArray();

// This list is a combination of eliminating fields that create
// noise in change detection for History creation, as well as
// functional attributes that cause problems if set (eg: frozen).
unset(
$newdata['id'],
$newdata['external_identity_id'],
$newdata['external_identity_role_id'],
$newdata['actor_identifier'],
$newdata['created'],
$newdata['deleted'],
$newdata['frozen'],
$newdata['full_name'],
// XXX we temporarily filter manager and sponsor identifiers because
// we haven't yet implemented support for mapping them
$newdata['manager_identifier'],
$newdata['sponsor_identifier'],
$newdata['modified'],
$newdata['primary_name'],
$newdata['revision'],
$newdata['role_key'],
// We don't want status for the External Identity, and we handle it
// specially for External Identity Roles
$newdata['status']
);

// Get the list of "visible" fields -- this should correlate with the
// set of fields defined on the entity, whether or not they are populated,
// including metadata fields.

$visible = $entity->getVisible();

// Timestamps are FrozenTime objects in the entity data, and is_scalar
// will filter them out, so convert them to strings

foreach(['valid_from', 'valid_through'] as $attr) {
if(in_array($attr, $visible)) {
if(!empty($entity->$attr)) {
$newdata[$attr] = $entity->$attr->i18nFormat('yyyy-MM-dd HH:mm:ss');
} else {
// Populate a blank value so removal works correctly (but don't inject
// the fields to models that don't have them)
$newdata[$attr] = "";
}
}
}

// This will remove anything that isn't stringy
return array_filter($newdata, 'is_scalar');
}

/**
* Map date_of_birth attribute from EIS format to CO format
*
* @param ?array $eisAttributes Array of attributes from EIS backend
* @return array Array containing mapped date_of_birth or empty array
* @since COmanage Registry v5.2.0
*/
protected function mapDateOfBirth(?array $eisAttributes): array {
$ret = [];
if(!empty($eisAttributes['date_of_birth'])) {
$dob = \DateTimeImmutable::createFromFormat('Y-m-d', $eisAttributes['date_of_birth']);
if($dob) {
$ret['date_of_birth'] = $dob->format('Y-m-d');
}
}
return $ret;
}

/**
* Map typed attribute lists from EIS format to CO format
*
* @param int $coId CO ID
* @param ?array $attributes Array of attributes
* @return array Array of mapped typed attributes
* @since COmanage Registry v5.2.0
*/
protected function mapTypedAttributesList(int $coId, ?array $attributes, array $mvModels): array {
if(empty($attributes) || empty($mvModels)) {
return [];
}

$typeOfRecord = match(true) {
isset($attributes['source_key']) => 'EIS',
isset($attributes['role_key']) => 'EIS Role',
default => 'Unknown'
};

$ret = [];
foreach($mvModels as $m) {
if(!empty($attributes[$m])) {
foreach($attributes[$m] as $attr) {
$copy = $attr;
try {
$copy['type_id'] = $this->Cos->Types->getTypeId(
$coId,
Inflector::camelize($m).".type",
$attr['type']
);
unset($copy['type']);
$ret[$m][] = $copy;
}
catch(\Exception $e) {
$this->llog(
'error',
"Failed to map $attr type \"" . $attr['type'] . "\" to a valid Type ID for $typeOfRecord record "
. ($attributes['source_key'] ?? $attributes['role_key'] ?? 'unknown')
. ", skipping"
);
}
}
}
}
return $ret;
}


/**
* Map External Identity Roles from an EIS Record to CO roles.
*
* @param int $coId CO ID
* @param int|null $syncAffiliationTypeId Default affiliation type ID to use
* @param ?array $eisAttributes Array of role attributes from EIS backend
* @return array Array of mapped CO role data
* @since COmanage Registry v5.2.0
*/
protected function mapExternalIdentityRoles(int $coId, ?int $syncAffiliationTypeId, ?array $eisAttributes): array {
$ret = [];
if(empty($eisAttributes['external_identity_roles'])) {
return $ret;
}
foreach($eisAttributes['external_identity_roles'] as $role) {
$rolecopy = [];
// Basic fields for the role
foreach($role as $attr => $val) {
if(is_array($val)) { continue; }
if($attr == 'role_key') {
$rolecopy['role_key'] = (string)$val;
} elseif($attr == 'affiliation') {
$rolecopy['affiliation_type_id'] = $this->Cos->Types->getTypeId(
$coId,
'PersonRoles.affiliation_type',
$val
);
} elseif($attr == 'status') {
$rolecopy['status'] = $val == ExternalIdentityStatusEnum::Deleted ? ExternalIdentityStatusEnum::Archived : $val;
} else {
// XXX need to add sponsor/manager mapping CFM-33; remove from duplicateFilterEntityData
$rolecopy[$attr] = $val;
}
}

// The pipeline affiliation type ID configuration always takes precedence
// XXX Consider adding a default affiliation in the configuration. Currently,
// if neither the pipeline affiliation type ID nor the source affiliation is provided,
// the save operation will fail ORM validation.
$rolecopy['affiliation_type_id'] = $syncAffiliationTypeId ?? $rolecopy['affiliation_type_id'] ?? null;

// Map typed attributes, linked multi-value models
$typed = $this->mapTypedAttributesList($coId, $role, ['addresses','telephone_numbers']);
// Pass the typed attributes through to the role copy
foreach ($typed as $k => $v) {
if (!empty($v)) {
$rolecopy[$k] = $v;
}
}

// Add hoc attributes
if(!empty($role['ad_hoc_attributes'])) {
$rolecopy['ad_hoc_attributes'] = $role['ad_hoc_attributes'];
}
$ret['external_identity_roles'][] = $rolecopy;
}

return $ret;
}
}
Loading

0 comments on commit ace8fb7

Please sign in to comment.