'); /////////////////////////////////////////////////////////////////////////////// // 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) { $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 = ""; $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("
"); $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[] = '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("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->p("Empty data set..., please provide some data!"); $oPage->add("\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'); $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(""); } else { $oPage->p("ok - required external keys (if any) have been found in the field list"); } $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; } 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(); ?>