');
///////////////////////////////////////////////////////////////////////////////
// External key/field naming conventions (sharing the naming space with std attributes
///////////////////////////////////////////////////////////////////////////////
function IsExtKeyField($sColDesc)
{
return ($iPos = strpos($sColDesc, EXTKEY_SEP));
}
function GetExtKeyFieldCodes($sColDesc)
{
$iPos = strpos($sColDesc, EXTKEY_SEP);
return array(
substr($sColDesc, 0, $iPos),
substr($sColDesc, $iPos + strlen(EXTKEY_SEP))
);
}
function MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode)
{
$oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode);
if ($sForeignAttCode == 'id')
{
$sForeignAttLabel = 'id';
}
else
{
$oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode);
$sForeignAttLabel = $oForeignAtt->GetLabel();
}
return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$sForeignAttLabel;
}
function MakeExtFieldSelectValue($sAttCode, $sExtAttCode)
{
return $sAttCode.EXTKEY_SEP.$sExtAttCode;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
function ShowTableForm($oPage, $oCSVParser, $sClass)
{
$aData = $oCSVParser->ToArray(null, 3);
$aColToRow = array();
foreach($aData as $aRow)
{
foreach ($aRow as $sFieldId=>$sValue)
{
$aColToRow[$sFieldId][] = $sValue;
}
}
$aFields = array();
foreach($oCSVParser->ListFields() as $iFieldIndex=>$sFieldName)
{
$sFieldName = trim($sFieldName);
$aOptions = array();
$aOptions['id'] = array(
'LabelHtml' => "Private key",
'LabelRef' => "Private key",
'IsReconcKey' => false,
'Tip' => '',
);
$sFoundAttCode = ""; // quick and dirty way to remind if a match has been found and suggest a reconciliation key if possible
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAtt)
{
if ($oAtt->IsExternalField()) continue;
$bIsThatField = (strcasecmp($sFieldName, $oAtt->GetLabel()) == 0);
$sFoundAttCode = (MetaModel::IsValidFilterCode($sClass, $sAttCode) && $bIsThatField) ? $sAttCode : $sFoundAttCode;
if ($oAtt->IsExternalKey())
{
// An external key might be loaded by
// the pkey or a reconciliation key
//
$aOptions[MakeExtFieldSelectValue($sAttCode, 'id')] = array(
'LabelHtml' => "".$oAtt->GetLabel()." (id)",
'LabelRef' => $oAtt->GetLabel()." (id)",
'IsReconcKey' => MetaModel::IsReconcKey($sClass, $sAttCode),
'Tip' => '',
);
$sRemoteClass = $oAtt->GetTargetClass();
foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sExtAttCode)
{
$sValue = MakeExtFieldSelectValue($sAttCode, $sExtAttCode);
// Create two entries:
// - generic syntax (ext key label -> remote field label)
// - if an ext field exists that corresponds to it, allow its label
$sLabel1 = MakeExtFieldLabel($sClass, $sAttCode, $sExtAttCode);
$bFoundTwin = false;
foreach (MetaModel::GetExternalFields($sClass, $sAttCode) as $oExtFieldAtt)
{
if ($oExtFieldAtt->GetExtAttCode() == $sExtAttCode)
{
$aOptions[$sValue] = array(
'LabelHtml' => htmlentities($oExtFieldAtt->GetLabel()),
'LabelRef' => $oExtFieldAtt->GetLabel(),
'IsReconcKey' => false,
'Tip' => "equivalent to '".htmlentities($sLabel1)."'",
);
$bFoundTwin = true;
$sLabel2 = $oExtFieldAtt->GetLabel();
break;
}
}
$aOptions[$sValue] = array(
'LabelHtml' => htmlentities($sLabel1),
'LabelRef' => $sLabel1,
'IsReconcKey' => false,
'Tip' => $bFoundTwin ? "equivalent to '".htmlentities($sLabel2)."'" : "",
);
}
}
else
{
$aOptions[$sAttCode] = array(
'LabelHtml' => htmlentities($oAtt->GetLabel()),
'LabelRef' => $oAtt->GetLabel(),
'IsReconcKey' => MetaModel::IsReconcKey($sClass, $sAttCode),
'Tip' => '',
);
}
}
// Find the best match
$iMin = strlen($sFieldName);
$sBestValue = null;
foreach ($aOptions as $sValue => $aData)
{
$iDist = levenshtein(strtolower($sFieldName), strtolower($aData['LabelRef']));
if (($iDist != -1) && ($iDist < $iMin))
{
$iMin = $iDist;
$sBestValue = $sValue;
}
}
$sSelField = "";
foreach ($aOptions as $sValue => $aData)
{
$sStyle = '';
$sComment = '';
$sSELECTED = '';
if ($sValue == $sBestValue)
{
$sSELECTED = ' SELECTED';
if ($iMin > 0)
{
$sStyle = " style=\"background-color: #ffdddd;\"";
$sComment = '- suggested';
}
}
$sIsRecondKey = $aData['IsReconcKey'] ? " [rk!]" : "";
$sSelField .= "".$aData['LabelHtml']."$sComment$sIsRecondKey \n";
}
$sSelField .= " ";
$aFields["field$iFieldIndex"]["label"] = $sSelField;
$sCHECKED = ($sFieldName == "id" || MetaModel::IsReconcKey($sClass, $sFoundAttCode)) ? " CHECKED" : "";
$aFields["field$iFieldIndex"]["label"] .= " ";
if (array_key_exists($iFieldIndex, $aColToRow))
{
$aFields["field$iFieldIndex"]["value"] = $aColToRow[$iFieldIndex];
}
}
$oPage->details($aFields);
}
function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CMDBChange $oChange = null)
{
// Note: $oChange can be null, in which case the aim is to check what would be done
// Setup field mapping: sort out between values and other specific columns
//
$iPKeyId = null;
$aReconcilKeys = array();
$aAttList = array();
$aExtKeys = array();
foreach($aFieldMap as $sFieldId=>$sColDesc)
{
$iFieldId = (int) substr($sFieldId, strlen("field"));
if ($sColDesc == "id")
{
// Skip !
$iPKeyId = $iFieldId;
}
elseif ($sColDesc == "__none__")
{
// Skip !
}
elseif (IsExtKeyField($sColDesc))
{
// This field is value to search on, to find a value for an external key
list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc);
$aExtKeys[$sExtKeyAttCode][$sExtReconcKeyAttCode] = $iFieldId;
}
elseif (array_key_exists($sFieldId, $aIsReconcKey))
{
// This value is a reconciliation key
$aReconcilKeys[$sColDesc] = $iFieldId;
$aAttList[$sColDesc] = $iFieldId; // A reconciliation key is also a field
}
else
{
// $sColDesc is an attribute code
$aAttList[$sColDesc] = $iFieldId;
}
}
// Setup result presentation
//
$aDisplayConfig = array();
$aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>"");
$aDisplayConfig["__STATUS__"] = array("label"=>"Import status", "description"=>"");
if (isset($iPKeyId))
{
$aDisplayConfig["col$iPKeyId"] = array("label"=>"id ", "description"=>"");
}
foreach($aReconcilKeys as $sAttCode => $iCol)
{
$sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel();
$aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>"");
}
foreach($aExtKeys as $sAttCode=>$aKeyConfig)
{
$oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sLabel = $oExtKeyAtt->GetLabel();
$aDisplayConfig[$sAttCode] = array("label"=>"$sLabel", "description"=>"");
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
{
// The foreign attribute is one of our reconciliation key
$sLabel = MakeExtFieldLabel($sClass, $sAttCode, $sForeignAttCode);
$aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>"");
}
}
foreach ($aAttList as $sAttCode => $iCol)
{
$sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel();
$aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>"");
}
// Compute the results
//
$aData = $oCSVParser->ToArray();
$oBulk = new BulkChange(
$sClass,
$aData,
$aAttList,
array_keys($aReconcilKeys),
$aExtKeys
);
$aRes = $oBulk->Process($oChange);
$aResultDisp = array(); // to be displayed
foreach($aRes as $iRow => $aRowData)
{
$aRowDisp = array();
$aRowDisp["__RECONCILIATION__"] = $aRowData["__RECONCILIATION__"];
$aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(true);
foreach($aRowData as $sKey => $value)
{
if ($sKey == '__RECONCILIATION__') continue;
if ($sKey == '__STATUS__') continue;
switch (get_class($value))
{
case 'CellChangeSpec_Void':
$sClass = '';
break;
case 'CellChangeSpec_Unchanged':
$sClass = '';
break;
case 'CellChangeSpec_Modify':
$sClass = 'csvimport_ok';
break;
case 'CellChangeSpec_Init':
$sClass = 'csvimport_init';
break;
case 'CellChangeSpec_Issue':
$sClass = 'csvimport_error';
break;
}
if (empty($sClass))
{
$aRowDisp[$sKey] = $value->GetDescription(true);
}
else
{
$aRowDisp[$sKey] = "
".$value->GetDescription(true)."
";
}
}
$aResultDisp[$iRow] = $aRowDisp;
}
$oPage->table($aDisplayConfig, $aResultDisp);
}
///////////////////////////////////////////////////////////////////////////////
// Wizard entry points
///////////////////////////////////////////////////////////////////////////////
function Do_Welcome($oPage, $sClass)
{
$sWiztep = "1_welcome";
$oPage->p("Bulk load from CSV data / step 1 ");
$sCSVData = utils::ReadPostedParam('csvdata');
$oPage->add("");
// As a help to the end-user, let's display the list of possible fields
// for a class, that can be copied/pasted into the CSV area.
$sCurrentList = "";
$aHeadersList = array();
foreach(MetaModel::GetClasses('bizmodel') as $sClassName)
{
$aList = MetaModel::GetZListItems($sClassName, 'details');
$aHeader = array();
// $aHeader[] = MetaModel::GetKeyLabel($sClassName);
$aHeader[] = 'id'; // Should be what's coded on the line above... but there is a bug
foreach($aList as $sAttCode)
{
$aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode);
}
$sAttributes = implode(",", $aHeader);
$aHeadersList[$sClassName] = $sAttributes;
if($sClassName == $sClass)
{
// this class is currently selected
$sCurrentList = $sAttributes;
}
}
// Store all the values in a variable client-side
$aScript = array();
foreach($aHeadersList as $sClassName => $sAttributes)
{
$aScript[] = "'$sClassName':'$sAttributes'";
}
$oPage->add("\n");
$oPage->add_ready_script("$('#select_class').change( function() {DisplayFields(this.value);} );");
$oPage->add(" ");
$oPage->add("Fields for this object$sCurrentList ");
}
function Do_Format($oPage, $sClass)
{
$oPage->p("Bulk load from CSV data / step 2 ");
$sWiztep = "2_format";
$sCSVData = utils::ReadPostedParam('csvdata');
$oCSVParser = new CSVParser($sCSVData);
$sSep = $oCSVParser->GuessSeparator();
$iSkip = $oCSVParser->GuessSkipLines();
// No data ?
$aData = $oCSVParser->ToArray(null);
$iTarget = count($aData);
if ($iTarget == 0)
{
$oPage->p("Empty data set..., please provide some data!");
$oPage->add("Back \n");
return;
}
// Guess the format :
$oPage->p("Guessed separator: '$sSep ' (ASCII=".ord($sSep).")");
$oPage->p("Guessed # of lines to skip: $iSkip");
$oPage->p("Target: $iTarget rows");
$oPage->Add("");
ShowTableForm($oPage, $oCSVParser, $sClass);
$oPage->Add(" ");
$oPage->Add(" ");
$oPage->Add(" ");
$oPage->Add(" ");
$oPage->Add(" ");
$oPage->add(" ");
$oPage->Add(" ");
$oPage->Add(" ");
}
function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null)
{
$sCSVData = utils::ReadPostedParam('csvdata');
$sSep = utils::ReadPostedParam('separator');
$iSkip = utils::ReadPostedParam('skiplines');
$aFieldMap = utils::ReadPostedParam('fmap');
$aIsReconcKey = utils::ReadPostedParam('iskey');
if (empty($aIsReconcKey))
{
$oPage->p("Error: no reconciliation key has been specified. Please specify which field(s) will be used to identify the object");
$oPage->add("Back \n");
$oPage->add("Next \n");
return;
}
$oCSVParser = new CSVParser($sCSVData);
$oCSVParser->SetSeparator($sSep);
$oCSVParser->SetSkipLines($iSkip);
$aData = $oCSVParser->ToArray(null);
$iTarget = count($aData);
$oPage->p("Goal summary ");
$oPage->p("Target: $iTarget rows");
$aSampleData = $oCSVParser->ToArray(array_keys($aFieldMap), 5);
$aDisplayConfig = array();
$aExtKeys = array();
foreach ($aFieldMap as $sFieldId=>$sColDesc)
{
if (array_key_exists($sFieldId, $aIsReconcKey))
{
$sReconcKey = " [search]";
}
else
{
$sReconcKey = "";
}
if ($sColDesc == "id")
{
$aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>"");
}
elseif ($sColDesc == "__none__")
{
// Skip !
}
else if (MetaModel::IsValidAttCode($sClass, $sColDesc))
{
$sAttCode = $sColDesc;
$sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel();
$aDisplayConfig[$sFieldId] = array("label"=>"$sLabel$sReconcKey", "description"=>"");
if (MetaModel::IsValidKeyAttCode($sClass, $sAttCode))
{
$aExtKeys[] = $sAttCode;
}
}
elseif (IsExtKeyField($sColDesc))
{
list($sExtKeyAttCode, $sForeignAttCode) = GetExtKeyFieldCodes($sColDesc);
$aDisplayConfig[$sFieldId] = array("label"=>MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode), "description"=>"");
$aExtKeys[] = $sExtKeyAttCode;
}
else
{
// ???
$aDisplayConfig[$sFieldId] = array("label"=>"-?-?-$sColDesc-?-?-", "description"=>"");
}
}
$oPage->table($aDisplayConfig, $aSampleData);
if ($oChange)
{
$oPage->p("Processing... ");
}
else
{
$oPage->p("Column consistency ");
$aMissingKeys = array();
foreach (MetaModel::GetExternalKeys($sClass) as $sExtKeyAttCode => $oExtKey)
{
if (!in_array($sExtKeyAttCode, $aExtKeys) && !$oExtKey->IsNullAllowed())
{
$aMissingKeys[$sExtKeyAttCode] = $oExtKey;
}
}
if (count($aMissingKeys) > 0)
{
$oPage->p("Warning: the objects could not be created, due to some missing mandatory external keys in the field list: ");
$oPage->add("");
foreach($aMissingKeys as $sAttCode => $oAttDef)
{
$oPage->add("".$oAttDef->GetLabel()." ");
}
$oPage->add(" ");
}
else
{
$oPage->p("ok - required external keys (if any) have been found in the field list");
}
$oPage->p("Note: the procedure will fail if any line has not the same number of columns as the first line");
$oPage->p("Check... ");
}
ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, $oChange);
$oPage->add("");
$oPage->add(" ");
$oPage->add(" ");
$oPage->add(" ");
$oPage->add(" ");
$oPage->add_input_hidden("fmap", $aFieldMap);
$oPage->add_input_hidden("iskey", $aIsReconcKey);
return true;
}
function Do_Verify($oPage, $sClass)
{
$oPage->p("Bulk load from CSV data / step 3 ");
$sWiztep = "3_verify";
if (DoProcessOrVerify($oPage, $sClass, null))
{
// FORM started by DoProcessOrVerify...
$oPage->add(" ");
$oPage->add(" ");
$oPage->add(" ");
$oPage->add(" ");
}
}
function Do_Execute($oPage, $sClass)
{
$oPage->p("Bulk load from CSV data / step 4 ");
$sWiztep = "4_execute";
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$iUser = UserRights::GetContactId();
if ($iUser != null)
{
// Ok, that's dirty, I admit :-)
$oUser = MetaModel::GetObject('bizContact', $iUser);
$sUser = $oUser->GetName();
$oMyChange->Set("userinfo", "CSV Import, by ".$sUser);
}
else
{
$oMyChange->Set("userinfo", "CSV Import");
}
$iChangeId = $oMyChange->DBInsert();
if (DoProcessOrVerify($oPage, $sClass, $oMyChange))
{
// FORM started by DoProcessOrVerify...
$oPage->add(" ");
$oPage->add(" ");
$oPage->add("");
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// M a i n P r o g r a m
//
///////////////////////////////////////////////////////////////////////////////////////////////////
$sFromWiztep = utils::ReadPostedParam('fromwiztep', '');
$sClass = utils::ReadPostedParam('class', '');
$sTodo = utils::ReadPostedParam('todo', '');
switch($sFromWiztep)
{
case '':
Do_Welcome($oPage, $sClass);
break;
case '1_welcome':
if ($sTodo == "Next") Do_Format($oPage, $sClass);
else trigger_error("Wrong argument todo='$sTodo'", E_USER_ERROR);
break;
case '2_format':
if ($sTodo == "Next") Do_Verify($oPage, $sClass);
else Do_Welcome($oPage, $sClass);
break;
case '3_verify':
if ($sTodo == "Next") Do_Execute($oPage, $sClass);
else Do_Format($oPage, $sClass);
break;
case '4_execute':
if ($sTodo == "Next") trigger_error("Wrong argument todo='$sTodo'", E_USER_ERROR);
else Do_Verify($oPage, $sClass);
break;
default:
trigger_error("Wrong argument fromwiztep='$sFromWiztep'", E_USER_ERROR);
}
$oPage->output();
?>