diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php
index 3ba09e7a0..6574cac74 100644
--- a/addons/userrights/userrightsprofile.class.inc.php
+++ b/addons/userrights/userrightsprofile.class.inc.php
@@ -311,11 +311,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Installation: create the very first user
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{
- // Create a change to record the history of the User object
- $oChange = MetaModel::NewObject("CMDBChange");
- $oChange->Set("date", time());
- $oChange->Set("userinfo", "Initialization");
- $iChangeId = $oChange->DBInsert();
+ CMDBObject::SetTrackInfo('Initialization');
+
+ $oChange = CMDBObject::GetCurrentChange();
$iContactId = 0;
// Support drastic data model changes: no organization class (or not writable)!
diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 2a58b16ec..ec4a04412 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -2487,7 +2487,7 @@ EOF
// Invoke extensions after insertion (the object must exist, have an id, etc.)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
- $oExtensionInstance->OnDBInsert($this, self::$m_oCurrChange);
+ $oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
}
return $res;
@@ -2500,7 +2500,7 @@ EOF
// Invoke extensions after insertion (the object must exist, have an id, etc.)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
- $oExtensionInstance->OnDBInsert($oNewObj, self::$m_oCurrChange);
+ $oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
}
return $oNewObj;
}
@@ -2512,7 +2512,7 @@ EOF
// Invoke extensions after the update (could be before)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
- $oExtensionInstance->OnDBUpdate($this, self::$m_oCurrChange);
+ $oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
}
return $res;
}
@@ -2528,18 +2528,12 @@ EOF
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
- $oExtensionInstance->OnDBDelete($this, self::$m_oCurrChange);
+ $oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
}
return parent::DBDeleteTracked_Internal($oDeletionPlan);
}
- protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
- {
- // Todo - invoke the extension
- return parent::BulkDeleteTracked_Internal($oFilter);
- }
-
public function IsModified()
{
if (parent::IsModified())
diff --git a/application/portalwebpage.class.inc.php b/application/portalwebpage.class.inc.php
index 6027b23c4..91b2d3a0f 100644
--- a/application/portalwebpage.class.inc.php
+++ b/application/portalwebpage.class.inc.php
@@ -658,12 +658,7 @@ EOF
// Record the change
//
- $oMyChange = MetaModel::NewObject("CMDBChange");
- $oMyChange->Set("date", time());
- $sUserString = CMDBChange::GetCurrentUserName();
- $oMyChange->Set("userinfo", $sUserString);
- $iChangeId = $oMyChange->DBInsert();
- $oObj->DBUpdateTracked($oMyChange);
+ $oObj->DBUpdate();
// Trigger ?
//
diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php
index ec538ca83..d5704461f 100644
--- a/application/ui.extkeywidget.class.inc.php
+++ b/application/ui.extkeywidget.class.inc.php
@@ -485,12 +485,7 @@ EOF
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0)
{
- $oMyChange = MetaModel::NewObject("CMDBChange");
- $oMyChange->Set("date", time());
- $sUserString = CMDBChange::GetCurrentUserName();
- $oMyChange->Set("userinfo", $sUserString);
- $iChangeId = $oMyChange->DBInsert();
- $oObj->DBInsertTracked($oMyChange);
+ $oObj->DBInsert();
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
}
else
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 03011643c..fecac2f81 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -75,6 +75,18 @@ define('DEL_SILENT', 2);
define('DEL_MOVEUP', 3);
+/**
+ * For Link sets: tracking_level
+ *
+ * @package iTopORM
+ */
+define('LINKSET_TRACKING_NONE', 0); // Do not track changes in the link set
+define('LINKSET_TRACKING_LIST', 1); // Do track added/removed items
+define('LINKSET_TRACKING_DETAILS', 2); // Do track modified items
+define('LINKSET_TRACKING_ALL', 3); // Do track added/removed/modified items
+
+
+
/**
* Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
*
@@ -567,6 +579,11 @@ class AttributeLinkedSet extends AttributeDefinition
}
}
+ public function GetTrackingLevel()
+ {
+ return $this->GetOptional('tracking_level', LINKSET_TRACKING_LIST);
+ }
+
public function GetLinkedClass() {return $this->Get('linked_class');}
public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');}
@@ -855,6 +872,11 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); }
public function GetEditClass() {return "LinkedSet";}
public function DuplicatesAllowed() {return $this->GetOptional("duplicates", false);} // The same object may be linked several times... or not...
+
+ public function GetTrackingLevel()
+ {
+ return $this->GetOptional('tracking_level', LINKSET_TRACKING_ALL);
+ }
}
/**
diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php
index aa2117699..8c0820838 100644
--- a/core/cmdbchangeop.class.inc.php
+++ b/core/cmdbchangeop.class.inc.php
@@ -64,6 +64,19 @@ class CMDBChangeOp extends DBObject
{
return '';
}
+
+ /**
+ * Safety net: in case the change is not given, let's guarantee that it will
+ * be set to the current ongoing change (or create a new one)
+ */
+ protected function OnInsert()
+ {
+ if ($this->Get('change') <= 0)
+ {
+ $this->Set('change', CMDBObject::GetCurrentChange());
+ }
+ parent::OnInsert();
+ }
}
@@ -124,6 +137,11 @@ class CMDBChangeOpDelete extends CMDBChangeOp
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
+
+ // Final class of the object (objclass must be set to the root class for efficiency purposes)
+ MetaModel::Init_AddAttribute(new AttributeString("fclass", array("allowed_values"=>null, "sql"=>"fclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+ // Last friendly name of the object
+ MetaModel::Init_AddAttribute(new AttributeString("fname", array("allowed_values"=>null, "sql"=>"fname", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
* Describe (as a text string) the modifications corresponding to this change
@@ -535,4 +553,172 @@ class CMDBChangeOpPlugin extends CMDBChangeOp
return $this->Get('description');
}
}
+
+/**
+ * Record added/removed objects from within a link set
+ *
+ * @package iTopORM
+ */
+abstract class CMDBChangeOpSetAttributeLinks 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_links",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ );
+ MetaModel::Init_Params($aParams);
+ MetaModel::Init_InheritAttributes();
+
+ // Note: item class/id points to the link class itself in case of a direct link set (e.g. Server::interface_list => Interface)
+ // item class/id points to the remote class in case of a indirect link set (e.g. Server::contract_list => Contract)
+ MetaModel::Init_AddAttribute(new AttributeString("item_class", array("allowed_values"=>null, "sql"=>"item_class", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeInteger("item_id", array("allowed_values"=>null, "sql"=>"item_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
+ }
+}
+
+/**
+ * Record added/removed objects from within a link set
+ *
+ * @package iTopORM
+ */
+class CMDBChangeOpSetAttributeLinksAddRemove extends CMDBChangeOpSetAttributeLinks
+{
+ public static function Init()
+ {
+ $aParams = array
+ (
+ "category" => "core/cmdb",
+ "key_type" => "",
+ "name_attcode" => "change",
+ "state_attcode" => "",
+ "reconc_keys" => array(),
+ "db_table" => "priv_changeop_links_addremove",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ );
+ MetaModel::Init_Params($aParams);
+ MetaModel::Init_InheritAttributes();
+
+ MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('added,removed'), "sql"=>"type", "default_value"=>"added", "is_null_allowed"=>false, "depends_on"=>array())));
+ }
+
+ /**
+ * Describe (as a text string) the modifications corresponding to this change
+ */
+ public function GetDescription()
+ {
+ $sResult = '';
+ $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)
+ {
+ if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
+
+ $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
+ $sAttName = $oAttDef->GetLabel();
+
+ $sItemDesc = MetaModel::GetHyperLink($this->Get('item_class'), $this->Get('item_id'));
+
+ $sResult = $sAttName.' - ';
+ switch ($this->Get('type'))
+ {
+ case 'added':
+ $sResult .= Dict::Format('Change:LinkSet:Added', $sItemDesc);
+ break;
+
+ case 'removed':
+ $sResult .= Dict::Format('Change:LinkSet:Removed', $sItemDesc);
+ break;
+ }
+ }
+ return $sResult;
+ }
+}
+
+/**
+ * Record attribute changes from within a link set
+ * A single record redirects to the modifications made within the same change
+ *
+ * @package iTopORM
+ */
+class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
+{
+ public static function Init()
+ {
+ $aParams = array
+ (
+ "category" => "core/cmdb",
+ "key_type" => "",
+ "name_attcode" => "change",
+ "state_attcode" => "",
+ "reconc_keys" => array(),
+ "db_table" => "priv_changeop_links_tune",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ );
+ MetaModel::Init_Params($aParams);
+ MetaModel::Init_InheritAttributes();
+
+ MetaModel::Init_AddAttribute(new AttributeInteger("link_id", array("allowed_values"=>null, "sql"=>"link_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
+ }
+
+ /**
+ * Describe (as a text string) the modifications corresponding to this change
+ */
+ public function GetDescription()
+ {
+ $sResult = '';
+ $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)
+ {
+ if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
+
+ $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
+ $sAttName = $oAttDef->GetLabel();
+
+ $sLinkClass = $oAttDef->GetLinkedClass();
+
+ // Search for changes on the corresponding link
+ //
+ $oSearch = new DBObjectSearch('CMDBChangeOpSetAttribute');
+ $oSearch->AddCondition('change', $this->Get('change'), '=');
+ $oSearch->AddCondition('objclass', $sLinkClass, '=');
+ $oSearch->AddCondition('objkey', $this->Get('link_id'), '=');
+ $oSet = new DBObjectSet($oSearch);
+ $aChanges = array();
+ while ($oChangeOp = $oSet->Fetch())
+ {
+ $aChanges[] = $oChangeOp->GetDescription();
+ }
+ if (count($aChanges) == 0)
+ {
+ return '';
+ }
+
+ $sItemDesc = MetaModel::GetHyperLink($this->Get('item_class'), $this->Get('item_id'));
+
+ $sResult = $sAttName.' - ';
+ $sResult .= Dict::Format('Change:LinkSet:Modified', $sItemDesc);
+ $sResult .= ' : '.implode(', ', $aChanges);
+ }
+ return $sResult;
+ }
+}
?>
diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
index 1994d904f..8b6308351 100644
--- a/core/cmdbobject.class.inc.php
+++ b/core/cmdbobject.class.inc.php
@@ -91,8 +91,11 @@ abstract class CMDBObject extends DBObject
protected $m_datUpdated;
// Note: this value is static, but that could be changed because it is sometimes a real issue (see update of interfaces / connected_to
protected static $m_oCurrChange = null;
+ protected static $m_sInfo = null; // null => the information is built in a standard way
-
+ /**
+ * Specify another change (this is mainly for backward compatibility)
+ */
public static function SetCurrentChange(CMDBChange $oChange)
{
self::$m_oCurrChange = $oChange;
@@ -100,34 +103,86 @@ abstract class CMDBObject extends DBObject
//
// Todo: simplify the APIs and do not pass the current change as an argument anymore
- // SetCurrentChange to be invoked in very few cases (UI.php, CSV import, Data synchro)
+ // SetTrackInfo to be invoked in very few cases (UI.php, CSV import, Data synchro)
+ // SetCurrentChange is an alternative to SetTrackInfo (csv ?)
// GetCurrentChange to be called ONCE (!) by CMDBChangeOp::OnInsert ($this->Set('change', ..GetCurrentChange())
// GetCurrentChange to create a default change if not already done in the current context
//
- public static function GetCurrentChange()
+ /**
+ * Get a change record (create it if not existing)
+ */
+ public static function GetCurrentChange($bAutoCreate = true)
{
+ if ($bAutoCreate && is_null(self::$m_oCurrChange))
+ {
+ self::CreateChange();
+ }
return self::$m_oCurrChange;
}
-
- private function RecordObjCreation(CMDBChange $oChange)
+ /**
+ * Override the additional information (defaulting to user name)
+ * A call to this verb should replace every occurence of
+ * $oMyChange = MetaModel::NewObject("CMDBChange");
+ * $oMyChange->Set("date", time());
+ * $oMyChange->Set("userinfo", 'this is done by ... for ...');
+ * $iChangeId = $oMyChange->DBInsert();
+ */
+ public static function SetTrackInfo($sInfo)
{
+ self::$m_sInfo = $sInfo;
+ }
+
+ /**
+ * Get the additional information (defaulting to user name)
+ */
+ protected static function GetTrackInfo()
+ {
+ if (is_null(self::$m_sInfo))
+ {
+ return CMDBChange::GetCurrentUserName();
+ }
+ else
+ {
+ return self::$m_sInfo;
+ }
+ }
+
+ /**
+ * Create a standard change record (done here 99% of the time, and nearly once per page)
+ */
+ protected static function CreateChange()
+ {
+ self::$m_oCurrChange = MetaModel::NewObject("CMDBChange");
+ self::$m_oCurrChange->Set("date", time());
+ self::$m_oCurrChange->Set("userinfo", self::GetTrackInfo());
+ self::$m_oCurrChange->DBInsert();
+ }
+
+ protected function RecordObjCreation()
+ {
+ parent::RecordObjCreation();
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$iId = $oMyChangeOp->DBInsertNoReload();
}
- private function RecordObjDeletion(CMDBChange $oChange, $objkey)
+
+ protected function RecordObjDeletion($objkey)
{
+ parent::RecordObjDeletion($objkey);
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpDelete");
- $oMyChangeOp->Set("change", $oChange->GetKey());
- $oMyChangeOp->Set("objclass", get_class($this));
+ $oMyChangeOp->Set("objclass", MetaModel::GetRootClass(get_class($this)));
$oMyChangeOp->Set("objkey", $objkey);
+ $oMyChangeOp->Set("fclass", get_class($this));
+ $oMyChangeOp->Set("fname", $this->GetRawName());
$iId = $oMyChangeOp->DBInsertNoReload();
}
- private function RecordAttChanges(CMDBChange $oChange, array $aValues, array $aOrigValues)
+
+ protected function RecordAttChanges(array $aValues, array $aOrigValues)
{
+ parent::RecordAttChanges($aValues, $aOrigValues);
+
// $aValues is an array of $sAttCode => $value
//
foreach ($aValues as $sAttCode=> $value)
@@ -149,7 +204,6 @@ abstract class CMDBObject extends DBObject
{
// One Way encrypted passwords' history is stored -one way- encrypted
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -165,7 +219,6 @@ abstract class CMDBObject extends DBObject
{
// Encrypted string history is stored encrypted
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -181,7 +234,6 @@ abstract class CMDBObject extends DBObject
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -209,7 +261,6 @@ abstract class CMDBObject extends DBObject
if ($item_value != $item_original)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
@@ -223,7 +274,6 @@ abstract class CMDBObject extends DBObject
elseif ($oAttDef instanceOf AttributeCaseLog)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -235,7 +285,6 @@ abstract class CMDBObject extends DBObject
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -252,7 +301,6 @@ abstract class CMDBObject extends DBObject
// Scalars
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
- $oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -294,32 +342,24 @@ abstract class CMDBObject extends DBObject
public function DBInsert()
{
- if(!is_object(self::$m_oCurrChange))
- {
- throw new CoreException("DBInsert() could not be used here, please use DBInsertTracked() instead");
- }
return $this->DBInsertTracked_Internal();
}
public function DBInsertTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
+ self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
$ret = $this->DBInsertTracked_Internal();
- self::$m_oCurrChange = $oPreviousChange;
return $ret;
}
public function DBInsertTrackedNoReload(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
+ self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
$ret = $this->DBInsertTracked_Internal(true);
- self::$m_oCurrChange = $oPreviousChange;
return $ret;
}
@@ -333,25 +373,18 @@ abstract class CMDBObject extends DBObject
{
$ret = parent::DBInsert();
}
- $this->RecordObjCreation(self::$m_oCurrChange);
return $ret;
}
public function DBClone($newKey = null)
{
- if(!self::$m_oCurrChange)
- {
- throw new CoreException("DBClone() could not be used here, please use DBCloneTracked() instead");
- }
return $this->DBCloneTracked_Internal();
}
public function DBCloneTracked(CMDBChange $oChange, $newKey = null)
{
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
+ self::SetCurrentChange($oChange);
$this->DBCloneTracked_Internal($newKey);
- self::$m_oCurrChange = $oPreviousChange;
}
protected function DBCloneTracked_Internal($newKey = null)
@@ -359,128 +392,57 @@ abstract class CMDBObject extends DBObject
$newKey = parent::DBClone($newKey);
$oClone = MetaModel::GetObject(get_class($this), $newKey);
- $oClone->RecordObjCreation(self::$m_oCurrChange);
return $newKey;
}
public function DBUpdate()
- {
- if(!self::$m_oCurrChange)
- {
- throw new CoreException("DBUpdate() could not be used here, please use DBUpdateTracked() instead");
- }
- return $this->DBUpdateTracked_internal();
- }
-
- public function DBUpdateTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
- {
- $this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
-
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
- $this->DBUpdateTracked_Internal();
- self::$m_oCurrChange = $oPreviousChange;
- }
-
- protected function DBUpdateTracked_Internal()
{
// Copy the changes list before the update (the list should be reset afterwards)
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
{
- //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, $aOriginalValues);
return $ret;
}
+ public function DBUpdateTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
+ {
+ self::SetCurrentChange($oChange);
+ $this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
+ $this->DBUpdate();
+ }
+
public function DBDelete(&$oDeletionPlan = null)
{
- if(!self::$m_oCurrChange)
- {
- throw new CoreException("DBDelete() could not be used here, please use DBDeleteTracked() instead");
- }
return $this->DBDeleteTracked_Internal($oDeletionPlan);
}
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
+ self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_DELETE);
-
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
$this->DBDeleteTracked_Internal($oDeletionPlan);
- self::$m_oCurrChange = $oPreviousChange;
}
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{
$prevkey = $this->GetKey();
$ret = parent::DBDelete($oDeletionPlan);
- $this->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
- return $ret;
- }
-
- public static function BulkDelete(DBObjectSearch $oFilter)
- {
- if(!self::$m_oCurrChange)
- {
- throw new CoreException("BulkDelete() could not be used here, please use BulkDeleteTracked() instead");
- }
- return $this->BulkDeleteTracked_Internal($oFilter);
- }
-
- public static function BulkDeleteTracked(CMDBChange $oChange, DBObjectSearch $oFilter)
- {
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
- $this->BulkDeleteTracked_Internal($oFilter);
- self::$m_oCurrChange = $oPreviousChange;
- }
-
- protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
- {
- throw new CoreWarning("Change tracking not tested for bulk operations");
-
- // Get the list of objects to delete (and record data before deleting the DB records)
- $oObjSet = new CMDBObjectSet($oFilter);
- $aObjAndKeys = array(); // array of id=>object
- while ($oItem = $oObjSet->Fetch())
- {
- $aObjAndKeys[$oItem->GetKey()] = $oItem;
- }
- $oObjSet->FreeResult();
-
- // Delete in one single efficient query
- $ret = parent::BulkDelete($oFilter);
- // Record... in many queries !!!
- foreach($aObjAndKeys as $prevkey=>$oItem)
- {
- $oItem->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
- }
return $ret;
}
public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
{
- if(!self::$m_oCurrChange)
- {
- throw new CoreException("BulkUpdate() could not be used here, please use BulkUpdateTracked() instead");
- }
return $this->BulkUpdateTracked_Internal($oFilter, $aValues);
}
public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues)
{
- $oPreviousChange = self::$m_oCurrChange;
- self::$m_oCurrChange = $oChange;
+ self::SetCurrentChange($oChange);
$this->BulkUpdateTracked_Internal($oFilter, $aValues);
- self::$m_oCurrChange = $oPreviousChange;
}
protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)
@@ -507,7 +469,7 @@ abstract class CMDBObject extends DBObject
while ($oItem = $oObjSet->Fetch())
{
$aChangedValues = $oItem->ListChangedValues($aValues);
- $oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues, $aOriginalValues[$oItem->GetKey()]);
+ $oItem->RecordAttChanges($aChangedValues, $aOriginalValues[$oItem->GetKey()]);
}
return $ret;
}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index dc217bccc..c8c228ac3 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -1424,6 +1424,8 @@ abstract class DBObject
$oTrigger->DoActivate($this->ToArgs('this'));
}
+ $this->RecordObjCreation();
+
return $this->m_iKey;
}
@@ -1434,13 +1436,15 @@ abstract class DBObject
return $this->m_iKey;
}
- public function DBInsertTracked(CMDBChange $oVoid)
+ public function DBInsertTracked(CMDBChange $oChange)
{
+ CMDBObject::SetCurrentChange($oChange);
return $this->DBInsert();
}
- public function DBInsertTrackedNoReload(CMDBChange $oVoid)
+ public function DBInsertTrackedNoReload(CMDBChange $oChange)
{
+ CMDBObject::SetCurrentChange($oChange);
return $this->DBInsertNoReload();
}
@@ -1450,7 +1454,9 @@ abstract class DBObject
{
$this->m_bIsInDB = false;
$this->m_iKey = $iNewKey;
- return $this->DBInsert();
+ $ret = $this->DBInsert();
+ $this->RecordObjCreation();
+ return $ret;
}
/**
@@ -1498,7 +1504,7 @@ abstract class DBObject
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
{
- //throw new CoreWarning("Attempting to update an unchanged object");
+ // Attempting to update an unchanged object
return;
}
@@ -1510,6 +1516,9 @@ abstract class DBObject
throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
}
+ // Save the original values (will be reset to the new values when the object get written to the DB)
+ $aOriginalValues = $this->m_aOrigValues;
+
$bHasANewExternalKeyValue = false;
$aHierarchicalKeys = array();
foreach($aChanges as $sAttCode => $valuecurr)
@@ -1588,11 +1597,17 @@ abstract class DBObject
$this->Reload();
}
+ if (count($aChanges) != 0)
+ {
+ $this->RecordAttChanges($aChanges, $aOriginalValues);
+ }
+
return $this->m_iKey;
}
- public function DBUpdateTracked(CMDBChange $oVoid)
+ public function DBUpdateTracked(CMDBChange $oChange)
{
+ CMDBObject::SetCurrentChange($oChange);
return $this->DBUpdate();
}
@@ -1664,6 +1679,8 @@ abstract class DBObject
$this->AfterDelete();
+ $this->RecordObjDeletion($this->m_iKey);
+
$this->m_bIsInDB = false;
$this->m_iKey = null;
}
@@ -1719,8 +1736,9 @@ abstract class DBObject
return $oDeletionPlan;
}
- public function DBDeleteTracked(CMDBChange $oVoid, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
+ public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
+ CMDBObject::SetCurrentChange($oChange);
$this->DBDelete($oDeletionPlan);
}
@@ -1899,6 +1917,152 @@ abstract class DBObject
{
}
+
+ /**
+ * Common to the recording of link set changes (add/remove/modify)
+ */
+ private function PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, $sChangeOpClass, $aOriginalValues = null)
+ {
+ if ($iLinkSetOwnerId <= 0)
+ {
+ return null;
+ }
+
+ if (!is_subclass_of($oLinkSet->GetHostClass(), 'CMDBObject'))
+ {
+ // The link set owner class does not keep track of its history
+ return null;
+ }
+
+ // Determine the linked item class and id
+ //
+ if ($oLinkSet->IsIndirect())
+ {
+ // The "item" is on the other end (N-N links)
+ $sExtKeyToRemote = $oLinkSet->GetExtKeyToRemote();
+ $oExtKeyToRemote = MetaModel::GetAttributeDef(get_class($this), $sExtKeyToRemote);
+ $sItemClass = $oExtKeyToRemote->GetTargetClass();
+ if ($aOriginalValues)
+ {
+ // Get the value from the original values
+ $iItemId = $aOriginalValues[$sExtKeyToRemote];
+ }
+ else
+ {
+ $iItemId = $this->Get($sExtKeyToRemote);
+ }
+ }
+ else
+ {
+ // I am the "item" (1-N links)
+ $sItemClass = get_class($this);
+ $iItemId = $this->GetKey();
+ }
+
+ // Get the remote object, to determine its exact class
+ // Possible optimization: implement a tool in MetaModel, to get the final class of an object (not always querying + query reduced to a select on the root table!
+ $oOwner = MetaModel::GetObject($oLinkSet->GetHostClass(), $iLinkSetOwnerId, false);
+ if ($oOwner)
+ {
+ $sLinkSetOwnerClass = get_class($oOwner);
+
+ $oMyChangeOp = MetaModel::NewObject($sChangeOpClass);
+ $oMyChangeOp->Set("objclass", $sLinkSetOwnerClass);
+ $oMyChangeOp->Set("objkey", $iLinkSetOwnerId);
+ $oMyChangeOp->Set("attcode", $oLinkSet->GetCode());
+ $oMyChangeOp->Set("item_class", $sItemClass);
+ $oMyChangeOp->Set("item_id", $iItemId);
+ return $oMyChangeOp;
+ }
+ else
+ {
+ // Depending on the deletion order, it may happen that the id is already invalid... ignore
+ return null;
+ }
+ }
+
+ /**
+ * This object has been created/deleted, record that as a change in link sets pointing to this (if any)
+ */
+ private function RecordLinkSetListChange($bAdd = true)
+ {
+ $aForwardChangeTracking = MetaModel::GetTrackForwardExternalKeys(get_class($this));
+ foreach(MetaModel::GetTrackForwardExternalKeys(get_class($this)) as $sExtKeyAttCode => $oLinkSet)
+ {
+ if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_LIST) == 0) continue;
+
+ $iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
+ $oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksAddRemove');
+ if ($oMyChangeOp)
+ {
+ if ($bAdd)
+ {
+ $oMyChangeOp->Set("type", "added");
+ }
+ else
+ {
+ $oMyChangeOp->Set("type", "removed");
+ }
+ $iId = $oMyChangeOp->DBInsertNoReload();
+ }
+ }
+ }
+
+ protected function RecordObjCreation()
+ {
+ $this->RecordLinkSetListChange(true);
+ }
+
+ protected function RecordObjDeletion($objkey)
+ {
+ $this->RecordLinkSetListChange(false);
+ }
+
+ protected function RecordAttChanges(array $aValues, array $aOrigValues)
+ {
+ $aForwardChangeTracking = MetaModel::GetTrackForwardExternalKeys(get_class($this));
+ foreach(MetaModel::GetTrackForwardExternalKeys(get_class($this)) as $sExtKeyAttCode => $oLinkSet)
+ {
+
+ if (array_key_exists($sExtKeyAttCode, $aValues))
+ {
+ if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_LIST) == 0) continue;
+
+ // Keep track of link added/removed
+ //
+ $iLinkSetOwnerNext = $aValues[$sExtKeyAttCode];
+ $oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerNext, $oLinkSet, 'CMDBChangeOpSetAttributeLinksAddRemove');
+ if ($oMyChangeOp)
+ {
+ $oMyChangeOp->Set("type", "added");
+ $oMyChangeOp->DBInsertNoReload();
+ }
+
+ $iLinkSetOwnerPrevious = $aOrigValues[$sExtKeyAttCode];
+ $oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerPrevious, $oLinkSet, 'CMDBChangeOpSetAttributeLinksAddRemove', $aOrigValues);
+ if ($oMyChangeOp)
+ {
+ $oMyChangeOp->Set("type", "removed");
+ $oMyChangeOp->DBInsertNoReload();
+ }
+ }
+ else
+ {
+ // Keep track of link changes
+ //
+ if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_DETAILS) == 0) continue;
+
+ $iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
+ $oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksTune');
+ if ($oMyChangeOp)
+ {
+ $oMyChangeOp->Set("link_id", $this->GetKey());
+ $iId = $oMyChangeOp->DBInsertNoReload();
+ }
+ }
+ }
+ }
+
// Return an empty set for the parent of all
public static function GetRelationQueries($sRelCode)
{
@@ -2103,7 +2267,7 @@ abstract class DBObject
}
// to be continued...
}
+
}
-
?>
diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php
index abf4ae396..2a8c2891e 100644
--- a/core/dbobjectsearch.class.php
+++ b/core/dbobjectsearch.class.php
@@ -1126,7 +1126,12 @@ class DBObjectSearch
if ($bOQLCacheEnabled && array_key_exists($sQuery, self::$m_aOQLQueries))
{
// hit!
- return clone self::$m_aOQLQueries[$sQuery];
+ $oClone = clone self::$m_aOQLQueries[$sQuery];
+ if (!is_null($aParams))
+ {
+ $oClone->m_aParams = $aParams;
+ }
+ return $oClone;
}
$oOql = new OqlInterpreter($sQuery);
@@ -1288,4 +1293,4 @@ class DBObjectSearch
}
-?>
+?>
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index 0aeee31bf..bc7d7c285 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -879,6 +879,34 @@ abstract class MetaModel
}
}
+ protected static $m_aTrackForwardCache = array();
+ /**
+ * List external keys for which there is a LinkSet (direct or indirect) on the other end
+ * For those external keys, a change will have a special meaning on the other end
+ * in term of change tracking
+ */
+ final static public function GetTrackForwardExternalKeys($sClass)
+ {
+ if (!isset(self::$m_aTrackForwardCache[$sClass]))
+ {
+ $aRes = array();
+ foreach (MetaModel::GetExternalKeys($sClass) as $sAttCode => $oAttDef)
+ {
+ $sRemoteClass = $oAttDef->GetTargetClass();
+ foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef)
+ {
+ if (!$oRemoteAttDef->IsLinkSet()) continue;
+ if ($oRemoteAttDef->GetLinkedClass() != $sClass) continue;
+ if ($oRemoteAttDef->GetExtKeyToMe() != $sAttCode) continue;
+ $aRes[$sAttCode] = $oRemoteAttDef;
+ }
+ }
+ self::$m_aTrackForwardCache[$sClass] = $aRes;
+ }
+ return self::$m_aTrackForwardCache[$sClass];
+ }
+
+
public static function GetLabel($sClass, $sAttCode)
{
$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
@@ -4730,7 +4758,23 @@ abstract class MetaModel
$oObj = self::GetObject($sTargetClass, $iKey, false);
if (is_null($oObj))
{
- return "$sTargetClass: $iKey (not found)";
+ // Whatever we are looking for, the root class is the key to search for
+ $sRootClass = self::GetRootClass($sTargetClass);
+ $oSearch = DBObjectSearch::FromOQL('SELECT CMDBChangeOpDelete WHERE objclass = :objclass AND objkey = :objkey', array('objclass' => $sRootClass, 'objkey' => $iKey));
+ $oSet = new DBObjectSet($oSearch);
+ $oRecord = $oSet->Fetch();
+ // An empty fname is obtained with iTop < 2.0
+ if (is_null($oRecord) || (strlen(trim($oRecord->Get('fname'))) == 0))
+ {
+ $sName = Dict::Format('Core:UnknownObjectLabel', $sTargetClass, $iKey);
+ $sTitle = Dict::S('Core:UnknownObjectTip');
+ }
+ else
+ {
+ $sName = $oRecord->Get('fname');
+ $sTitle = Dict::Format('Core:DeletedObjectTip', $oRecord->Get('date'), $oRecord->Get('userinfo'));
+ }
+ return ''.htmlentities($sName, ENT_QUOTES, 'UTF-8').'';
}
return $oObj->GetHyperLink();
}
@@ -4751,6 +4795,8 @@ abstract class MetaModel
public static function BulkDelete(DBObjectSearch $oFilter)
{
+ throw new Exception("Bulk deletion cannot be done this way: it will not work with hierarchical keys - implementation to be reviewed!");
+
$sSQL = self::MakeDeleteQuery($oFilter);
if (!self::DBIsReadOnly())
{
diff --git a/core/ormstopwatch.class.inc.php b/core/ormstopwatch.class.inc.php
index 333641d36..bfad787ad 100644
--- a/core/ormstopwatch.class.inc.php
+++ b/core/ormstopwatch.class.inc.php
@@ -429,12 +429,9 @@ class CheckStopWatchThresholds implements iBackgroundProcess
if($oObj->IsModified())
{
- // Todo - factorize so that only one single change will be instantiated
- $oMyChange = new CMDBChange();
- $oMyChange->Set("date", time());
- $oMyChange->Set("userinfo", "Automatic - threshold triggered");
- $iChangeId = $oMyChange->DBInsertNoReload();
-
+ CMDBObject::SetTrackInfo("Automatic - threshold triggered");
+
+ $oMyChange = CMDBObject::GetCurrentChange();
$oObj->DBUpdateTracked($oMyChange, true /*skip security*/);
}
diff --git a/css/light-grey.css b/css/light-grey.css
index 982f2192c..ed539ae15 100644
--- a/css/light-grey.css
+++ b/css/light-grey.css
@@ -1368,4 +1368,7 @@ a.summary, a.summary:hover {
}
.sortable_field_list > li.selected {
background: #F6A828;
+}
+.itop-deleted-object {
+ text-decoration: line-through;
}
\ No newline at end of file
diff --git a/datamodel/itop-config-mgmt-1.0.0/datamodel.itop-config-mgmt.xml b/datamodel/itop-config-mgmt-1.0.0/datamodel.itop-config-mgmt.xml
index d3bc4c36c..ca6369dfe 100644
--- a/datamodel/itop-config-mgmt-1.0.0/datamodel.itop-config-mgmt.xml
+++ b/datamodel/itop-config-mgmt-1.0.0/datamodel.itop-config-mgmt.xml
@@ -2621,6 +2621,10 @@
m_aOrigValues['connected_if']; // The interface this interface was connected to
+ if ($iPrevTargetIf == $this->Get('connected_if'))
+ {
+ return;
+ }
if ($iPrevTargetIf != 0)
{
@@ -2628,11 +2632,11 @@
$oPrevConnectedIf = MetaModel::GetObject('NetworkInterface', $iPrevTargetIf, false);
if (!is_null($oPrevConnectedIf))
{
- $oPrevConnectedIf->Set('connected_if', 0);
- // Need to backup the current change, because it is reset when DBUpdateTracked is complete
- $oCurrChange = self::$m_oCurrChange;
- $oPrevConnectedIf->DBUpdateTracked($oCurrChange);
- self::$m_oCurrChange = $oCurrChange;
+ if ($oPrevConnectedIf->Get('connected_if') == $this->GetKey()) // protection against reentrance
+ {
+ $oPrevConnectedIf->Set('connected_if', 0);
+ $oPrevConnectedIf->DBUpdate();
+ }
}
}
@@ -2644,28 +2648,12 @@
if (($oConnIf->Get('connected_if') != $this->GetKey()) || ($sConnLink != $oConnIf->Get('link_type')))
{
- // Something has to be changed on the connected interface...
- if ($oConnIf->Get('connected_if') != $this->GetKey())
- {
- // It is connected to another interface: reset that third one...
- $oThirdIf = MetaModel::GetObject('NetworkInterface', $oConnIf->Get('connected_if'), false);
- if (!is_null($oThirdIf))
- {
- $oThirdIf->Set('connected_if', 0);
- // Need to backup the current change, because it is reset when DBUpdateTracked is complete
- $oCurrChange = self::$m_oCurrChange;
- $oThirdIf->DBUpdateTracked($oCurrChange);
- self::$m_oCurrChange = $oCurrChange;
- }
- }
// Connect the remote interface to the current one
$oConnIf->Set('connected_if', $this->GetKey());
$oConnIf->Set('link_type', $sConnLink);
// Need to backup the current change, because it is reset when DBUpdateTracked is complete
- $oCurrChange = self::$m_oCurrChange;
- $oConnIf->DBUpdateTracked($oCurrChange);
- self::$m_oCurrChange = $oCurrChange;
+ $oConnIf->DBUpdate();
}
}
}]]>
diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php
index d889dc927..27b0cccc4 100644
--- a/dictionaries/dictionary.itop.core.php
+++ b/dictionaries/dictionary.itop.core.php
@@ -24,6 +24,12 @@
*/
Dict::Add('EN US', 'English', 'English', array(
+ 'Core:DeletedObjectLabel' => '%1s (deleted)',
+ 'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)',
+
+ 'Core:UnknownObjectLabel' => 'Object not found (class: %1$s, id: %2$d)',
+ 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.',
+
'Core:AttributeLinkedSet' => 'Array of objects',
'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass',
@@ -240,6 +246,9 @@ Dict::Add('EN US', 'English', 'English', array(
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modified, previous value: %2$s',
'Change:AttName_Changed' => '%1$s modified',
'Change:AttName_EntryAdded' => '%1$s modified, new entry added.',
+ 'Change:LinkSet:Added' => 'added %1$s',
+ 'Change:LinkSet:Removed' => 'removed %1$s',
+ 'Change:LinkSet:Modified' => 'modified %1$s',
));
//
diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php
index c09d16c40..fcc796d2b 100644
--- a/dictionaries/fr.dictionary.itop.core.php
+++ b/dictionaries/fr.dictionary.itop.core.php
@@ -22,6 +22,10 @@
*/
Dict::Add('FR FR', 'French', 'Français', array(
+ 'Core:DeletedObjectTip' => 'L\'objet a été effacé le %1$s (%2$s)',
+ 'Core:UnknownObjectLabel' => 'Classe: %1$s, Identifiant: %2$d',
+ 'Core:UnknownObjectTip' => 'L\'objet n\'a pu être trouvé. Il se peut que les archives aient été purgées après son effacement.',
+
'Class:ActionEmail' => 'email notification',
'Class:ActionEmail+' => 'Action: Email notification',
'Class:ActionEmail/Attribute:test_recipient' => 'Destinataire de test',
@@ -503,6 +507,9 @@ Opérateurs :
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifié, ancienne valeur: %2$s',
'Change:AttName_Changed' => '%1$s modifié',
'Change:AttName_EntryAdded' => '%1$s champ modifié, une nouvelle entrée a été ajoutée',
+ 'Change:LinkSet:Added' => 'ajout de %1$s',
+ 'Change:LinkSet:Removed' => 'suppression de %1$s',
+ 'Change:LinkSet:Modified' => 'modification de %1$s',
'Class:Action' => 'Action',
'Class:Action+' => 'Action spécifique',
'Class:Action/Attribute:name' => 'Nom',
diff --git a/pages/UI.php b/pages/UI.php
index d1e2e2f42..c46f2750c 100644
--- a/pages/UI.php
+++ b/pages/UI.php
@@ -34,15 +34,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
{
if ($bDeleteConfirmed)
{
- // Prepare the change reporting
- //
- $oMyChange = MetaModel::NewObject("CMDBChange");
- $oMyChange->Set("date", time());
- $sUserString = CMDBChange::GetCurrentUserName();
- $oMyChange->Set("userinfo", $sUserString);
- $oMyChange->DBInsert();
-
- $oObj->DBDeleteTracked($oMyChange, null, $oDeletionPlan);
+ $oObj->DBDeleteTracked(CMDBObject::GetCurrentChange(), null, $oDeletionPlan);
}
else
{
@@ -363,9 +355,8 @@ EOF
* @param $oP WebPage The page for the output
* @param $oObj CMDBObject The object to process
* @param $sNextAction string The code of the stimulus for the 'action' (i.e. Transition) to apply
- * @param $oMyChange CMDBChange The change used to log the modifications or null is none is available (a new one will be created)
*/
-function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction, $oMyChange)
+function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction)
{
// Here handle the apply stimulus
$aTransitions = $oObj->EnumTransitions();
@@ -383,15 +374,7 @@ function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction, $oMyChange
// If all the mandatory fields are already present, just apply the transition silently...
if ($oObj->ApplyStimulus($sNextAction))
{
- if ($oMyChange == null)
- {
- $oMyChange = MetaModel::NewObject("CMDBChange");
- $oMyChange->Set("date", time());
- $sUserString = CMDBChange::GetCurrentUserName();
- $oMyChange->Set("userinfo", $sUserString);
- $iChangeId = $oMyChange->DBInsert();
- }
- $oObj->DBUpdateTracked($oMyChange);
+ $oObj->DBUpdate();
}
$oObj->Reload();
$oObj->DisplayDetails($oP);
@@ -1013,11 +996,6 @@ EOF
{
throw new Exception(Dict::S('UI:Error:ObjectAlreadyUpdated'));
}
- $oMyChange = MetaModel::NewObject("CMDBChange");
- $oMyChange->Set("date", time());
- $sUserString = CMDBChange::GetCurrentUserName();
- $oMyChange->Set("userinfo", $sUserString);
- $iChangeId = $oMyChange->DBInsert();
utils::RemoveTransaction($sTransactionId);
}
foreach($aSelectedObj as $iId)
@@ -1049,7 +1027,7 @@ EOF
);
if ($bResult && (!$bPreview))
{
- $oObj->DBUpdateTracked($oMyChange);
+ $oObj->DBUpdate();
}
}
$oP->Table($aHeaders, $aRows);
@@ -1224,7 +1202,6 @@ EOF
}
else
{
- $oMyChange = null;
$oObj->UpdateObjectFromPostedForm();
if (!$oObj->IsModified())
@@ -1240,12 +1217,7 @@ EOF
$oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding
$oP->add("