'); class CSVParser { private $m_sCSVData; private $m_sSep; private $m_iSkip; public function __construct($sTxt) { $this->m_sCSVData = $sTxt; } public function SetSeparator($sSep) { $this->m_sSep = $sSep; } public function GetSeparator() { return $this->m_sSep; } public function SetSkipLines($iSkip) { $this->m_iSkip = $iSkip; } public function GetSkipLines() { return $this->m_iSkip; } public function GuessSeparator() { // Note: skip the first line anyway $aKnownSeps = array(';', ',', "\t"); // Use double quote for special chars!!! $aStatsBySeparator = array(); foreach ($aKnownSeps as $sSep) { $aStatsBySeparator[$sSep] = array(); } foreach(split("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; if (empty($sLine)) continue; $aLineCharsCount = count_chars($sLine, 0); foreach ($aKnownSeps as $sSep) { $aStatsBySeparator[$sSep][] = $aLineCharsCount[ord($sSep)]; } } // Default to ',' $this->SetSeparator(","); foreach ($aKnownSeps as $sSep) { // Note: this function is NOT available :-( // stats_variance($aStatsBySeparator[$sSep]); $iMin = min($aStatsBySeparator[$sSep]); $iMax = max($aStatsBySeparator[$sSep]); if (($iMin == $iMax) && ($iMax > 0)) { $this->SetSeparator($sSep); break; } } return $this->GetSeparator(); } public function GuessSkipLines() { // Take the FIRST -valuable- LINE ONLY // If there is a number, then for sure this is not a header line // Otherwise, we may consider that there is one line to skip foreach(split("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; if (empty($sLine)) continue; foreach (split($this->m_sSep, $sLine) as $value) { if (is_numeric($value)) { $this->SetSkipLines(0); return 0; } } $this->SetSkipLines(1); return 1; } } function ToArray($aFieldMap, $iMax = 0) { // $aFieldMap is an array of col_index=>col_name // $iMax is a limit $aRes = array(); $iCount = 0; $iSkipped = 0; foreach(split("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; if (empty($sLine)) continue; if ($iSkipped < $this->m_iSkip) { $iSkipped++; continue; } foreach (split($this->m_sSep, $sLine) as $iCol=>$sValue) { if (is_array($aFieldMap)) $sColRef = $aFieldMap[$iCol]; else $sColRef = "field$iCol"; $aRes[$iCount][$sColRef] = $sValue; } $iCount++; if (($iMax > 0) && ($iCount >= $iMax)) break; } return $aRes; } public function ListFields() { // Take the first valuable line foreach(split("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; if (empty($sLine)) continue; // We've got the first valuable line, that's it! break; } $aRet = array(); foreach (split($this->m_sSep, $sLine) as $iCol=>$value) { if ($this->m_iSkip == 0) { // No header to help us $sLabel = "field $iCol"; } else { $sLabel = "$value"; } $aRet[] = $sLabel; } return $aRet; } } /////////////////////////////////////////////////////////////////////////////// // 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); $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$oForeignAtt->GetLabel(); } 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) { $sSelField = ""; $sCHECKED = ($sFieldName == "pkey" || MetaModel::IsReconcKey($sClass, $sFoundAttCode)) ? " CHECKED" : ""; $sSelField .= " "; $aFields["field$iFieldIndex"]["label"] = $sSelField; $aFields["field$iFieldIndex"]["value"] = $aColToRow["field$iFieldIndex"]; } $oPage->details($aFields); } function PrepareObject(&$oTargetObj, $aRowData, $aAttList, $aExtKeys, &$aWarnings, &$aErrors) { $aResults = array(); $aWarnings = array(); $aErrors = array(); // External keys reconciliation // foreach($aExtKeys as $sAttCode=>$aKeyConfig) { $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); $oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass()); foreach ($aKeyConfig as $iCol => $sForeignAttCode) { // The foreign attribute is one of our reconciliation key $sFieldId = MakeExtFieldSelectValue($sAttCode, $sForeignAttCode); $oReconFilter->AddCondition($sForeignAttCode, $aRowData[$sFieldId], '='); $aResults["col$iCol"]= "
".$aRowData[$sFieldId]."
"; } $oExtObjects = new CMDBObjectSet($oReconFilter); switch($oExtObjects->Count()) { case 0: $aErrors[$sAttCode] = "Object not found"; $aResults[$sAttCode]= "
".$aErrors[$sAttCode]."
"; break; case 1: // Do change the external key attribute $oForeignObj = $oExtObjects->Fetch(); $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); // Report it if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { $aResults[$sAttCode]= "
".$oForeignObj->GetName()."
"; } else { $aResults[$sAttCode]= "
".$oForeignObj->GetName()."
"; } break; default: $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; $aResults[$sAttCode]= "
".$aErrors[$sAttCode]."
"; } } // Set the object attributes // foreach ($aAttList as $iCol => $sAttCode) { $oTargetObj->Set($sAttCode, $aRowData[$sAttCode]); } // Reporting on fields // $aChangedFields = $oTargetObj->ListChanges(); foreach ($aAttList as $iCol => $sAttCode) { // By default... nothing happens $sClass = ""; $sMoreInfo = ""; // Override if the attribute has changed if (array_key_exists($sAttCode, $aChangedFields)) { $sClass = "csvimport_ok"; } // Override if a warning is found if (isset($aWarnings[$sAttCode])) { $sClass = "csvimport_warning"; $sMoreInfo .= ", ".$aWarnings[$sAttCode]; } // Override if an error is found if (isset($aErrors[$sAttCode])) { $sClass = "csvimport_error"; $sMoreInfo = ", ".$aErrors[$sAttCode]; } $aResults["col$iCol"]= "
".$aRowData[$sAttCode].$sMoreInfo."
"; } // Checks // if (!$oTargetObj->CheckConsistency()) { $aErrors["GLOBAL"] = "Attributes not consistent with each others"; } return $aResults; } function CreateObject(&$aResult, $iRow, $sClass, $aRowData, $aAttList, $aExtKeys, CMDBChange $oChange = null) { $oTargetObj = MetaModel::NewObject($sClass); $aResult[$iRow] = PrepareObject($oTargetObj, $aRowData, $aAttList, $aExtKeys, $aWarnings, $aErrors); if (count($aErrors) > 0) { $sErrors = implode(', ', $aErrors); $aResult[$iRow]["__STATUS__"] = "Unexpected attribute value(s)"; return; } // Check that any external key will have a value proposed // Could be said once for all rows !!! foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAtt) { if (!$oAtt->IsExternalKey()) continue; //if (!in_array($sAttCode, $aAttList)) //{ // $aResult[$iRow]["__STATUS__"] = "Could not be created - Missing external key (".$oAtt->GetLabel().")"; // return; //} } // Optionaly record the results // if ($oChange) { $newID = $oTargetObj->DBInsertTracked($oChange); $aResult[$iRow]["__STATUS__"] = "Created: ".$oTargetObj->GetName(); } else { $aResult[$iRow]["__STATUS__"] = "Create"; } } function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, $aAttList, $aExtKeys, CMDBChange $oChange = null) { $aResult[$iRow] = PrepareObject($oTargetObj, $aRowData, $aAttList, $aExtKeys, $aWarnings, $aErrors); // Reporting // if (count($aErrors) > 0) { $sErrors = implode(', ', $aErrors); $aResult[$iRow]["__STATUS__"] = "Unexpected attribute value(s)"; return; } $aChangedFields = $oTargetObj->ListChanges(); if (count($aChangedFields) > 0) { $sVerb = $oChange ? "Updated" : "Update"; $aResult[$iRow]["__STATUS__"] = "$sVerb ".count($aChangedFields)." cols"; // Optionaly record the results // if ($oChange) { $oTargetObj->DBUpdateTracked($oChange); } } else { $aResult[$iRow]["__STATUS__"] = "No change"; } } 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 == "pkey") { // Skip ! $iPKeyId = $iFieldId; } elseif ($sColDesc == "__none__") { // Skip ! } elseif (IsExtKeyField($sColDesc)) { list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); $aExtKeys[$sExtKeyAttCode][$iFieldId] = $sExtReconcKeyAttCode; } elseif (array_key_exists($sFieldId, $aIsReconcKey)) { $aReconcilKeys[$iFieldId] = $sColDesc; $aAttList[$iFieldId] = $sColDesc; // A reconciliation key is also a field } else { // $sColDesc is an attribute code // $aAttList[$iFieldId] = $sColDesc; } } // Setup result presentation // $aDisplayConfig = array(); $aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>""); $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); if (isset($iPKeyId)) { $aDisplayConfig["col$iPKeyId"] = array("label"=>"pkey", "description"=>""); } foreach($aReconcilKeys as $iCol=>$sAttCode) { $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 $iCol => $sForeignAttCode) { // The foreign attribute is one of our reconciliation key $sLabel = MakeExtFieldLabel($sClass, $sAttCode, $sForeignAttCode); $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); } } foreach ($aAttList as $iCol => $sAttCode) { $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); } // Compute the results // $aResult = array(); foreach($oCSVParser->ToArray(array_values($aFieldMap)) as $iRow => $aRowData) { $oReconciliationFilter = new CMDBSearchFilter($sClass); if (isset($iPKeyId)) { $oReconciliationFilter->AddCondition("pkey", $aRowData["pkey"], '='); } foreach($aReconcilKeys as $iCol=>$sAttCode) { $sSearchAttCode = $aFieldMap['field'.$iCol]; $oReconciliationFilter->AddCondition($sSearchAttCode, $aRowData[$sSearchAttCode], '='); } $oReconciliationSet = new CMDBObjectSet($oReconciliationFilter); switch($oReconciliationSet->Count()) { case 0: CreateObject($aResult, $iRow, $sClass, $aRowData, $aAttList, $aExtKeys, $oChange); // $aResult[$iRow]["__STATUS__"]=> set in CreateObject $aResult[$iRow]["__RECONCILIATION__"] = "Object not found"; break; case 1: $oTargetObj = $oReconciliationSet->Fetch(); UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $aAttList, $aExtKeys, $oChange); $aResult[$iRow]["__RECONCILIATION__"] = "Found a match: ".$oTargetObj->GetName(); // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject break; default: foreach ($aAttList as $iCol => $sAttCode) { $aResult[$iRow]["col$iCol"]= $aRowData[$sAttCode]; } $aResult[$iRow]["__RECONCILIATION__"] = "Found ".$oReconciliationSet->Count()." matches"; $aResult[$iRow]["__STATUS__"]= "skipped"; } // Whatever happened, do report the reconciliation values if (isset($iPKeyId)) { $aResult[$iRow]["col$iPKeyId"] = "
".$aRowData["pkey"]."
"; } foreach($aReconcilKeys as $iCol=>$sAttCode) { $aResult[$iRow]["col$iCol"] = "
".$aRowData[$sAttCode]."
"; } } $oPage->table($aDisplayConfig, $aResult); } /////////////////////////////////////////////////////////////////////////////// // 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("
"); $oPage->MakeClassesSelect("class", $sClass, 50); //$oPage->Add(""); $oPage->add("
"); $oPage->add(""); $oPage->add("
"); $oPage->add(""); $oPage->add("
\n"); $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[] = 'pkey'; // 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("Fields for this object: "); } 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->add("Empty data set..."); $oPage->add("
"); $oPage->add(""); $oPage->add(""); $oPage->add(""); $oPage->add("
"); } // 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'); $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(null, 5); $aDisplayConfig = array(); foreach ($aFieldMap as $sFieldId=>$sColDesc) { if (array_key_exists($sFieldId, $aIsReconcKey)) { $sReconcKey = " [search]"; } else { $sReconcKey = ""; } if ($sColDesc == "pkey") { $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>"blah pkey"); } 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"=>""); } elseif (IsExtKeyField($sColDesc)) { list($sExtKeyAttCode, $sForeignAttCode) = GetExtKeyFieldCodes($sColDesc); $aDisplayConfig[$sFieldId] = array("label"=>MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode), "description"=>""); } else { // ??? $aDisplayConfig[$sFieldId] = array("label"=>"-?-?-$sColDesc-?-?-", "description"=>""); } } $oPage->table($aDisplayConfig, $aSampleData); if ($oChange) { $oPage->p("

Processing...

"); } else { $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); } function Do_Verify($oPage, $sClass) { $oPage->p("

Bulk load from CSV data / step 3

"); $sWiztep = "3_verify"; 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()); $oMyChange->Set("userinfo", "CSV Import"); $iChangeId = $oMyChange->DBInsert(); 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(); ?>