mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-25 11:38:44 +02:00
CSV import: fixed a few issues (restricted to N-N links) + added arguments to the export page, to allow for exporting N-N links
SVN:trunk[1130]
This commit is contained in:
@@ -827,7 +827,11 @@ EOF
|
||||
{
|
||||
$sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma
|
||||
$sTextQualifier = isset($aParams['text_qualifier']) ? $aParams['text_qualifier'] : '"'; // default text qualifier is double quote
|
||||
$aFields = isset($aParams['fields']) ? explode(',', $aParams['fields']) : null;
|
||||
$aFields = null;
|
||||
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0))
|
||||
{
|
||||
$aFields = explode(',', $aParams['fields']);
|
||||
}
|
||||
|
||||
$aList = array();
|
||||
|
||||
@@ -847,13 +851,23 @@ EOF
|
||||
{
|
||||
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (!is_null($aFields) && !in_array($sAttCode, $aFields)) continue;
|
||||
|
||||
if ($oAttDef->IsExternalField() || $oAttDef->IsWritable())
|
||||
if (is_null($aFields) || (count($aFields) == 0))
|
||||
{
|
||||
// Standard list of attributes (no link sets)
|
||||
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
|
||||
{
|
||||
$aList[$sClassName][$sAttCode] = $oAttDef;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User defined list of attributes
|
||||
if (in_array($sAttCode, $aFields))
|
||||
{
|
||||
$aList[$sClassName][$sAttCode] = $oAttDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
$aHeader[] = 'id';
|
||||
foreach($aList[$sClassName] as $sAttCode => $oAttDef)
|
||||
{
|
||||
|
||||
@@ -203,6 +203,7 @@ abstract class AttributeDefinition
|
||||
public function IsNull($proposedValue) {return is_null($proposedValue);}
|
||||
|
||||
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 Equals($val1, $val2) {return ($val1 == $val2);}
|
||||
|
||||
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
|
||||
@@ -411,10 +412,19 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
$aItems = array();
|
||||
while ($oObj = $sValue->Fetch())
|
||||
{
|
||||
$sObjClass = get_class($oObj);
|
||||
// Show only relevant information (hide the external key to the current object)
|
||||
$aAttributes = array();
|
||||
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
|
||||
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($sAttCode == 'finalclass')
|
||||
{
|
||||
if ($sObjClass == $this->GetLinkedClass())
|
||||
{
|
||||
// Simplify the output if the exact class could be determined implicitely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ($sAttCode == $this->GetExtKeyToMe()) continue;
|
||||
if ($oAttDef->IsExternalField()) continue;
|
||||
if (!$oAttDef->IsDirectField()) continue;
|
||||
@@ -472,9 +482,9 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
$aLinks = array();
|
||||
foreach($aInput as $aRow)
|
||||
{
|
||||
$aNewRow = array();
|
||||
$oLink = MetaModel::NewObject($sTargetClass);
|
||||
// 1st - get the values, split the extkey->searchkey specs, and eventually get the finalclass value
|
||||
$aExtKeys = array();
|
||||
$aValues = array();
|
||||
foreach($aRow as $sCell)
|
||||
{
|
||||
$iSepPos = strpos($sCell, $sSepValue);
|
||||
@@ -509,10 +519,35 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
{
|
||||
throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sAttCode));
|
||||
}
|
||||
$aValues[$sAttCode] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2nd - Instanciate the object and set the value
|
||||
if (isset($aValues['finalclass']))
|
||||
{
|
||||
$sLinkClass = $aValues['finalclass'];
|
||||
if (!is_subclass_of($sLinkClass, $sTargetClass))
|
||||
{
|
||||
throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
|
||||
}
|
||||
}
|
||||
elseif (MetaModel::IsAbstract($sTargetClass))
|
||||
{
|
||||
throw new CoreException('Missing finalclass for link attribute specification');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLinkClass = $sTargetClass;
|
||||
}
|
||||
|
||||
$oLink = MetaModel::NewObject($sLinkClass);
|
||||
foreach ($aValues as $sAttCode => $sValue)
|
||||
{
|
||||
$oLink->Set($sAttCode, $sValue);
|
||||
}
|
||||
}
|
||||
// Set external keys from search conditions
|
||||
|
||||
// 3rd - Set external keys from search conditions
|
||||
foreach ($aExtKeys as $sKeyAttCode => $aReconciliation)
|
||||
{
|
||||
$oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
|
||||
@@ -547,6 +582,25 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
|
||||
return $oSet;
|
||||
}
|
||||
|
||||
public function Equals($val1, $val2)
|
||||
{
|
||||
if ($val1 === $val2) return true;
|
||||
|
||||
if (is_object($val1) != is_object($val2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!is_object($val1))
|
||||
{
|
||||
// string ?
|
||||
// todo = implement this case ?
|
||||
return false;
|
||||
}
|
||||
|
||||
// Both values are Object sets
|
||||
return $val1->HasSameContents($val2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -399,7 +399,7 @@ class BulkChange
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef->IsLinkSet())
|
||||
if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -435,6 +435,8 @@ class BulkChange
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->m_bReportHtml)
|
||||
{
|
||||
$sCurValue = $oTargetObj->GetAsHTML($sAttCode);
|
||||
@@ -466,6 +468,7 @@ class BulkChange
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks
|
||||
//
|
||||
|
||||
@@ -826,9 +826,9 @@ abstract class DBObject
|
||||
}
|
||||
elseif(is_object($proposedValue))
|
||||
{
|
||||
$oLinkAttDef = MetaModel::GetAttributeDef(get_class($this), $sAtt);
|
||||
// The value is an object, the comparison is not strict
|
||||
// #@# todo - should be even less strict => add verb on AttributeDefinition: Compare($a, $b)
|
||||
if ($this->m_aOrigValues[$sAtt] != $proposedValue)
|
||||
if (!$oLinkAttDef->Equals($proposedValue, $this->m_aOrigValues[$sAtt]))
|
||||
{
|
||||
$aDelta[$sAtt] = $proposedValue;
|
||||
}
|
||||
@@ -852,17 +852,59 @@ abstract class DBObject
|
||||
// List the attributes that have been changed
|
||||
// Returns an array of attname => currentvalue
|
||||
public function ListChanges()
|
||||
{
|
||||
if ($this->m_bIsInDB)
|
||||
{
|
||||
return $this->ListChangedValues($this->m_aCurrValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->m_aCurrValues;
|
||||
}
|
||||
}
|
||||
|
||||
// Tells whether or not an object was modified
|
||||
// Tells whether or not an object was modified since last read (ie: does it differ from the DB ?)
|
||||
public function IsModified()
|
||||
{
|
||||
$aChanges = $this->ListChanges();
|
||||
return (count($aChanges) != 0);
|
||||
}
|
||||
|
||||
public function Equals($oSibling)
|
||||
{
|
||||
if (get_class($oSibling) != get_class($this))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ($this->GetKey() != $oSibling->GetKey())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ($this->m_bIsInDB)
|
||||
{
|
||||
// If one has changed, then consider them as being different
|
||||
if ($this->IsModified() || $oSibling->IsModified())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Todo - implement this case (loop on every attribute)
|
||||
//foreach(MetaModel::ListAttributeDefs(get_class($this) as $sAttCode => $oAttDef)
|
||||
//{
|
||||
//if (!isset($this->m_CurrentValues[$sAttCode])) continue;
|
||||
//if (!isset($this->m_CurrentValues[$sAttCode])) continue;
|
||||
//if (!$oAttDef->Equals($this->m_CurrentValues[$sAttCode], $oSibling->m_CurrentValues[$sAttCode]))
|
||||
//{
|
||||
//return false;
|
||||
//}
|
||||
//}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// used both by insert/update
|
||||
private function DBWriteLinks()
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ class DBObjectSet
|
||||
|
||||
$this->m_bLoaded = false; // true when the filter has been used OR the set is built step by step (AddObject...)
|
||||
$this->m_aData = array(); // array of (row => array of (classalias) => object/null)
|
||||
$this->m_aId2Row = array();
|
||||
$this->m_aId2Row = array(); // array of (pkey => index in m_aData)
|
||||
$this->m_iCurrRow = 0;
|
||||
}
|
||||
|
||||
@@ -445,6 +445,43 @@ class DBObjectSet
|
||||
return $oNewSet;
|
||||
}
|
||||
|
||||
// Note: This verb works only with objects existing in the database
|
||||
//
|
||||
public function HasSameContents($oObjectSet)
|
||||
{
|
||||
if ($this->GetRootClass() != $oObjectSet->GetRootClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!$this->m_bLoaded) $this->Load();
|
||||
|
||||
if ($this->Count() != $oObjectSet->Count())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$sClassAlias = $this->m_oFilter->GetClassAlias();
|
||||
$oObjectSet->Rewind();
|
||||
while ($oObject = $oObjectSet->Fetch())
|
||||
{
|
||||
$iObjectKey = $oObject->GetKey();
|
||||
if ($iObjectKey < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!array_key_exists($iObjectKey, $this->m_aId2Row[$sClassAlias]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$iRow = $this->m_aId2Row[$sClassAlias][$iObjectKey];
|
||||
$oSibling = $this->m_aData[$iRow][$sClassAlias];
|
||||
if (!$oObject->Equals($oSibling))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function CreateDelta($oObjectSet)
|
||||
{
|
||||
if ($this->GetRootClass() != $oObjectSet->GetRootClass())
|
||||
|
||||
@@ -163,7 +163,7 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMo
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($oAttDef->IsWritable() && ($bAdvancedMode || !$oAttDef->IsLinkset()))
|
||||
else if ($oAttDef->IsWritable() && (!$oAttDef->IsLinkset() || ($bAdvancedMode && $oAttDef->IsIndirect())))
|
||||
{
|
||||
|
||||
if (!$oAttDef->IsNullAllowed())
|
||||
|
||||
@@ -1101,8 +1101,10 @@ EOF
|
||||
|
||||
// Create a truncated version of the data used for the fast preview
|
||||
// Take about 20 lines of data... knowing that some lines may contain carriage returns
|
||||
$iMaxLines = 20;
|
||||
$iMaxLen = strlen($sUTF8Data);
|
||||
if ($iMaxLen > 0)
|
||||
{
|
||||
$iMaxLines = 20;
|
||||
$iCurPos = true;
|
||||
while ( ($iCurPos > 0) && ($iMaxLines > 0))
|
||||
{
|
||||
@@ -1119,6 +1121,11 @@ EOF
|
||||
$iMaxLines--;
|
||||
}
|
||||
$sCSVDataTruncated = substr($sUTF8Data, 0, $iCurPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sCSVDataTruncated = '';
|
||||
}
|
||||
|
||||
$sSynchroScope = utils::ReadParam('synchro_scope', '');
|
||||
if (!empty($sSynchroScope))
|
||||
@@ -1433,7 +1440,7 @@ EOF
|
||||
|
||||
$oPage->output();
|
||||
}
|
||||
catch(CoreException $e)
|
||||
catch(xxxxxxxCoreException $e)
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
@@ -1462,7 +1469,7 @@ catch(CoreException $e)
|
||||
// For debugging only
|
||||
//throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
catch(xxxxxException $e)
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
|
||||
@@ -2139,6 +2139,54 @@ class TestDataExchange extends TestBizModel
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
/*
|
||||
$aScenarios = array(
|
||||
array(
|
||||
'desc' => 'Load user logins',
|
||||
'login' => 'admin',
|
||||
'password' => 'admin',
|
||||
'target_class' => 'UserLocal',
|
||||
'full_load_periodicity' => 3600, // should be ignored in this case
|
||||
'reconciliation_policy' => 'use_attributes',
|
||||
'action_on_zero' => 'create',
|
||||
'action_on_one' => 'update',
|
||||
'action_on_multiple' => 'error',
|
||||
'delete_policy' => 'delete',
|
||||
'delete_policy_update' => '',
|
||||
'delete_policy_retention' => 0,
|
||||
'source_data' => array(
|
||||
array('primary_key', 'login', 'password', 'profile_list'),
|
||||
array(
|
||||
array('user_A', 'login_A', 'password_A', 'profileid:10;reason:he/she is managing services'),
|
||||
),
|
||||
),
|
||||
'target_data' => array(
|
||||
array('login'),
|
||||
array(
|
||||
// Initial state
|
||||
),
|
||||
array(
|
||||
array('login_A'),
|
||||
),
|
||||
),
|
||||
'attributes' => array(
|
||||
'login' => array(
|
||||
'do_reconcile' => true,
|
||||
'do_update' => true,
|
||||
'automatic_prefix' => true, // unique id (for unit testing)
|
||||
),
|
||||
'password' => array(
|
||||
'do_reconcile' => false,
|
||||
'do_update' => true,
|
||||
),
|
||||
'profile_list' => array(
|
||||
'do_reconcile' => false,
|
||||
'do_update' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
*/
|
||||
$aScenarios = array(
|
||||
array(
|
||||
'desc' => 'Simple scenario with delete option (and extkey given as org/name)',
|
||||
@@ -2222,11 +2270,11 @@ class TestDataExchange extends TestBizModel
|
||||
'action_on_multiple' => 'error',
|
||||
'delete_policy' => 'update_then_delete',
|
||||
'delete_policy_update' => 'status:obsolete',
|
||||
'delete_policy_retention' => 5,
|
||||
'delete_policy_retention' => 15,
|
||||
'source_data' => array(
|
||||
array('primary_key', 'org_id', 'name', 'status'),
|
||||
array(
|
||||
array('obj_A', 'OMED', 'obj_A', 'production'),
|
||||
array('obj_A', 'Demo', 'obj_A', 'production'),
|
||||
),
|
||||
array(
|
||||
),
|
||||
@@ -2248,7 +2296,7 @@ class TestDataExchange extends TestBizModel
|
||||
'org_id' => array(
|
||||
'do_reconcile' => true,
|
||||
'do_update' => true,
|
||||
'reconciliation_attcode' => 'code',
|
||||
'reconciliation_attcode' => 'name',
|
||||
),
|
||||
'name' => array(
|
||||
'do_reconcile' => true,
|
||||
|
||||
@@ -42,7 +42,7 @@ $currentOrganization = utils::ReadParam('org_id', '');
|
||||
// Main program
|
||||
$sExpression = utils::ReadParam('expression', '');
|
||||
$sFormat = strtolower(utils::ReadParam('format', 'html'));
|
||||
$sFields = utils::ReadParam('fields', ''); // CSV field list
|
||||
$sFields = utils::ReadParam('fields', ''); // CSV field list (allows to specify link set attributes, still not taken into account for XML export)
|
||||
|
||||
$oP = null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user