From cf8174d54ef79929b5ea9a7ec9b966449b099c3c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 2 Mar 2011 14:38:12 +0000 Subject: [PATCH] Data Exchange - Improved to allow the import of complex attributes like passwords and documents + few fixes SVN:trunk[1106] --- core/attributedef.class.inc.php | 83 ++++++-- core/config.class.inc.php | 2 +- core/dbobject.class.php | 30 +-- core/metamodel.class.php | 29 ++- dictionaries/dictionary.itop.core.php | 2 +- .../model.authent-external.php | 2 +- modules/authent-ldap/model.authent-ldap.php | 2 +- modules/authent-local/model.authent-local.php | 2 +- synchro/synchrodatasource.class.inc.php | 197 +++++++++++------- 9 files changed, 227 insertions(+), 122 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index ddf2d94b3..569b3c317 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -204,12 +204,27 @@ abstract class AttributeDefinition public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!) - public function GetSQLExpressions() {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select) + public function GetSQLExpressions($sPrefix = '') {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select) public function FromSQLToValue($aCols, $sPrefix = '') {return null;} // returns a value out of suffix/value pairs, for SELECT result interpretation public function GetSQLColumns() {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) public function RequiresIndex() {return false;} + // Import - differs slightly from SQL input, but identical in most cases + // + public function GetImportColumns() {return $this->GetSQLColumns();} + public function FromImportToValue($aCols, $sPrefix = '') + { + $aValues = array(); + foreach ($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr) + { + // This is working, based on the assumption that importable fields + // are not computed fields => the expression is the name of a column + $aValues[$sPrefix.$sAlias] = $aCols[$sExpr]; + } + return $this->FromSQLToValue($aValues, $sPrefix); + } + public function GetValidationPattern() { return ''; @@ -414,7 +429,7 @@ class AttributeDBFieldVoid extends AttributeDefinition // protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) - public function GetSQLExpressions() + public function GetSQLExpressions($sPrefix = '') { $aColumns = array(); // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix @@ -1941,9 +1956,16 @@ class AttributeExternalField extends AttributeDefinition return $oExtAttDef->GetSQLCol(); } - public function GetSQLExpressions() + public function GetSQLExpressions($sPrefix = '') { - return array('' => $this->GetCode()); + if ($sPrefix == '') + { + return array('' => $this->GetCode()); + } + else + { + return $sPrefix; + } } public function GetLabel() @@ -2096,7 +2118,7 @@ class AttributeExternalField extends AttributeDefinition // Do not overload GetSQLExpression here because this is handled in the joins - //public function GetSQLExpressions() {return array();} + //public function GetSQLExpressions($sPrefix = '') {return array();} // Here, we get the data... public function FromSQLToValue($aCols, $sPrefix = '') @@ -2187,13 +2209,17 @@ class AttributeBlob extends AttributeDefinition return $proposedValue; } - public function GetSQLExpressions() + public function GetSQLExpressions($sPrefix = '') { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); + } $aColumns = array(); // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $this->GetCode().'_mimetype'; - $aColumns['_data'] = $this->GetCode().'_data'; - $aColumns['_filename'] = $this->GetCode().'_filename'; + $aColumns[''] = $sPrefix.'_mimetype'; + $aColumns['_data'] = $sPrefix.'_data'; + $aColumns['_filename'] = $sPrefix.'_filename'; return $aColumns; } @@ -2331,12 +2357,16 @@ class AttributeOneWayPassword extends AttributeDefinition return $oPassword; } - public function GetSQLExpressions() + public function GetSQLExpressions($sPrefix = '') { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); + } $aColumns = array(); // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $this->GetCode().'_hash'; - $aColumns['_salt'] = $this->GetCode().'_salt'; + $aColumns[''] = $sPrefix.'_hash'; + $aColumns['_salt'] = $sPrefix.'_salt'; return $aColumns; } @@ -2391,6 +2421,27 @@ class AttributeOneWayPassword extends AttributeDefinition return $aColumns; } + public function GetImportColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode()] = 'TINYTEXT'; + return $aColumns; + } + + public function FromImportToValue($aCols, $sPrefix = '') + { + if (!isset($aCols[$sPrefix])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $sClearPwd = $aCols[$sPrefix]; + + $oPassword = new ormPassword('', ''); + $oPassword->SetPassword($sClearPwd); + return $oPassword; + } + public function GetFilterDefinitions() { return array(); @@ -2574,9 +2625,13 @@ class AttributeComputedFieldVoid extends AttributeDefinition // // protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) - public function GetSQLExpressions() + public function GetSQLExpressions($sPrefix = '') { - return array('' => $this->GetCode()); + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); + } + return array('' => $sPrefix); } public function FromSQLToValue($aCols, $sPrefix = '') diff --git a/core/config.class.inc.php b/core/config.class.inc.php index b48f29b35..8c3412259 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -94,7 +94,7 @@ class Config ), 'skip_check_ext_keys' => array( 'type' => 'bool', - 'description' => 'Disable external key check when checking the value of attribtutes', + 'description' => 'Disable external key check when checking the value of attributes', 'default' => false, 'value' => false, 'source_of_value' => '', diff --git a/core/dbobject.class.php b/core/dbobject.class.php index fbaa96d06..ea091eff4 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -396,23 +396,25 @@ abstract class DBObject } } - // Compute scalar attributes that depend on any other type of attribute - public function DoComputeValues() + public function ComputeValues() { - if (is_callable(array($this, 'ComputeValues'))) + } + + // Compute scalar attributes that depend on any other type of attribute + final public function DoComputeValues() + { + // TODO - use a flag rather than checking the call stack -> this will certainly accelerate things + + // First check that we are not currently computing the fields + // (yes, we need to do some things like Set/Get to compute the fields which will in turn trigger the update...) + foreach (debug_backtrace() as $aCallInfo) { - // First check that we are not currently computing the fields - // (yes, we need to do some things like Set/Get to compute the fields which will in turn trigger the update...) - foreach (debug_backtrace() as $aCallInfo) - { - if (!array_key_exists("class", $aCallInfo)) continue; - if ($aCallInfo["class"] != get_class($this)) continue; - if ($aCallInfo["function"] != "ComputeValues") continue; - return; //skip! - } - - $this->ComputeValues(); + if (!array_key_exists("class", $aCallInfo)) continue; + if ($aCallInfo["class"] != get_class($this)) continue; + if ($aCallInfo["function"] != "ComputeValues") continue; + return; //skip! } + $this->ComputeValues(); } public function GetAsHTML($sAttCode) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 68de1a208..2a477423e 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1657,18 +1657,29 @@ abstract class MetaModel } return $aSubClasses; } - public static function GetClasses($sCategory = '') + public static function GetClasses($sCategories = '', $bStrict = false) { - if (array_key_exists($sCategory, self::$m_Category2Class)) + $aCategories = explode(',', $sCategories); + $aClasses = array(); + foreach($aCategories as $sCategory) { - return self::$m_Category2Class[$sCategory]; - } + $sCategory = trim($sCategory); + if (strlen($sCategory) == 0) + { + return array_keys(self::$m_aClassParams); + } - //if (count(self::$m_Category2Class) > 0) - //{ - // throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); - //} - return array(); + if (array_key_exists($sCategory, self::$m_Category2Class)) + { + $aClasses = array_merge($aClasses, self::$m_Category2Class[$sCategory]); + } + elseif ($bStrict) + { + throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); + } + } + + return array_unique($aClasses); } public static function HasTable($sClass) diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 25df08ad5..1a377657d 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -537,7 +537,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:SynchroLogTitle' => '%1$s - %2$s', 'Core:Synchro:Nb_Replica' => 'Replica processed: %1$s', 'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s', - 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified.', + 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified, or the reconciliation policy must be to use the primary key.', 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete', 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.', 'Core:SynchroReplica:PublicData' => 'Public Data', diff --git a/modules/authent-external/model.authent-external.php b/modules/authent-external/model.authent-external.php index 18d440cac..74f1d36d9 100644 --- a/modules/authent-external/model.authent-external.php +++ b/modules/authent-external/model.authent-external.php @@ -40,7 +40,7 @@ class UserExternal extends User "key_type" => "autoincrement", "name_attcode" => "login", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array('login'), "db_table" => "", "db_key_field" => "id", "db_finalclass_field" => "", diff --git a/modules/authent-ldap/model.authent-ldap.php b/modules/authent-ldap/model.authent-ldap.php index 1c356eb42..4ab4aecc9 100644 --- a/modules/authent-ldap/model.authent-ldap.php +++ b/modules/authent-ldap/model.authent-ldap.php @@ -35,7 +35,7 @@ class UserLDAP extends UserInternal "key_type" => "autoincrement", "name_attcode" => "login", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array('login'), "db_table" => "", "db_key_field" => "id", "db_finalclass_field" => "", diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index c4ce53fa5..489dd1352 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -35,7 +35,7 @@ class UserLocal extends UserInternal "key_type" => "autoincrement", "name_attcode" => "login", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array('login'), "db_table" => "priv_user_local", "db_key_field" => "id", "db_finalclass_field" => "", diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php index e4e443afb..5d7197d0e 100644 --- a/synchro/synchrodatasource.class.inc.php +++ b/synchro/synchrodatasource.class.inc.php @@ -52,7 +52,7 @@ class SynchroDataSource extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('implementation,production,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=>null, "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeClass("scope_class", array("class_category"=>"bizmodel", "more_values"=>"", "sql"=>"scope_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("scope_class", array("class_category"=>"bizmodel,addon/authentication", "more_values"=>"", "sql"=>"scope_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); // Declared here for a future usage, but ignored so far MetaModel::Init_AddAttribute(new AttributeString("scope_restriction", array("allowed_values"=>null, "sql"=>"scope_restriction", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); @@ -60,7 +60,7 @@ class SynchroDataSource extends cmdbAbstractObject //MetaModel::Init_AddAttribute(new AttributeDateTime("last_synchro_date", array("allowed_values"=>null, "sql"=>"last_synchro_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); // Format: seconds (int) - MetaModel::Init_AddAttribute(new AttributeDuration("full_load_periodicity", array("allowed_values"=>null, "sql"=>"full_load_periodicity", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDuration("full_load_periodicity", array("allowed_values"=>null, "sql"=>"full_load_periodicity", "default_value"=>86400, "is_null_allowed"=>true, "depends_on"=>array()))); // MetaModel::Init_AddAttribute(new AttributeString("reconciliation_list", array("allowed_values"=>null, "sql"=>"reconciliation_list", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("reconciliation_policy", array("allowed_values"=>new ValueSetEnum('use_primary_key,use_attributes'), "sql"=>"reconciliation_policy", "default_value"=>"use_attributes", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -433,9 +433,9 @@ EOF /* * Overload the standard behavior */ - public function DoCheckToWrite() + public function ComputeValues() { - parent::DoCheckToWrite(); + parent::ComputeValues(); if ($this->IsNew()) { @@ -467,6 +467,10 @@ EOF } $this->Set('attribute_list', $oAttributeSet); } + } + public function DoCheckToWrite() + { + parent::DoCheckToWrite(); // Check that there is at least one reconciliation key defined if ($this->Get('reconciliation_policy') == 'use_attributes') @@ -676,6 +680,70 @@ EOF throw new SynchroExceptionNotStarted(Dict::S('Core:SyncDataSourceAccessRestriction')); } + // Get the list of SQL columns + $sClass = $this->GetTargetClass(); + $aAttCodesExpected = array(); + $aAttCodesToReconcile = array(); + $aAttCodesToUpdate = array(); + $sSelectAtt = "SELECT SynchroAttribute WHERE sync_source_id = :source_id AND (update = 1 OR reconcile = 1)"; + $oSetAtt = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */); + while ($oSyncAtt = $oSetAtt->Fetch()) + { + if ($oSyncAtt->Get('update')) + { + $aAttCodesToUpdate[$oSyncAtt->Get('attcode')] = $oSyncAtt; + } + if ($oSyncAtt->Get('reconcile')) + { + $aAttCodesToReconcile[$oSyncAtt->Get('attcode')] = $oSyncAtt; + } + $aAttCodesExpected[$oSyncAtt->Get('attcode')] = $oSyncAtt; + } + $aColumns = $this->GetSQLColumns(array_keys($aAttCodesExpected)); + $aExtDataFields = array_keys($aColumns); + $aExtDataFields[] = 'primary_key'; + $aExtDataSpec = array( + 'table' => $this->GetDataTable(), + 'join_key' => 'id', + 'fields' => $aExtDataFields + ); + + // Get the list of attributes, determine reconciliation keys and update targets + // + if ($this->Get('reconciliation_policy') == 'use_attributes') + { + $aReconciliationKeys = $aAttCodesToReconcile; + } + elseif ($this->Get('reconciliation_policy') == 'use_primary_key') + { + // Override the setings made at the attribute level ! + $aReconciliationKeys = array("primary_key" => null); + } + + $oStatLog->AddTrace("Update of: {".implode(', ', array_keys($aAttCodesToUpdate))."}"); + $oStatLog->AddTrace("Reconciliation on: {".implode(', ', array_keys($aReconciliationKeys))."}"); + + if (count($aAttCodesToUpdate) == 0) + { + $oStatLog->AddTrace("No attribute to update"); + throw new SynchroExceptionNotStarted('There is no attribute to update'); + } + if (count($aReconciliationKeys) == 0) + { + $oStatLog->AddTrace("No attribute for reconciliation"); + throw new SynchroExceptionNotStarted('No attribute for reconciliation'); + } + + $aAttributes = array(); + foreach($aAttCodesToUpdate as $sAttCode => $oSyncAtt) + { + $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode); + if ($oAttDef->IsWritable() && $oAttDef->IsScalar()) + { + $aAttributes[$sAttCode] = $oSyncAtt; + } + } + $sDeletePolicy = $this->Get('delete_policy'); // Count the replicas @@ -742,58 +810,6 @@ EOF // Get all the replicas that are 'new' or modified // - // Get the list of SQL columns - $sClass = $this->GetTargetClass(); - $aAttCodesExpected = array(); - $aAttCodesToReconcile = array(); - $aAttCodesToUpdate = array(); - $sSelectAtt = "SELECT SynchroAttribute WHERE sync_source_id = :source_id AND (update = 1 OR reconcile = 1)"; - $oSetAtt = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */); - while ($oSyncAtt = $oSetAtt->Fetch()) - { - if ($oSyncAtt->Get('update')) - { - $aAttCodesToUpdate[$oSyncAtt->Get('attcode')] = $oSyncAtt; - } - if ($oSyncAtt->Get('reconcile')) - { - $aAttCodesToReconcile[$oSyncAtt->Get('attcode')] = $oSyncAtt; - } - $aAttCodesExpected[$oSyncAtt->Get('attcode')] = $oSyncAtt; - } - $aColumns = $this->GetSQLColumns(array_keys($aAttCodesExpected)); - $aExtDataFields = array_keys($aColumns); - $aExtDataFields[] = 'primary_key'; - $aExtDataSpec = array( - 'table' => $this->GetDataTable(), - 'join_key' => 'id', - 'fields' => $aExtDataFields - ); - - // Get the list of reconciliation keys - if ($this->Get('reconciliation_policy') == 'use_attributes') - { - $aReconciliationKeys = $aAttCodesToReconcile; - } - elseif ($this->Get('reconciliation_policy') == 'use_primary_key') - { - // Override the setings made at the attribute level ! - $aReconciliationKeys = array("primary_key" => null); - } - - $oStatLog->AddTrace("Update of: {".implode(', ', array_keys($aAttCodesToUpdate))."}"); - $oStatLog->AddTrace("Reconciliation on: {".implode(', ', array_keys($aReconciliationKeys))."}"); - - $aAttributes = array(); - foreach($aAttCodesToUpdate as $sAttCode => $oSyncAtt) - { - $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode); - if ($oAttDef->IsWritable() && $oAttDef->IsScalar()) - { - $aAttributes[$sAttCode] = $oSyncAtt; - } - } - $sSelectToSync = "SELECT SynchroReplica WHERE (status = 'new' OR status = 'modified') AND sync_source_id = :source_id"; $oSetToSync = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToSync), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, $aExtDataSpec, 0 /* limitCount */, 0 /* limitStart */); @@ -855,7 +871,7 @@ EOF } else { - foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) + foreach($oAttDef->GetImportColumns() as $sField => $sDBFieldType) { $aColumns[$sField] = $sDBFieldType; } @@ -1181,8 +1197,10 @@ class SynchroReplica extends DBObject implements iDisplay MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("base_class", array("allowed_values"=>null, "extkey_attcode"=> 'sync_source_id', "target_attcode"=>"scope_class"))); + MetaModel::Init_AddAttribute(new AttributeInteger("dest_id", array("allowed_values"=>null, "sql"=>"dest_id", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeClass("dest_class", array("class_category"=>"bizmodel", "more_values"=>"", "sql"=>"dest_class", "default_value"=>'Organization', "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("dest_class", array("class_category"=>"", "more_values"=>"", "sql"=>"dest_class", "default_value"=>'Organization', "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDateTime("status_last_seen", array("allowed_values"=>null, "sql"=>"status_last_seen", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,synchronized,modified,orphan,obsolete'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -1247,6 +1265,7 @@ class SynchroReplica extends DBObject implements iDisplay // If needed, construct the query used for the reconciliation if (!isset(self::$aSearches[$oDataSource->GetKey()])) { + $aCriterias = array(); foreach($aReconciliationKeys as $sFilterCode => $oSyncAtt) { $aCriterias[] = ($sFilterCode == 'primary_key' ? 'id' : $sFilterCode).' = :'.$sFilterCode; @@ -1498,44 +1517,62 @@ class SynchroReplica extends DBObject implements iDisplay /** * Get the value from the 'Extended Data' located in the synchro_data_xxx table for this replica */ - protected function GetValueFromExtData($sColumnName, $oSyncAtt, &$oStatLog) - { - // $aData should contain attributes defined either for reconciliation or create/update + protected function GetValueFromExtData($sAttCode, $oSyncAtt, &$oStatLog) + { + // $aData should contain attributes defined either for reconciliation or create/update $aData = $this->GetExtendedData(); - // In any case, a null column means "ignore this column" - // - if (is_null($aData[$sColumnName])) - { - return null; - } + $sClass = $this->Get('base_class'); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); if (!is_null($oSyncAtt) && ($oSyncAtt instanceof SynchroAttExtKey)) { + $rawValue = $aData[$sAttCode]; + if (is_null($rawValue)) + { + // Null means "ignore" this attribute + return null; + } + $sReconcAttCode = $oSyncAtt->Get('reconciliation_attcode'); if (!empty($sReconcAttCode)) { - $oDataSource = MetaModel::GetObject('SynchroDataSource', $this->Get('sync_source_id')); - $sClass = $oDataSource->GetTargetClass(); - $oAttDef = MetaModel::GetAttributeDef($sClass, $sColumnName); - $sRemoteClass = $oAttDef->GetTargetClass(); - $oObj = MetaModel::GetObjectByColumn($sRemoteClass, $sReconcAttCode, $aData[$sColumnName], false); - if ($oObj) - { - return $oObj->GetKey(); + $sRemoteClass = $oAttDef->GetTargetClass(); + $oObj = MetaModel::GetObjectByColumn($sRemoteClass, $sReconcAttCode, $rawValue, false); + if ($oObj) + { + $retValue = $oObj->GetKey(); } else { // Note: differs from null (in which case the value would be left unchanged) - $oStatLog->AddTrace("Could not find [unique] object for '$sColumnName': searched on $sReconcAttCode = '$aData[$sColumnName]'", $this); - return 0; + $oStatLog->AddTrace("Could not find [unique] object for '$sAttCode': searched on $sReconcAttCode = '$rawValue'", $this); + $retValue = 0; } } + else + { + $retValue = $rawValue; + } + } + else + { + $aColumns = $oAttDef->GetImportColumns(); + foreach($aColumns as $sColumn => $sFormat) + { + // In any case, a null column means "ignore this attribute" + // + if (is_null($aData[$sColumn])) + { + return null; + } + } + $retValue = $oAttDef->FromImportToValue($aData, $sAttCode); } - return $aData[$sColumnName]; - } - + return $retValue; + } + /** * Maps the given context parameter name to the appropriate filter/search code for this class * @param string $sContextParam Name of the context parameter, i.e. 'org_id'