diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 746bac4f3..23f95c86f 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -6503,10 +6503,10 @@ class AttributeCustomFields extends AttributeDefinition * @param array|null $aValues * @return CustomFieldsHandler */ - public function GetHandler(DBObject $oHostObject, $aValues = null) + public function GetHandler($aValues = null) { $sHandlerClass = $this->Get('handler_class'); - $oHandler = new $sHandlerClass($oHostObject, $this->GetCode()); + $oHandler = new $sHandlerClass($this->GetCode()); if (!is_null($aValues)) { $oHandler->SetCurrentValues($aValues); @@ -6581,11 +6581,23 @@ class AttributeCustomFields extends AttributeDefinition */ public function GetForm(DBObject $oHostObject, $sFormPrefix = null) { - $oValue = $oHostObject->Get($this->GetCode()); - $oHandler = $this->GetHandler($oHostObject, $oValue->GetValues()); - $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode(); - $oHandler->BuildForm($sFormId); - return $oHandler->GetForm(); + try + { + $oValue = $oHostObject->Get($this->GetCode()); + $oHandler = $this->GetHandler($oValue->GetValues()); + $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode(); + $oHandler->BuildForm($oHostObject, $sFormId); + $oForm = $oHandler->GetForm(); + } + catch (Exception $e) + { + $oForm = new \Combodo\iTop\Form\Form(''); + $oField = new \Combodo\iTop\Form\Field\LabelField(''); + $oField->SetLabel('Custom field error: '.$e->getMessage()); + $oForm->AddField($oField); + $oForm->Finalize(); + } + return $oForm; } /** @@ -6595,9 +6607,16 @@ class AttributeCustomFields extends AttributeDefinition */ public function ReadValue($oHostObject) { - $oHandler = $this->GetHandler($oHostObject); - $aValues = $oHandler->ReadValues(); - $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); + try + { + $oHandler = $this->GetHandler(); + $aValues = $oHandler->ReadValues($oHostObject); + $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); + } + catch (Exception $e) + { + $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode()); + } return $oRet; } @@ -6611,18 +6630,18 @@ class AttributeCustomFields extends AttributeDefinition { if (is_null($oValue)) { - $oHandler = $this->GetHandler($oHostObject); + $oHandler = $this->GetHandler(); $aValues = array(); } else { // Pass the values through the form to make sure that they are correct - $oHandler = $this->GetHandler($oHostObject, $oValue->GetValues()); - $oHandler->BuildForm(''); + $oHandler = $this->GetHandler($oValue->GetValues()); + $oHandler->BuildForm($oHostObject, ''); $oForm = $oHandler->GetForm(); $aValues = $oForm->GetCurrentValues(); } - return $oHandler->WriteValues($aValues); + return $oHandler->WriteValues($oHostObject, $aValues); } /** @@ -6635,8 +6654,8 @@ class AttributeCustomFields extends AttributeDefinition { try { - $oHandler = $this->GetHandler($oHostObject, $value->GetValues()); - $oHandler->BuildForm(''); + $oHandler = $this->GetHandler($value->GetValues()); + $oHandler->BuildForm($oHostObject, ''); $oForm = $oHandler->GetForm(); $oForm->Validate(); if ($oForm->GetValid()) @@ -6667,23 +6686,50 @@ class AttributeCustomFields extends AttributeDefinition public function DeleteValue(DBObject $oHostObject) { $oValue = $oHostObject->Get($this->GetCode()); - $oHandler = $this->GetHandler($oHostObject, $oValue->GetValues()); - return $oHandler->DeleteValues(); + $oHandler = $this->GetHandler($oValue->GetValues()); + return $oHandler->DeleteValues($oHostObject); } public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { - return $value->GetAsHTML($bLocalize); + try + { + $sRet = $value->GetAsHTML($bLocalize); + } + catch (Exception $e) + { + $sRet = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); + } + return $sRet; } public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - return $value->GetAsXML($bLocalize); + try + { + $sRet = $value->GetAsXML($bLocalize); + } + catch (Exception $e) + { + $sRet = Str::pure2xml('Custom field error: '.$e->getMessage()); + } + return $sRet; } public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) { - return $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText); + try + { + $sRet = $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText); + } + catch (Exception $e) + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, 'Custom field error: '.$e->getMessage()); + $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; + } + return $sRet; } /** @@ -6704,7 +6750,15 @@ class AttributeCustomFields extends AttributeDefinition */ public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) { - return $value->GetForTemplate($sVerb, $bLocalize); + try + { + $sRet = $value->GetForTemplate($sVerb, $bLocalize); + } + catch (Exception $e) + { + $sRet = 'Custom field error: '.$e->getMessage(); + } + return $sRet; } public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) @@ -6732,7 +6786,15 @@ class AttributeCustomFields extends AttributeDefinition public function Equals($val1, $val2) { - return $val1->Equals($val2); + try + { + $bEquals = $val1->Equals($val2); + } + catch (Exception $e) + { + false; + } + return $bEquals; } } diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 2937df71c..38b23eeb0 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -910,4 +910,70 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks return $sResult; } } -?> + +/** + * Record the modification of custom fields + * + * @package iTopORM + */ +class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_custfields", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeLongText("prevdata", array("allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + $sResult = ''; + if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) + { + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $aValues = json_decode($this->Get('prevdata'), true); + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + + try + { + $oHandler = $oAttDef->GetHandler($aValues); + $sValueDesc = $oHandler->GetAsHTML($aValues); + } + catch (Exception $e) + { + $sValueDesc = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); + } + $sTextView = '
'.$sValueDesc.'
'; + + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView); + } + } + return $sResult; + } +} diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index c9e424d65..78aedbd72 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -396,6 +396,17 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("newvalue", $value[$sAttCode]); $iId = $oMyChangeOp->DBInsertNoReload(); } + elseif ($oAttDef instanceOf AttributeCustomFields) + { + // Custom fields + // + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields"); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + $oMyChangeOp->Set("prevdata", json_encode($original->GetValues())); + $iId = $oMyChangeOp->DBInsertNoReload(); + } else { // Scalars @@ -536,13 +547,13 @@ abstract class CMDBObject extends DBObject public static function BulkUpdate(DBSearch $oFilter, array $aValues) { - return $this->BulkUpdateTracked_Internal($oFilter, $aValues); + return static::BulkUpdateTracked_Internal($oFilter, $aValues); } public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues) { self::SetCurrentChange($oChange); - $this->BulkUpdateTracked_Internal($oFilter, $aValues); + static::BulkUpdateTracked_Internal($oFilter, $aValues); } protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) diff --git a/core/customfieldshandler.class.inc.php b/core/customfieldshandler.class.inc.php index 2a4110bda..1befb9c1d 100644 --- a/core/customfieldshandler.class.inc.php +++ b/core/customfieldshandler.class.inc.php @@ -28,7 +28,6 @@ use Combodo\iTop\Form\FormManager; abstract class CustomFieldsHandler { - protected $oHostObject; protected $sAttCode; protected $aValues; protected $oForm; @@ -37,17 +36,15 @@ abstract class CustomFieldsHandler * This constructor's prototype must be frozen. * Any specific behavior must be implemented in BuildForm() * - * @param DBObject $oHostObject * @param $sAttCode */ - final public function __construct(DBObject $oHostObject, $sAttCode) + final public function __construct($sAttCode) { - $this->oHostObject = $oHostObject; $this->sAttCode = $sAttCode; $this->aValues = null; } - abstract public function BuildForm($sFormId); + abstract public function BuildForm(DBObject $oHostObject, $sFormId); /** * @@ -109,21 +106,24 @@ abstract class CustomFieldsHandler abstract public function GetAsCSV($aValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true); /** + * @param DBObject $oHostObject * @return array Associative array id => value */ - abstract public function ReadValues(); + abstract public function ReadValues(DBObject $oHostObject); /** * Record the data (currently in the processing of recording the host object) * It is assumed that the data has been checked prior to calling Write() + * @param DBObject $oHostObject * @param array Associative array id => value */ - abstract public function WriteValues($aValues); + abstract public function WriteValues(DBObject $oHostObject, $aValues); /** * Cleanup data upon object deletion (object id still available here) + * @param DBObject $oHostObject */ - abstract public function DeleteValues(); + abstract public function DeleteValues(DBObject $oHostObject); /** * @param $aValuesA diff --git a/core/dbobject.class.php b/core/dbobject.class.php index c0ed8e287..35a079e16 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1428,6 +1428,8 @@ abstract class DBObject implements iDisplay { if (!$oAttDef->LoadInObject()) continue; if ($oAttDef->LoadFromDB()) continue; + if (!array_key_exists($sAttCode, $this->m_aTouchedAtt)) continue; + if (array_key_exists($sAttCode, $this->m_aModifiedAtt) && ($this->m_aModifiedAtt[$sAttCode] == false)) continue; $oAttDef->WriteValue($this, $this->m_aCurrValues[$sAttCode]); } } @@ -1841,11 +1843,15 @@ abstract class DBObject implements iDisplay $bHasANewExternalKeyValue = false; $aHierarchicalKeys = array(); + $aDBChanges = array(); foreach($aChanges as $sAttCode => $valuecurr) { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true; - if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]); + if ($oAttDef->IsDirectField()) + { + $aDBChanges[$sAttCode] = $aChanges[$sAttCode]; + } if ($oAttDef->IsHierarchicalKey()) { $aHierarchicalKeys[$sAttCode] = $oAttDef; @@ -1865,7 +1871,7 @@ abstract class DBObject implements iDisplay $iDelta =$iMyRight - $iMyLeft + 1; MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable); - if ($aChanges[$sAttCode] == 0) + if ($aDBChanges[$sAttCode] == 0) { // No new parent, insert completely at the right of the tree $sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`"; @@ -1882,33 +1888,36 @@ abstract class DBObject implements iDisplay else { // Insert at the right of the specified parent - $sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aChanges[$sAttCode]); + $sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aDBChanges[$sAttCode]); $iNewLeft = CMDBSource::QueryToScalar($sSQL); } MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable); $aHKChanges = array(); - $aHKChanges[$sAttCode] = $aChanges[$sAttCode]; + $aHKChanges[$sAttCode] = $aDBChanges[$sAttCode]; $aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft; $aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1; - $aChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below + $aDBChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below } // Update scalar attributes - if (count($aChanges) != 0) + if (count($aDBChanges) != 0) { $oFilter = new DBObjectSearch(get_class($this)); $oFilter->AddCondition('id', $this->m_iKey, '='); - $sSQL = $oFilter->MakeUpdateQuery($aChanges); + $sSQL = $oFilter->MakeUpdateQuery($aDBChanges); CMDBSource::Query($sSQL); } } $this->DBWriteLinks(); $this->WriteExternalAttributes(); + $this->m_bDirty = false; + $this->m_aTouchedAtt = array(); + $this->m_aModifiedAtt = array(); $this->AfterUpdate(); @@ -1928,7 +1937,6 @@ abstract class DBObject implements iDisplay $this->m_aOrigValues[$sAttCode] = is_object($value) ? clone $value : $value; } } - } if (count($aChanges) != 0) diff --git a/core/ormcustomfieldsvalue.class.inc.php b/core/ormcustomfieldsvalue.class.inc.php index 42a1d686f..ad81da24d 100644 --- a/core/ormcustomfieldsvalue.class.inc.php +++ b/core/ormcustomfieldsvalue.class.inc.php @@ -59,21 +59,21 @@ class ormCustomFieldsValue public function GetAsHTML($bLocalize = true) { $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode); - $oHandler = $oAttDef->GetHandler($this->oHostObject, $this->GetValues()); + $oHandler = $oAttDef->GetHandler($this->GetValues()); return $oHandler->GetAsHTML($this->aCurrentValues, $bLocalize); } public function GetAsXML($bLocalize = true) { $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode); - $oHandler = $oAttDef->GetHandler($this->oHostObject, $this->GetValues()); + $oHandler = $oAttDef->GetHandler($this->GetValues()); return $oHandler->GetAsXML($this->aCurrentValues, $bLocalize); } public function GetAsCSV($sSeparator = ',', $sTextQualifier = '"', $bLocalize = true) { $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode); - $oHandler = $oAttDef->GetHandler($this->oHostObject, $this->GetValues()); + $oHandler = $oAttDef->GetHandler($this->GetValues()); return $oHandler->GetAsCSV($this->aCurrentValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true); } @@ -86,7 +86,7 @@ class ormCustomFieldsValue public function GetForTemplate($sVerb, $bLocalize = true) { $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode); - $oHandler = $oAttDef->GetHandler($this->oHostObject, $this->GetValues()); + $oHandler = $oAttDef->GetHandler($this->GetValues()); return 'template...verb='.$sVerb.' sur "'.json_encode($this->aCurrentValues).'"'; } @@ -97,7 +97,7 @@ class ormCustomFieldsValue public function Equals(ormCustomFieldsValue $oReference) { $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode); - $oHandler = $oAttDef->GetHandler($this->oHostObject, $this->GetValues()); + $oHandler = $oAttDef->GetHandler($this->GetValues()); return $oHandler->CompareValues($this->aCurrentValues, $oReference->aCurrentValues); } }