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("

".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

\n"); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); + $oObj->DBUpdate(); utils::RemoveTransaction($sTransactionId); $oP->p(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); @@ -1273,7 +1245,7 @@ EOF $sNextAction = utils::ReadPostedParam('next_action', ''); if (!empty($sNextAction)) { - ApplyNextAction($oP, $oObj, $sNextAction, $oMyChange); + ApplyNextAction($oP, $oObj, $sNextAction); } else { @@ -1386,12 +1358,7 @@ EOF list($bRes, $aIssues) = $oObj->CheckToWrite(); if ($bRes) { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBInsertTracked($oMyChange); + $oObj->DBInsert(); utils::RemoveTransaction($sTransactionId); $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); $oP->add("

".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

\n"); @@ -1400,7 +1367,7 @@ EOF $sNextAction = utils::ReadPostedParam('next_action', ''); if (!empty($sNextAction)) { - ApplyNextAction($oP, $oObj, $sNextAction, $oMyChange); + ApplyNextAction($oP, $oObj, $sNextAction); } else { @@ -1440,12 +1407,7 @@ EOF { $sClass = get_class($oObj); $sClassLabel = MetaModel::GetName($sClass); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBInsertTracked($oMyChange); + $oObj->DBInsert(); $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); $oP->add("

".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

\n"); $oObj->DisplayDetails($oP); @@ -1683,11 +1645,6 @@ EOF $oP->add(''); $oSet = DBObjectSet::FromArray($sClass, $aObjects); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); // For reporting $aHeaders = array( @@ -1743,7 +1700,7 @@ EOF $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); if ($bResult) { - $oObj->DBUpdateTracked($oMyChange); + $oObj->DBUpdate(); } else { @@ -1982,12 +1939,7 @@ EOF { if ($oObj->ApplyStimulus($sStimulus)) { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); + $oObj->DBUpdate(); $oP->p(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); } else diff --git a/pages/csvimport.php b/pages/csvimport.php index 3264fb168..441bff18c 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -298,15 +298,10 @@ try if (!$bSimulate) { // We're doing it for real, let's create a change - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $sUserString .= ' (CSV)'; - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); + $sUserString = CMDBChange::GetCurrentUserName().' (CSV)'; + CMDBObject::SetTrackInfo($sUserString); - // Todo - simplify that when reworking the change tracking - CMDBObject::SetCurrentChange($oMyChange); + $oMyChange = CMDBObject::GetCurrentChange(); } $oBulk = new BulkChange( diff --git a/pages/preferences.php b/pages/preferences.php index bd96c1d67..56ea5971d 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -232,12 +232,7 @@ try $sLangCode = utils::ReadParam('language', 'EN US'); $oUser = UserRights::GetUserObject(); $oUser->Set('language', $sLangCode); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oUser->DBUpdateTracked($oMyChange); + $oUser->DBUpdate(); // Redirect to force a reload/display of the page with the new language $oAppContext = new ApplicationContext(); $sURL = utils::GetAbsoluteUrlAppRoot().'pages/preferences.php?'.$oAppContext->GetForLink(); diff --git a/pages/schema.php b/pages/schema.php index ee717836e..50d4dd55e 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -378,12 +378,19 @@ function DisplayClassDetails($oPage, $sClass, $sContext) $oPage->AddTabContainer('details'); $oPage->SetCurrentTabContainer('details'); // List the attributes of the object + $aForwardChangeTracking = MetaModel::GetTrackForwardExternalKeys($sClass); $aDetails = array(); foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) { if ($oAttDef->IsExternalKey()) { $sValue = Dict::Format('UI:Schema:ExternalKey_To',MakeClassHLink($oAttDef->GetTargetClass(), $sContext)); + if (array_key_exists($sAttCode, $aForwardChangeTracking)) + { + $oLinkSet = $aForwardChangeTracking[$sAttCode]; + $sRemoteClass = $oLinkSet->GetHostClass(); + $sValue = $sValue."*"; + } } elseif ($oAttDef->IsLinkSet()) { diff --git a/portal/index.php b/portal/index.php index 2409ccb84..f03c5a9f3 100644 --- a/portal/index.php +++ b/portal/index.php @@ -356,12 +356,7 @@ function DoCreateRequest($oP, $oUserOrg) list($bRes, $aIssues) = $oRequest->CheckToWrite(); if ($bRes) { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oRequest->DBInsertTracked($oMyChange); + $oRequest->DBInsert(); $oP->add("

".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName(get_class($oRequest)))."

\n"); //DisplayObject($oP, $oRequest, $oUserOrg); diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index fb0c518f2..3ea53ac7c 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -303,13 +303,6 @@ try $aPredefinedObjects = call_user_func(array($sClass, 'GetPredefinedObjects')); if ($aPredefinedObjects != null) { - // Temporary... until this get really encapsulated as the default and transparent behavior - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - // Create/Delete/Update objects of this class, // according to the given constant values // @@ -324,12 +317,12 @@ try { $oObj->Set($sAttCode, $value); } - $oObj->DBUpdateTracked($oMyChange); + $oObj->DBUpdate(); $aDBIds[$oObj->GetKey()] = true; } else { - $oObj->DBDeleteTracked($oMyChange); + $oObj->DBDelete(); } } foreach ($aPredefinedObjects as $iRefId => $aObjValues) @@ -342,7 +335,7 @@ try { $oNewObj->Set($sAttCode, $value); } - $oNewObj->DBInsertTracked($oMyChange); + $oNewObj->DBInsert(); } } } diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index 4b430eb93..ccb7a7d32 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -516,13 +516,6 @@ class ApplicationInstaller if ($aPredefinedObjects != null) { SetupPage::log_info("$sClass::GetPredefinedObjects() returned ".count($aPredefinedObjects)." elements."); - - // Temporary... until this get really encapsulated as the default and transparent behavior - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); // Create/Delete/Update objects of this class, // according to the given constant values @@ -538,12 +531,12 @@ class ApplicationInstaller { $oObj->Set($sAttCode, $value); } - $oObj->DBUpdateTracked($oMyChange); + $oObj->DBUpdate(); $aDBIds[$oObj->GetKey()] = true; } else { - $oObj->DBDeleteTracked($oMyChange); + $oObj->DBDelete(); } } foreach ($aPredefinedObjects as $iRefId => $aObjValues) @@ -556,7 +549,7 @@ class ApplicationInstaller { $oNewObj->Set($sAttCode, $value); } - $oNewObj->DBInsertTracked($oMyChange); + $oNewObj->DBInsert(); } } } @@ -622,12 +615,12 @@ class ApplicationInstaller $oDataLoader = new XMLDataLoader(); - $oChange = MetaModel::NewObject("CMDBChange"); - $oChange->Set("date", time()); - $oChange->Set("userinfo", "Initialization"); - $iChangeId = $oChange->DBInsert(); + + CMDBObject::SetTrackInfo("Initialization"); + $oMyChange = CMDBObject::GetCurrentChange(); + SetupPage::log_info("starting data load session"); - $oDataLoader->StartSession($oChange); + $oDataLoader->StartSession($oMyChange); $aFiles = array(); $oProductionEnv = new RunTimeEnvironment(); diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 87f4af56d..2f9a88a35 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -288,6 +288,27 @@ EOF; return $sRes; } + /** + * Helper to format the tracking level for linkset (direct or indirect attributes) + * @param string $sTrackingLevel Value set from within the XML + * Returns string PHP flag + */ + protected function TrackingLevelToPHP($sTrackingLevel) + { + static $aXmlToPHP = array( + 'none' => 'LINKSET_TRACKING_NONE', + 'list' => 'LINKSET_TRACKING_LIST', + 'details' => 'LINKSET_TRACKING_DETAILS', + 'all' => 'LINKSET_TRACKING_ALL', + ); + + if (!array_key_exists($sTrackingLevel, $aXmlToPHP)) + { + throw new exception("Tracking level: unknown value '$sTrackingLevel'"); + } + return $aXmlToPHP[$sTrackingLevel]; + } + /** * Format a path (file or url) as an absolute path or relative to the module or the app @@ -542,21 +563,29 @@ EOF; $aParameters['linked_class'] = $this->GetPropString($oField, 'linked_class', ''); $aParameters['ext_key_to_me'] = $this->GetPropString($oField, 'ext_key_to_me', ''); $aParameters['ext_key_to_remote'] = $this->GetPropString($oField, 'ext_key_to_remote', ''); - // todo - utile ? $aParameters['allowed_values'] = 'null'; $aParameters['count_min'] = $this->GetPropNumber($oField, 'count_min', 0); $aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0); $aParameters['duplicates'] = $this->GetPropBoolean($oField, 'duplicates', false); + $sTrackingLevel = $oField->GetChildText('tracking_level'); + if (!is_null($sTrackingLevel)) + { + $aParameters['tracking_level'] = $this->TrackingLevelToPHP($sTrackingLevel); + } $aParameters['depends_on'] = $sDependencies; } elseif ($sAttType == 'AttributeLinkedSet') { $aParameters['linked_class'] = $this->GetPropString($oField, 'linked_class', ''); $aParameters['ext_key_to_me'] = $this->GetPropString($oField, 'ext_key_to_me', ''); - // todo - utile ? $aParameters['allowed_values'] = 'null'; $aParameters['count_min'] = $this->GetPropNumber($oField, 'count_min', 0); $aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0); + $sTrackingLevel = $oField->GetChildText('tracking_level'); + if (!is_null($sTrackingLevel)) + { + $aParameters['tracking_level'] = $this->TrackingLevelToPHP($sTrackingLevel); + } $aParameters['depends_on'] = $sDependencies; } elseif ($sAttType == 'AttributeExternalKey') diff --git a/webservices/import.php b/webservices/import.php index 0c1aec625..9760d8e82 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -636,19 +636,17 @@ try } else { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); if (strlen($sComment) > 0) { - $sMoreInfo = 'Web Service (CSV) - '.$sComment; + $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; } else { - $sMoreInfo = 'Web Service (CSV)'; + $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV)'; } - $oMyChange->Set("userinfo", $sUserString.', '.$sMoreInfo); - $iChangeId = $oMyChange->DBInsert(); + CMDBChange::SetTrackInfo($sMoreInfo); + + $oMyChange = CMDBObject::GetCurrentChange(); } $aRes = $oBulk->Process($oMyChange);