Skip to content
Permalink
main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
…laneous fixes (NOJIRA)
0 contributors

Users who have contributed to this file

239 lines (204 sloc) 8.08 KB
<?php
/**
* COmanage Match Attribute Manager
*
* Attribute Manager knows about both the wire format and database format in order
* to convert between them.
*
* 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 http://www.internet2.edu/comanage COmanage Project
* @package match
* @since COmanage Match v1.0.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/
declare(strict_types = 1);
namespace App\Lib\Match;
use Cake\Log\Log;
class AttributeManager {
/* We treat attribute groups (eg: preferred name vs official name) the same
* way as attribute types (eg: identifier/national vs identifier/network),
* even though they're somewhat different in the configuration. We store
* attribute types as groups, and in either case use "_default" if not
* supplied.
*
* The format of this array is group => attribute => value
*
* For complex attributes, we concatenate the components with :, creating
* a single attribute key for the array. So, for example:
*
* [_default][dateOfBirth]
* [official][names:given]
* [official][names:family]
* [national][identifiers:identifier]
*/
protected $attributes = [];
// Requested referenceId for forced reconciliation request
protected $requestedReferenceId = null;
/**
* Determine if any attributes were parsed from the source request.
*
* @since COmanage Match v1.0.0
* @return boolean True if at least one attribute was parsed, false otherwise
*/
public function attributesAvailable() {
return !empty($this->attributes);
}
/**
* Obtain the requested Reference ID, if provided in the request.
*
* @since COmanage Match v1.0.0
* @return string Requested Reference ID, or null
*/
public function getRequestedReferenceId() {
return $this->requestedReferenceId;
}
/**
* Obtain the value for an attribute based on an Attribute object.
*
* @since COmanage Match v1.0.0
* @param Attribute $attribute Attribute, including nested AttributeGroup object if set
* @return string Attribute value or null
*/
public function getValueByAttribute(\App\Model\Entity\Attribute $attribute) {
$ret = null;
// We can have three types of attributes here:
// (1) simple, eg "dateOfBirth", context = "_default" (or NULL)
// (2) typed, eg "identifiers:identifier/national", context = type (eg: "national")
// (3) grouped, eg "names:given" where attribute_group_id is not null, context = group name
// The attribute group name needs to match the type, eg for names/type=official
// define attribute_groups:name as "official". We'll need a better solution though
// if we ever had two different types of "official" attributes.
if($attribute->attribute_group_id) {
// Type 3 (Grouped)
$ret = $this->getValueByContext($attribute->api_name, $attribute->attribute_group->name);
} elseif(strpos($attribute->api_name, '/') !== false) {
// Type 2 (Typed)
$a = explode('/', $attribute->api_name);
$ret = $this->getValueByContext($a[0], $a[1]);
} else {
// Type 1 (Simple)
$ret = $this->getValueByContext($attribute->api_name);
}
if($ret
&& $attribute->null_equivalents
&& !preg_match('/[1-9[:alpha:]]/', $ret)) {
// We require one alphabetic character or a number other than 0, otherwise return null
Log::write('debug', "Ignoring null equivalent value '" . $ret . "' for attribute " . $attribute->name);
return null;
}
return $ret;
}
/**
* Obtain the value for an attribute based on the attribute name and context.
*
* @since COmanage Match v1.0.0
* @param string $attribute Attribute name
* @param string $context Attribute context (type or group label)
* @return string Attribute value or null
*/
public function getValueByContext(string $attribute, string $context="_default") {
if(!empty($this->attributes[$context][$attribute])) {
return $this->attributes[$context][$attribute];
}
return null;
}
/**
* Load an array into the Attribute Manager.
*
* @since COmanage Match v1.0.0
* @param array $attributes Array where keys are API Names and values are attribute values
* @throws RuntimeException
*/
public function parseFromArray(array $attributes) {
// First grab the requested Reference ID, if specified.
if(!empty($attributes['referenceid'])) {
$this->requestedReferenceId = $attributes['referenceid'];
}
// API Names might be of the form
// (1) simple, eg "dateOfBirth", context = "_default" (or NULL)
// (2) typed, eg "identifiers:identifier/national", context = type (eg: "national")
// (3) grouped, eg "names:given/official", context = group name
// We treat the second and third forms the same.
foreach($attributes as $a => $v) {
$bits = explode('/', $a, 2);
if(!empty($bits[1])) {
$this->attributes[ $bits[1] ][ $bits[0] ] = $v;
} else {
// Simple attribute
$this->attributes['_default'][$a] = $v;
}
}
}
/**
* Load a JSON object (as returned from json_decode) into the Attribute Manager.
*
* @since COmanage Match v1.0.0
* @param stdClass $json JSON Object from json_decode
* @throws RuntimeException
*/
public function parseFromJSON(\stdClass $json) {
// First grab the requested Reference ID, if specified.
if(!empty($json->referenceId)) {
$this->requestedReferenceId = $json->referenceId;
}
// We parse all attributes given (under "sorAttributes"), regardless of what
// a given matchgrid's configuration is.
if(empty($json->sorAttributes)) {
// This might legitimately be empty, eg for Reassign Reference Identifier,
// which may have no attributes.
return;
}
// We need to "flatten" attributes from wire (hierarchical) notation to
// column (flat) notation.
foreach($json->sorAttributes as $k => $v) {
switch(gettype($v)) {
case 'array':
// We have a set of objects of type $k. We group on type.
foreach($v as $vobject) {
// Group is default unless type is set
$g = "_default";
// For some reason, $vobject is an array if it comes over the API,
// or an object if we get here via /matchgrids/reconcile. So for now
// at least we check both.
if(is_array($vobject) && !empty($vobject['type'])) {
$g = $vobject['type'];
} elseif(!empty($vobject->type)) {
$g = $vobject->type;
}
foreach($vobject as $vk => $vv) {
if($vk == 'type') {
continue;
}
// We'll use $k and $vk to construct the attribute name, eg names:given
$this->attributes[$g][$k.':'.$vk] = $vv;
}
}
break;
case 'integer':
case 'string':
// A simple attribute, eg: dateofBirth: 00-00-0000
$this->attributes['_default'][$k] = $v;
break;
default:
throw new RuntimeException(__('match.er.parse.json', [gettype($v), $k]));
break;
}
}
}
}