diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 146f88e42..bc1008ed3 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -74,12 +74,6 @@ abstract class cmdbAbstractObject extends CMDBObject return "GetForLink()."\" title=\"$sHint\">$sLabel"; } - public function GetHyperlink() - { - $aAvailableFields[MetaModel::GetNameAttributeCode(get_class($this))] = $this->GetName(); - return $this->MakeHyperLink(get_class($this), $this->GetKey(), $aAvailableFields); - } - public function GetDisplayValue($sAttCode) { $sDisplayValue = ""; diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 89bfdd0fa..6b4332d52 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -37,36 +37,55 @@ abstract class CellChangeSpec $this->m_proposedValue = $proposedValue; } - public function GetValue() + static protected function ValueAsHtml($value) { - return $this->m_proposedValue; + if (MetaModel::IsValidObject($value)) + { + return $value->GetHyperLink(); + } + else + { + return htmlentities($value); + } } - abstract public function GetDescription(); + public function GetValue($bHtml = false) + { + if ($bHtml) + { + return self::ValueAsHtml($this->m_proposedValue); + } + else + { + return $this->m_proposedValue; + } + } + + abstract public function GetDescription($bHtml = false); } class CellChangeSpec_Void extends CellChangeSpec { - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue(); + return $this->GetValue($bHtml); } } class CellChangeSpec_Unchanged extends CellChangeSpec { - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue()." (unchanged)"; + return $this->GetValue($bHtml)." (unchanged)"; } } class CellChangeSpec_Init extends CellChangeSpec { - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue(); + return $this->GetValue($bHtml); } } @@ -80,9 +99,9 @@ class CellChangeSpec_Modify extends CellChangeSpec parent::__construct($proposedValue); } - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue()." (previous: ".$this->m_previousValue.")"; + return $this->GetValue($bHtml)." (previous: ".self::ValueAsHtml($this->m_previousValue).")"; } } @@ -96,13 +115,13 @@ class CellChangeSpec_Issue extends CellChangeSpec_Modify parent::__construct($proposedValue, $previousValue); } - public function GetDescription() + public function GetDescription($bHtml = false) { if (is_null($this->m_proposedValue)) { return 'Could not be changed - reason: '.$this->m_sReason; } - return 'Could not be changed to "'.$this->GetValue().'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')'; + return 'Could not be changed to "'.$this->GetValue($bHtml).'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')'; } } @@ -124,12 +143,12 @@ abstract class RowStatus { } - abstract public function GetDescription(); + abstract public function GetDescription($bHtml = false); } class RowStatus_NoChange extends RowStatus { - public function GetDescription() + public function GetDescription($bHtml = false) { return "unchanged"; } @@ -139,12 +158,13 @@ class RowStatus_NewObj extends RowStatus { protected $m_iObjKey; - public function __construct($iObjKey = null) + public function __construct($sClass = '', $iObjKey = null) { $this->m_iObjKey = $iObjKey; + $this->m_sClass = $sClass; } - public function GetDescription() + public function GetDescription($bHtml = false) { if (is_null($this->m_iObjKey)) { @@ -152,7 +172,15 @@ class RowStatus_NewObj extends RowStatus } else { - return 'Created ('.$this->m_iObjKey.')'; + if (empty($this->m_sClass)) + { + $oObj = MetaModel::GetObject($this->m_sClass, $this->m_iObjKey); + return 'Created '.$oObj->GetHyperLink(); + } + else + { + return 'Created (id: '.$this->m_iObjKey.')'; + } } } } @@ -166,7 +194,7 @@ class RowStatus_Modify extends RowStatus $this->m_iChanged = $iChanged; } - public function GetDescription() + public function GetDescription($bHtml = false) { return "update ".$this->m_iChanged." cols"; } @@ -181,7 +209,7 @@ class RowStatus_Issue extends RowStatus $this->m_sReason = $sReason; } - public function GetDescription() + public function GetDescription($bHtml = false) { return 'Skipped - reason:'.$this->m_sReason; } @@ -217,6 +245,19 @@ class BulkChange $this->m_aExtKeys = $aExtKeys; } + static protected function MakeSpecObject($sClass, $iId) + { + $oObj = MetaModel::GetObject($sClass, $iId); + if (is_null($oObj)) + { + return $iId; + } + else + { + return $oObj; + } + } + protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors) { $aResults = array(); @@ -250,23 +291,26 @@ class BulkChange // Report it if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { + if ($oTargetObj->IsNew()) { - $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj); } else { - $aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->GetOriginal($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj, $previousValue); } } else { - $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oTargetObj->Get($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oForeignObj); } break; default: $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), "Found ".$oExtObjects->Count()." matches"); + $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $previousValue, "Found ".$oExtObjects->Count()." matches"); } } @@ -291,18 +335,17 @@ class BulkChange { if (isset($aErrors[$sAttCode])) { - $aResults["col$iCol"]= new CellChangeSpec_Issue($aRowData[$iCol], $oTargetObj->Get($sAttCode), $aErrors[$sAttCode]); + $aResults["col$iCol"]= new CellChangeSpec_Issue($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode), $aErrors[$sAttCode]); } elseif (array_key_exists($sAttCode, $aChangedFields)) { - $originalValue = $oTargetObj->GetOriginal($sAttCode); if ($oTargetObj->IsNew()) { - $aResults["col$iCol"]= new CellChangeSpec_Init($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue); + $aResults["col$iCol"]= new CellChangeSpec_Init($oTargetObj->Get($sAttCode)); } else { - $aResults["col$iCol"]= new CellChangeSpec_Modify($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue); + $aResults["col$iCol"]= new CellChangeSpec_Modify($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); } } else @@ -358,7 +401,7 @@ class BulkChange if ($oChange) { $newID = $oTargetObj->DBInsertTrackedNoReload($oChange); - $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($newID); + $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($this->m_sClass, $newID); } else { @@ -424,7 +467,7 @@ class BulkChange case 1: $oTargetObj = $oReconciliationSet->Fetch(); $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); - $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetKey(); + $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetHyperLink(); // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject break; default: diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index d49f7280b..6608f3218 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -154,7 +154,7 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("objkey", $objkey); $iId = $oMyChangeOp->DBInsertNoReload(); } - private function RecordAttChanges(CMDBChange $oChange, array $aValues) + private function RecordAttChanges(CMDBChange $oChange, array $aValues, array $aOrigValues) { // $aValues is an array of $sAttCode => $value // @@ -167,7 +167,15 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("objclass", get_class($this)); $oMyChangeOp->Set("objkey", $this->GetKey()); $oMyChangeOp->Set("attcode", $sAttCode); - $oMyChangeOp->Set("oldvalue", $this->GetOriginal($sAttCode)); + if (array_key_exists($sAttCode, $aOrigValues)) + { + $sOriginalValue = $aOrigValues[$sAttCode]; + } + else + { + $sOriginalValue = 'undefined'; + } + $oMyChangeOp->Set("oldvalue", $sOriginalValue); $oMyChangeOp->Set("newvalue", $value); $iId = $oMyChangeOp->DBInsertNoReload(); } @@ -262,9 +270,11 @@ abstract class CMDBObject extends DBObject throw new CoreWarning("Attempting to update an unchanged object"); return; } - + + // Save the original values (will be reset to the new values when the object get written to the DB) + $aOriginalValues = $this->m_aOrigValues; $ret = parent::DBUpdate(); - $this->RecordAttChanges(self::$m_oCurrChange, $aChanges); + $this->RecordAttChanges(self::$m_oCurrChange, $aChanges, $aOriginalValues); return $ret; } @@ -355,14 +365,23 @@ abstract class CMDBObject extends DBObject $oObjSet = new CMDBObjectSet($oFilter); $oObjSet->Load(); + // Keep track of the previous values (will be overwritten when the objects are synchronized with the DB) + $aOriginalValues = array(); + $oObjSet->Rewind(); + while ($oItem = $oObjSet->Fetch()) + { + $aOriginalValues[$oItem->GetKey()] = $oItem->m_aOrigValues; + } + // Update in one single efficient query $ret = parent::BulkUpdate($oFilter, $aValues); // Record... in many queries !!! + $oObjSet->Rewind(); while ($oItem = $oObjSet->Fetch()) { $aChangedValues = $oItem->ListChangedValues($aValues); - $oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues); + $oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues, $aOriginalValues[$oItem->GetKey()]); } return $ret; } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index eaca961f4..c74652755 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -33,7 +33,7 @@ abstract class DBObject private $m_bIsInDB = false; // true IIF the object is mapped to a DB record private $m_iKey = null; private $m_aCurrValues = array(); - private $m_aOrigValues = array(); + protected $m_aOrigValues = array(); private $m_bFullyLoaded = false; // Compound objects can be partially loaded private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode @@ -217,6 +217,12 @@ abstract class DBObject public function Set($sAttCode, $value) { + if ($sAttCode == 'finalclass') + { + // Ignore it - this attribute is set upon object creation and that's it + //throw new CoreWarning('Attempting to set the value for the internal attribute \"finalclass\"', array('current value'=>$this->Get('finalclass'), 'new value'=>$value)); + return; + } if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this)))) { throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this)); @@ -348,15 +354,7 @@ abstract class DBObject } $sTargetClass = $oAtt->GetTargetClass(EXTKEY_ABSOLUTE); - $aMakeHLink = array(get_class($this), 'MakeHyperLink'); - if (is_callable($aMakeHLink)) - { - return call_user_func($aMakeHLink, $sTargetClass, $this->Get($sAttCode), $aAvailableFields); - } - else - { - return $this->Get($sAttCode); - } + return $this->MakeHyperLink($sTargetClass, $this->Get($sAttCode), $aAvailableFields); } // That's a standard attribute (might be an ext field or a direct field, etc.) @@ -375,6 +373,20 @@ abstract class DBObject return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sSepEscape); } + protected static function MakeHyperLink($sObjClass, $sObjKey, $aAvailableFields) + { + if ($sObjKey == 0) return 'undefined'; + + return MetaModel::GetName($sObjClass)."::$sObjKey"; + } + + public function GetHyperlink() + { + $aAvailableFields[MetaModel::GetNameAttributeCode(get_class($this))] = $this->GetName(); + return $this->MakeHyperLink(get_class($this), $this->GetKey(), $aAvailableFields); + } + + // could be in the metamodel ? public static function IsValidPKey($value) { diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 51aaadbdb..39d116f49 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2636,6 +2636,12 @@ abstract class MetaModel return self::GetObjectByRow($sClass, $aRow); } + public static function GetHyperLink($sTargetClass, $sOldValue) + { + $oObj = self::GetObject($sTargetClass, $sOldValue); + return $oObj->GetHyperLink(); + } + public static function NewObject($sClass) { self::_check_subclass($sClass); diff --git a/pages/csvimport.php b/pages/csvimport.php index 4d8911e77..24bf03798 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -38,9 +38,17 @@ function GetExtKeyFieldCodes($sColDesc) function MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode) { $oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); - $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); + if ($sForeignAttCode == 'id') + { + $sForeignAttLabel = 'id'; + } + else + { + $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); + $sForeignAttLabel = $oForeignAtt->GetLabel(); + } - return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$oForeignAtt->GetLabel(); + return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$sForeignAttLabel; } function MakeExtFieldSelectValue($sAttCode, $sExtAttCode) @@ -67,27 +75,33 @@ function ShowTableForm($oPage, $oCSVParser, $sClass) $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[$iFieldIndex]; + // 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); } @@ -151,7 +209,7 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM foreach($aFieldMap as $sFieldId=>$sColDesc) { $iFieldId = (int) substr($sFieldId, strlen("field")); - if ($sColDesc == "pkey") + if ($sColDesc == "id") { // Skip ! $iPKeyId = $iFieldId; @@ -162,18 +220,19 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM } 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; } } @@ -182,10 +241,10 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM // $aDisplayConfig = array(); $aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>""); - $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); + $aDisplayConfig["__STATUS__"] = array("label"=>"Import status", "description"=>""); if (isset($iPKeyId)) { - $aDisplayConfig["col$iPKeyId"] = array("label"=>"pkey", "description"=>""); + $aDisplayConfig["col$iPKeyId"] = array("label"=>"id", "description"=>""); } foreach($aReconcilKeys as $sAttCode => $iCol) { @@ -223,13 +282,12 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM $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(); + $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(true); foreach($aRowData as $sKey => $value) { if ($sKey == '__RECONCILIATION__') continue; @@ -255,11 +313,11 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM } if (empty($sClass)) { - $aRowDisp[$sKey] = $value->GetDescription(); + $aRowDisp[$sKey] = $value->GetDescription(true); } else { - $aRowDisp[$sKey] = "
".$value->GetDescription()."
"; + $aRowDisp[$sKey] = "
".$value->GetDescription(true)."
"; } } $aResultDisp[$iRow] = $aRowDisp; @@ -297,7 +355,7 @@ function Do_Welcome($oPage, $sClass) $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 + $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); @@ -347,12 +405,9 @@ function Do_Format($oPage, $sClass) $iTarget = count($aData); if ($iTarget == 0) { - $oPage->add("Empty data set..."); - $oPage->add("
"); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add("
"); + $oPage->p("Empty data set..., please provide some data!"); + $oPage->add("\n"); + return; } // Guess the format : @@ -405,9 +460,9 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $sReconcKey = ""; } - if ($sColDesc == "pkey") + if ($sColDesc == "id") { - $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>"blah pkey"); + $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>""); } elseif ($sColDesc == "__none__") { @@ -455,12 +510,16 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) 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->p(""); + } + else + { + $oPage->p("ok - required external keys (if any) have been found in the field list"); } $oPage->p("

Check...

");