Browse Source

Deletion: entirely reviewed to take into account the objects being synchronized by an external data source, or the plugins constraints.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1194 a333f486-631f-4898-b8df-5754b55c2be0
romainq 14 years ago
parent
commit
84a737920e

+ 87 - 15
application/cmdbabstract.class.inc.php

@@ -102,15 +102,16 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		$oBlock->Display($oPage, -1);
 		$oBlock->Display($oPage, -1);
 	
 	
 		// Master data sources
 		// Master data sources
+		$sSynchroIcon = '';
 		$oReplicaSet = $this->GetMasterReplica();
 		$oReplicaSet = $this->GetMasterReplica();
 		$bSynchronized = false;
 		$bSynchronized = false;
-		$bCreated = false;
-		$bCanBeDeleted = false;
+		$oCreatorTask = null;
+		$bCanBeDeletedByTask = false;
+		$bCanBeDeletedByUser = true;
 		$aMasterSources = array();
 		$aMasterSources = array();
 		if ($oReplicaSet->Count() > 0)
 		if ($oReplicaSet->Count() > 0)
 		{
 		{
 			$bSynchronized = true;
 			$bSynchronized = true;
-			$sTip = "<p>".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."</p>";
 			while($aData = $oReplicaSet->FetchAssoc())
 			while($aData = $oReplicaSet->FetchAssoc())
 			{
 			{
 				// Assumption: $aData['datasource'] will not be null because the data source id is always set...
 				// Assumption: $aData['datasource'] will not be null because the data source id is always set...
@@ -122,27 +123,59 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 				}
 				}
 				if ($aData['replica']->Get('status_dest_creator') == 1)
 				if ($aData['replica']->Get('status_dest_creator') == 1)
 				{
 				{
-					$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sLink)."</p>";
-					$bCreated = true;
+					$oCreatorTask = $aData['datasource'];
+					$bCreatedByTask = true;
+				}
+				else
+				{
+					$bCreatedByTask = false;
 				}
 				}
-				if ($bCreated)
+				if ($bCreatedByTask)
 				{
 				{
 					$sDeletePolicy = $aData['datasource']->Get('delete_policy');
 					$sDeletePolicy = $aData['datasource']->Get('delete_policy');
 					if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
 					if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
 					{
 					{
-						$bCanBeDeleted = true;
-						$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sLink)."</p>";
+						$bCanBeDeletedByTask = true;
+					}
+					$sUserDeletePolicy = $aData['datasource']->Get('user_delete_policy');
+					if ($sUserDeletePolicy == 'nobody')
+					{
+						$bCanBeDeletedByUser = false;
+					}
+					elseif (($sUserDeletePolicy == 'administrators') && !UserRights::IsAdministrator())
+					{
+						$bCanBeDeletedByUser = false;
+					}
+					else // everybody...
+					{
 					}
 					}
 				}
 				}
 				$aMasterSources[$aData['datasource']->GetKey()]['datasource'] = $aData['datasource'];
 				$aMasterSources[$aData['datasource']->GetKey()]['datasource'] = $aData['datasource'];
 				$aMasterSources[$aData['datasource']->GetKey()]['url'] = $sLink;
 				$aMasterSources[$aData['datasource']->GetKey()]['url'] = $sLink;
 				$aMasterSources[$aData['datasource']->GetKey()]['last_synchro'] = $aData['replica']->Get('status_last_seen');
 				$aMasterSources[$aData['datasource']->GetKey()]['last_synchro'] = $aData['replica']->Get('status_last_seen');
 			}
 			}
-		}
-		
-		$sSynchroIcon = '';
-		if ($bSynchronized)
-		{
+
+			if (is_object($oCreatorTask))
+			{
+				$sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url'];
+				if (!$bCanBeDeletedByUser)
+				{
+					$sTip = "<p>".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sTaskUrl)."</p>";
+				}
+				else
+				{
+					$sTip = "<p>".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."</p>";
+				}
+				if ($bCanBeDeletedByTask)
+				{
+					$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."</p>";
+				}
+			}
+			else
+			{
+				$sTip = "<p>".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."</p>";
+			}
+
 			$sTip .= "<p><b>".Dict::S('Core:Synchro:ListOfDataSources')."</b></p>";
 			$sTip .= "<p><b>".Dict::S('Core:Synchro:ListOfDataSources')."</b></p>";
 			foreach($aMasterSources as $aStruct)
 			foreach($aMasterSources as $aStruct)
 			{
 			{
@@ -2203,7 +2236,7 @@ EOF
 		return parent::BulkUpdateTracked_Internal($oFilter, $aValues);
 		return parent::BulkUpdateTracked_Internal($oFilter, $aValues);
 	}
 	}
 
 
-	protected function DBDeleteTracked_Internal()
+	protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
 	{
 	{
 		// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
 		// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
 		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
 		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
@@ -2211,7 +2244,7 @@ EOF
 			$oExtensionInstance->OnDBDelete($this, self::$m_oCurrChange);
 			$oExtensionInstance->OnDBDelete($this, self::$m_oCurrChange);
 		}
 		}
 
 
-		return parent::DBDeleteTracked_Internal();
+		return parent::DBDeleteTracked_Internal($oDeletionPlan);
 	}
 	}
 
 
 	protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
 	protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
@@ -2223,6 +2256,9 @@ EOF
 	public function DoCheckToWrite()
 	public function DoCheckToWrite()
 	{
 	{
 		parent::DoCheckToWrite();
 		parent::DoCheckToWrite();
+
+		// Plugins
+		//
 		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
 		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
 		{
 		{
 			$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
 			$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
@@ -2231,11 +2267,37 @@ EOF
 				$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
 				$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
 			}
 			}
 		}
 		}
+
+		// User rights
+		//
+		$aChanges = $this->ListChanges();
+		if (count($aChanges) > 0)
+		{
+			$aForbiddenFields = array();
+			foreach ($this->ListChanges() as $sAttCode => $value)
+			{
+				$bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, UR_ACTION_MODIFY, DBObjectSet::FromObject($this));
+				if (!$bUpdateAllowed)
+				{
+					$oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+					$aForbiddenFields[] = $oAttCode->GetLabel();
+				}
+				if (count($aForbiddenFields) > 0)
+				{
+					// Security issue
+					$this->m_bSecurityIssue = true;
+					$this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenFields));
+				}
+			}
+		}
 	}
 	}
 
 
 	protected function DoCheckToDelete()
 	protected function DoCheckToDelete()
 	{
 	{
 		parent::DoCheckToDelete();
 		parent::DoCheckToDelete();
+
+		// Plugins
+		//
 		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
 		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
 		{
 		{
 			$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
 			$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
@@ -2244,6 +2306,16 @@ EOF
 				$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
 				$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
 			}
 			}
 		}
 		}
+
+		// User rights
+		//
+		$bDeleteAllowed = UserRights::IsActionAllowed(get_class($this), UR_ACTION_DELETE, DBObjectSet::FromObject($this));
+		if (!$bDeleteAllowed)
+		{
+			// Security issue
+			$this->m_bSecurityIssue = true;
+			$this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete');
+		}
 	}
 	}
 
 
 	/**
 	/**

+ 2 - 2
application/user.preferences.class.inc.php

@@ -169,9 +169,9 @@ class appUserPreferences extends DBObject
 	* Overloading this function here to secure a fix done right before the release
 	* Overloading this function here to secure a fix done right before the release
 	* The real fix should be to implement this verb in DBObject	
 	* The real fix should be to implement this verb in DBObject	
 	*/
 	*/
-	public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
+	public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
 	{
 	{
-		$this->DBDelete();
+		$this->DBDelete($oDeletionPlan);
 	}
 	}
 }
 }
 ?>
 ?>

+ 17 - 3
core/bulkchange.class.inc.php

@@ -620,7 +620,14 @@ class BulkChange
 			//
 			//
 			if ($oChange)
 			if ($oChange)
 			{
 			{
-				$oTargetObj->DBUpdateTracked($oChange);
+				try
+				{
+					$oTargetObj->DBUpdateTracked($oChange);
+				}
+				catch(CoreException $e)
+				{
+					$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue($e->getMessage());
+				}
 			}
 			}
 		}
 		}
 		else
 		else
@@ -649,12 +656,19 @@ class BulkChange
 		if (count($aChangedFields) > 0)
 		if (count($aChangedFields) > 0)
 		{
 		{
 			$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(count($aChangedFields));
 			$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(count($aChangedFields));
-	
+
 			// Optionaly record the results
 			// Optionaly record the results
 			//
 			//
 			if ($oChange)
 			if ($oChange)
 			{
 			{
-				$oTargetObj->DBUpdateTracked($oChange);
+				try
+				{
+					$oTargetObj->DBUpdateTracked($oChange);
+				}
+				catch(CoreException $e)
+				{
+					$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue($e->getMessage());
+				}
 			}
 			}
 		}
 		}
 		else
 		else

+ 6 - 6
core/cmdbobject.class.inc.php

@@ -369,28 +369,28 @@ abstract class CMDBObject extends DBObject
 		return $ret;
 		return $ret;
 	}
 	}
 
 
-	public function DBDelete()
+	public function DBDelete(&$oDeletionPlan = null)
 	{
 	{
 		if(!self::$m_oCurrChange)
 		if(!self::$m_oCurrChange)
 		{
 		{
 			throw new CoreException("DBDelete() could not be used here, please use DBDeleteTracked() instead");
 			throw new CoreException("DBDelete() could not be used here, please use DBDeleteTracked() instead");
 		}
 		}
-		return $this->DBDeleteTracked_Internal();
+		return $this->DBDeleteTracked_Internal($oDeletionPlan);
 	}
 	}
 
 
-	public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
+	public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
 	{
 	{
 		$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_DELETE);
 		$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_DELETE);
 
 
 		self::$m_oCurrChange = $oChange;
 		self::$m_oCurrChange = $oChange;
-		$this->DBDeleteTracked_Internal();
+		$this->DBDeleteTracked_Internal($oDeletionPlan);
 		self::$m_oCurrChange = null;
 		self::$m_oCurrChange = null;
 	}
 	}
 
 
-	protected function DBDeleteTracked_Internal()
+	protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
 	{
 	{
 		$prevkey = $this->GetKey();
 		$prevkey = $this->GetKey();
-		$ret = parent::DBDelete();
+		$ret = parent::DBDelete($oDeletionPlan);
 		$this->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
 		$this->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
 		return $ret;
 		return $ret;
 	}
 	}

+ 181 - 70
core/dbobject.class.php

@@ -24,6 +24,8 @@
  */
  */
 
 
 require_once('metamodel.class.php');
 require_once('metamodel.class.php');
+require_once('deletionplan.class.inc.php');
+
 
 
 /**
 /**
  * A persistent object, as defined by the metamodel 
  * A persistent object, as defined by the metamodel 
@@ -49,6 +51,7 @@ abstract class DBObject
 										// The object may have incorrect external keys, then any attempt of reload must be avoided
 										// The object may have incorrect external keys, then any attempt of reload must be avoided
 	private $m_bCheckStatus = null; // Means: the object has been verified and is consistent with integrity rules
 	private $m_bCheckStatus = null; // Means: the object has been verified and is consistent with integrity rules
 													//        if null, then the check has to be performed again to know the status
 													//        if null, then the check has to be performed again to know the status
+	protected $m_bSecurityIssue = null;
 	protected $m_aCheckIssues = null;
 	protected $m_aCheckIssues = null;
 	protected $m_aDeleteIssues = null;
 	protected $m_aDeleteIssues = null;
 
 
@@ -153,7 +156,7 @@ abstract class DBObject
 	protected function Reload()
 	protected function Reload()
 	{
 	{
 		assert($this->m_bIsInDB);
 		assert($this->m_bIsInDB);
-		$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey);
+		$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false/*, $this->m_bAllowAllData*/);
 		if (empty($aRow))
 		if (empty($aRow))
 		{
 		{
 			throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey);
 			throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey);
@@ -671,7 +674,7 @@ abstract class DBObject
 	 * @param string $sAttCode The code of the attribute
 	 * @param string $sAttCode The code of the attribute
 	 * @return integer Flags: the binary combination of the flags applicable to this attribute
 	 * @return integer Flags: the binary combination of the flags applicable to this attribute
 	 */	 	  	 	
 	 */	 	  	 	
-	public function GetAttributeFlags($sAttCode)
+	public function GetAttributeFlags($sAttCode, &$aReasons = array())
 	{
 	{
 		$iFlags = 0; // By default (if no life cycle) no flag at all
 		$iFlags = 0; // By default (if no life cycle) no flag at all
 		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
 		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
@@ -765,6 +768,7 @@ abstract class DBObject
 		$this->DoComputeValues();
 		$this->DoComputeValues();
 
 
 		$this->m_aCheckIssues = array();
 		$this->m_aCheckIssues = array();
+		$aChanges = $this->ListChanges();
 
 
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		{
 		{
@@ -787,6 +791,27 @@ abstract class DBObject
 			// $res contains the error description
 			// $res contains the error description
 			$this->m_aCheckIssues[] = "Consistency rules not followed: $res";
 			$this->m_aCheckIssues[] = "Consistency rules not followed: $res";
 		}
 		}
+
+		// Synchronization: are we attempting to modify an attribute for which an external source is master?
+		//
+		if ($this->m_bIsInDB && $this->InSyncScope() && (count($aChanges) > 0))
+		{
+			foreach($aChanges as $sAttCode => $value)
+			{
+				$iFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
+				if ($iFlags & OPT_ATT_SLAVE)
+				{
+					// Note: $aReasonInfo['name'] could be reported (the task owning the attribute)
+					$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+					$sAttLabel = $oAttDef->GetLabel();
+					foreach($aReasons as $aReasonInfo)
+					{
+						// Todo: associate the attribute code with the error
+						$this->m_aCheckIssues[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $sAttLabel);
+					}
+				}
+			}
+		}
 	}
 	}
 
 
 	final public function CheckToWrite()
 	final public function CheckToWrite()
@@ -809,7 +834,7 @@ abstract class DBObject
 				$this->m_bCheckStatus = false;
 				$this->m_bCheckStatus = false;
 			}
 			}
 		}
 		}
-		return array($this->m_bCheckStatus, $this->m_aCheckIssues);
+		return array($this->m_bCheckStatus, $this->m_aCheckIssues, $this->m_bSecurityIssue);
 	}
 	}
 
 
 	// check if it is allowed to delete the existing object from the database
 	// check if it is allowed to delete the existing object from the database
@@ -817,10 +842,57 @@ abstract class DBObject
 	protected function DoCheckToDelete()
 	protected function DoCheckToDelete()
 	{
 	{
 		$this->m_aDeleteIssues = array(); // Ok
 		$this->m_aDeleteIssues = array(); // Ok
+
+		if ($this->InSyncScope())
+		{
+			$oReplicaSet = $this->GetMasterReplica();
+			if ($oReplicaSet->Count() > 0)
+			{
+				while($aData = $oReplicaSet->FetchAssoc())
+				{
+					if ($aData['datasource']->GetKey() == SynchroDataSource::GetCurrentTaskId())
+					{
+						// The current task has the right to delete the object
+						continue;
+					}
+					
+					if ($aData['replica']->Get('status_dest_creator') != 1)
+					{
+						// The object is not owned by the task
+						continue;
+					}
+
+					$sLink = $aData['datasource']->GetName();
+					$sUserDeletePolicy = $aData['datasource']->Get('user_delete_policy');
+					switch($sUserDeletePolicy)
+					{
+					case 'nobody':
+						$this->m_aDeleteIssues[] = Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sLink);
+						break;
+
+					case 'administrators':
+						if (!UserRights::IsAdministrator())
+						{
+							$this->m_aDeleteIssues[] = Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sLink);
+						}
+						break;
+
+					case 'everybody':
+					default:
+						// Ok
+						break;
+					}
+				}
+			}
+		}
 	}
 	}
 
 
-  	// final public function CheckToDelete() - THE EQUIVALENT OF CheckToWrite IS NOT AVAILABLE
-  	// Todo - split the "DeleteObject()" function (UI.php) and move the generic part in cmdbAbstractObject, etc. 
+  	final public function CheckToDelete(&$oDeletionPlan)
+  	{
+		$this->MakeDeletionPlan($oDeletionPlan);
+		$oDeletionPlan->ComputeResults();
+		return (!$oDeletionPlan->FoundStopper());
+	} 
 
 
 	protected function ListChangedValues(array $aProposal)
 	protected function ListChangedValues(array $aProposal)
 	{
 	{
@@ -1079,7 +1151,8 @@ abstract class DBObject
 		list($bRes, $aIssues) = $this->CheckToWrite();
 		list($bRes, $aIssues) = $this->CheckToWrite();
 		if (!$bRes)
 		if (!$bRes)
 		{
 		{
-			throw new CoreException("Object not following integrity rules - it will not be written into the DB", array('class' => $sClass, 'id' => $this->GetKey(), 'issues' => $aIssues));
+			$sIssues = implode(', ', $aIssues);
+			throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
 		}
 		}
 
 
 		// First query built upon on the root class, because the ID must be created first
 		// First query built upon on the root class, because the ID must be created first
@@ -1176,7 +1249,8 @@ abstract class DBObject
 		list($bRes, $aIssues) = $this->CheckToWrite();
 		list($bRes, $aIssues) = $this->CheckToWrite();
 		if (!$bRes)
 		if (!$bRes)
 		{
 		{
-			throw new CoreException("Object not following integrity rules - it will not be written into the DB", array('class' => get_class($this), 'id' => $this->GetKey(), 'issues' => $aIssues));
+			$sIssues = implode(', ', $aIssues);
+			throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
 		}
 		}
 
 
 		$bHasANewExternalKeyValue = false;
 		$bHasANewExternalKeyValue = false;
@@ -1245,24 +1319,16 @@ abstract class DBObject
 		CMDBSource::DeleteFrom($sDeleteSQL);
 		CMDBSource::DeleteFrom($sDeleteSQL);
 	}
 	}
 
 
-	private function DBDeleteInternal()
-	{
-		$sClass = get_class($this);
-
-		foreach(MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL) as $sParentClass)
-		{
-			$this->DBDeleteSingleTable($sParentClass);
-		}
-	}
-	
-	// Delete a record
-	public function DBDelete()
+	private function DBDeleteSingleObject()
 	{
 	{
 		$this->OnDelete();
 		$this->OnDelete();
 
 
 		if (!MetaModel::DBIsReadOnly())
 		if (!MetaModel::DBIsReadOnly())
 		{
 		{
-			$this->DBDeleteInternal();
+			foreach(MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL) as $sParentClass)
+			{
+				$this->DBDeleteSingleTable($sParentClass);
+			}
 		}
 		}
 
 
 		$this->AfterDelete();
 		$this->AfterDelete();
@@ -1271,9 +1337,53 @@ abstract class DBObject
 		$this->m_iKey = null;
 		$this->m_iKey = null;
 	}
 	}
 
 
-	public function DBDeleteTracked(CMDBChange $oVoid)
+	// Delete an object... and guarantee data integrity
+	//
+	public function DBDelete(&$oDeletionPlan = null)
 	{
 	{
-		$this->DBDelete();
+		if (is_null($oDeletionPlan))
+		{
+			$oDeletionPlan = new DeletionPlan();
+		}
+		$this->MakeDeletionPlan($oDeletionPlan);
+		$oDeletionPlan->ComputeResults();
+
+		if ($oDeletionPlan->FoundStopper())
+		{
+			$aIssues = $oDeletionPlan->GetIssues();
+			throw new DeleteException('Found issue(s)', array('target_class' => get_class($this), 'target_id' => $this->GetKey(), 'issues' => implode(', ', $aIssues)));	
+		}
+		else
+		{
+			foreach ($oDeletionPlan->ListDeletes() as $sClass => $aToDelete)
+			{
+				foreach ($aToDelete as $iId => $aData)
+				{
+					$oToDelete = $aData['to_delete'];
+					$oToDelete->DBDeleteSingleObject();
+				}
+			}
+
+			foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate)
+			{
+				foreach ($aToUpdate as $iId => $aData)
+				{
+					$oToUpdate = $aData['to_reset'];
+					foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
+					{
+						$oToUpdate->Set($sRemoteExtKey, 0);
+						$oToUpdate->DBUpdate();
+					}
+				}
+			}
+		}
+
+		return $oDeletionPlan;
+	}
+
+	public function DBDeleteTracked(CMDBChange $oVoid, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
+	{
+		$this->DBDelete($oDeletionPlan);
 	}
 	}
 
 
 	public function EnumTransitions()
 	public function EnumTransitions()
@@ -1447,7 +1557,7 @@ abstract class DBObject
 		return $aResults;
 		return $aResults;
 	}
 	}
 
 
-	public function GetReferencingObjects()
+	public function GetReferencingObjects($bAllowAllData = false)
 	{
 	{
 		$aDependentObjects = array();
 		$aDependentObjects = array();
 		$aRererencingMe = MetaModel::EnumReferencingClasses(get_class($this));
 		$aRererencingMe = MetaModel::EnumReferencingClasses(get_class($this));
@@ -1460,6 +1570,10 @@ abstract class DBObject
 
 
 				$oSearch = new DBObjectSearch($sRemoteClass);
 				$oSearch = new DBObjectSearch($sRemoteClass);
 				$oSearch->AddCondition($sExtKeyAttCode, $this->GetKey(), '=');
 				$oSearch->AddCondition($sExtKeyAttCode, $this->GetKey(), '=');
+				if ($bAllowAllData)
+				{
+					$oSearch->AllowAllData();
+				}
 				$oSet = new CMDBObjectSet($oSearch);
 				$oSet = new CMDBObjectSet($oSearch);
 				if ($oSet->Count() > 0)
 				if ($oSet->Count() > 0)
 				{
 				{
@@ -1473,15 +1587,13 @@ abstract class DBObject
 		return $aDependentObjects;
 		return $aDependentObjects;
 	}
 	}
 
 
-	/**
-	 *		$aDeletedObjs = array(); // [class][key] => structure
-	 *		$aResetedObjs = array(); // [class][key] => object
-	 */	 	
-	public function GetDeletionScheme(&$aDeletedObjs, &$aResetedObjs, $aVisited = array())
+	private function MakeDeletionPlan(&$oDeletionPlan, $aVisited = array(), $iDeleteOption = null)
 	{
 	{
 		$sClass = get_class($this);
 		$sClass = get_class($this);
 		$iThisId = $this->GetKey();
 		$iThisId = $this->GetKey();
 
 
+		$iDeleteOption = $oDeletionPlan->AddToDelete($this, $iDeleteOption);
+
 		if (array_key_exists($sClass, $aVisited))
 		if (array_key_exists($sClass, $aVisited))
 		{
 		{
 			if (in_array($iThisId, $aVisited[$sClass]))
 			if (in_array($iThisId, $aVisited[$sClass]))
@@ -1491,13 +1603,16 @@ abstract class DBObject
 		}
 		}
 		$aVisited[$sClass] = $iThisId;
 		$aVisited[$sClass] = $iThisId;
 
 
-		$aDeletedObjs[$sClass][$iThisId]['to_delete'] = $this;
-		$aDeletedObjs[$sClass][$iThisId]['auto_delete'] = true;
+		if ($iDeleteOption == DEL_MANUAL)
+		{
+			// Stop the recursion here
+			return;
+		}
 		// Check the node itself
 		// Check the node itself
 		$this->DoCheckToDelete();
 		$this->DoCheckToDelete();
-		$aDeletedObjs[$sClass][$iThisId]['issues'] = $this->m_aDeleteIssues;
+		$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
 	
 	
-		$aDependentObjects = $this->GetReferencingObjects();
+		$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
 		foreach ($aDependentObjects as $sRemoteClass => $aPotentialDeletes)
 		foreach ($aDependentObjects as $sRemoteClass => $aPotentialDeletes)
 		{
 		{
 			foreach ($aPotentialDeletes as $sRemoteExtKey => $aData)
 			foreach ($aPotentialDeletes as $sRemoteExtKey => $aData)
@@ -1512,44 +1627,12 @@ abstract class DBObject
 					if ($oAttDef->IsNullAllowed())
 					if ($oAttDef->IsNullAllowed())
 					{
 					{
 						// Optional external key, list to reset
 						// Optional external key, list to reset
-						if (!array_key_exists($sRemoteClass, $aResetedObjs) || !array_key_exists($iId, $aResetedObjs[$sRemoteClass]))
-						{
-							$aResetedObjs[$sRemoteClass][$iId]['to_reset'] = $oDependentObj;
-						}
-						$aResetedObjs[$sRemoteClass][$iId]['attributes'][$sRemoteExtKey] = $oAttDef;
+						$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef);
 					}
 					}
 					else
 					else
 					{
 					{
 						// Mandatory external key, list to delete
 						// Mandatory external key, list to delete
-						if (array_key_exists($sRemoteClass, $aDeletedObjs) && array_key_exists($iId, $aDeletedObjs[$sRemoteClass]))
-						{
-							$iCurrentOption = $aDeletedObjs[$sRemoteClass][$iId];
-							if ($iCurrentOption == DEL_AUTO)
-							{
-								// be conservative, take the new option
-								// (DEL_MANUAL has precedence over DEL_AUTO)
-								$aDeletedObjs[$sRemoteClass][$iId]['auto_delete'] = ($iDeletePropagationOption == DEL_AUTO); 
-							}
-							else
-							{
-								// DEL_MANUAL... leave it as is, it HAS to be verified anyway
-							}
-						}
-						else
-						{
-							// First time we find the given object in the list
-							// (and most likely case is that no other occurence will be found)
-							if ($iDeletePropagationOption == DEL_AUTO)
-							{
-							// Recursively inspect this object
-								$oDependentObj->GetDeletionScheme($aDeletedObjs, $aResetedObjs, $aVisited);
-							}
-							else
-							{
-								$aDeletedObjs[$sRemoteClass][$iId]['to_delete'] = $oDependentObj;
-								$aDeletedObjs[$sRemoteClass][$iId]['auto_delete'] = false;
-							} 
-						}
+						$oDependentObj->MakeDeletionPlan($oDeletionPlan, $aVisited, $iDeletePropagationOption);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -1565,10 +1648,10 @@ abstract class DBObject
 	{
 	{
 		if ($this->m_oMasterReplicaSet == null)
 		if ($this->m_oMasterReplicaSet == null)
 		{
 		{
-			$aParentClasses = MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL);
-			$sClassesList = "'".implode("','", $aParentClasses)."'";
-			$sOQL = "SELECT replica,datasource FROM SynchroReplica AS replica JOIN SynchroDataSource AS datasource ON replica.sync_source_id=datasource.id WHERE datasource.scope_class IN ($sClassesList) AND replica.dest_id = :dest_id";
-			$oReplicaSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('dest_id' => $this->GetKey()));
+			//$aParentClasses = MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL);
+			//$sClassesList = "'".implode("','", $aParentClasses)."'";
+			$sOQL = "SELECT replica,datasource FROM SynchroReplica AS replica JOIN SynchroDataSource AS datasource ON replica.sync_source_id=datasource.id WHERE replica.dest_class = :dest_class AND replica.dest_id = :dest_id";
+			$oReplicaSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('dest_class' => get_class($this), 'dest_id' => $this->GetKey()));
 			$this->m_oMasterReplicaSet = $oReplicaSet;
 			$this->m_oMasterReplicaSet = $oReplicaSet;
 		}
 		}
 		else
 		else
@@ -1584,6 +1667,11 @@ abstract class DBObject
 		$oSet = $this->GetMasterReplica();
 		$oSet = $this->GetMasterReplica();
 		while($aData = $oSet->FetchAssoc())
 		while($aData = $oSet->FetchAssoc())
 		{
 		{
+			if ($aData['datasource']->GetKey() == SynchroDataSource::GetCurrentTaskId())
+			{
+				// Ignore the current task (check to write => ok)
+				continue;
+			}
 			// Assumption: $aData['datasource'] will not be null because the data source id is always set...
 			// Assumption: $aData['datasource'] will not be null because the data source id is always set...
 			$oReplica = $aData['replica'];
 			$oReplica = $aData['replica'];
 			$oSource = $aData['datasource'];
 			$oSource = $aData['datasource'];
@@ -1600,6 +1688,29 @@ abstract class DBObject
 		}
 		}
 		return $iFlags;
 		return $iFlags;
 	}
 	}
+
+	public function InSyncScope()
+	{
+		return true;
+
+		// TODO - FINALIZE THIS OPTIMIZATION
+		//
+		// Optimization: cache the list of Data Sources and classes candidates for synchro
+		//
+		static $aSynchroClasses = null;
+		if (is_null($aSynchroClasses))
+		{
+			$aSynchroClasses = array();
+			$sOQL = "SELECT SynchroDataSource AS datasource";
+			$oSourceSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array());
+			while($oSource = $oSourceSet->Fetch())
+			{
+				$sTarget = $oSource->Get('scope_class');
+				$aSynchroClasses[] = $oSource;
+			}
+		}
+		// to be continued...
+	}
 }
 }
 
 
 
 

+ 287 - 0
core/deletionplan.class.inc.php

@@ -0,0 +1,287 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Class dbObject: the root of persistent classes
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+
+class DeleteException extends CoreException
+{
+}
+
+/**
+ * Deletion plan (other objects to be deleted/modified, eventual issues, etc.) 
+ *
+ * @package     iTopORM
+ */
+class DeletionPlan
+{
+	//protected $m_aIssues;
+
+	protected $m_bFoundStopper;
+	protected $m_bFoundSecurityIssue;
+	protected $m_bFoundManualDelete;
+	protected $m_bFoundManualOperation;
+
+	protected $m_iToDelete;
+	protected $m_iToUpdate;
+
+  	protected $m_aToDelete;
+  	protected $m_aToUpdate;
+
+	protected static $m_aModeUpdate = array(
+		DEL_SILENT => array(
+			DEL_SILENT => DEL_SILENT,
+			DEL_AUTO => DEL_AUTO,
+			DEL_MANUAL => DEL_MANUAL
+		),
+		DEL_MANUAL => array(
+			DEL_SILENT => DEL_MANUAL,
+			DEL_AUTO => DEL_AUTO,
+			DEL_MANUAL => DEL_MANUAL
+		),
+		DEL_AUTO => array(
+			DEL_SILENT => DEL_AUTO,
+			DEL_AUTO => DEL_AUTO,
+			DEL_MANUAL => DEL_AUTO
+		)
+	);
+
+	public function __construct()
+	{
+		$this->m_iToDelete = 0;
+		$this->m_iToUpdate = 0;
+
+		$this->m_aToDelete = array();
+		$this->m_aToUpdate = array();
+
+		$this->m_bFoundStopper = false;
+		$this->m_bFoundSecurityIssue = false;
+		$this->m_bFoundManualDelete = false;
+		$this->m_bFoundManualOperation = false;
+	}
+
+	public function ComputeResults()
+	{
+		foreach($this->m_aToDelete as $sClass => $aToDelete)
+		{
+			foreach($aToDelete as $iId => $aData)
+			{
+				$this->m_iToDelete++;
+				if (isset($aData['issue']))
+				{
+					$this->m_bFoundStopper = true;
+					$this->m_bFoundManualOperation = true;
+					if (isset($aData['issue_security']))
+					{
+						$this->m_bFoundSecurityIssue = true;
+					}
+				}
+				if ($aData['mode'] == DEL_MANUAL)
+				{
+					$this->m_bFoundStopper = true;
+					$this->m_bFoundManualDelete = true;
+				}
+			}
+		}
+
+		foreach($this->m_aToUpdate as $sClass => $aToUpdate)
+		{
+			foreach($aToUpdate as $iId => $aData)
+			{
+				$this->m_iToUpdate++;
+
+            $oObject = $aData['to_reset'];
+				$aExtKeyLabels = array();
+				foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
+				{
+					$oObject->Set($sRemoteExtKey, 0);
+					$aExtKeyLabels[] = $aRemoteAttDef->GetLabel();
+				}
+				$this->m_aToUpdate[$sClass][$iId]['attributes_list'] = implode(', ', $aExtKeyLabels); 
+
+				list($bRes, $aIssues, $bSecurityIssues) = $oObject->CheckToWrite();
+				if (!$bRes)
+				{
+					$this->m_aToUpdate[$sClass][$iId]['issue'] = implode(', ', $aIssues);
+					$this->m_bFoundStopper = true;
+
+					if ($bSecurityIssues)
+					{
+						$this->m_aToUpdate[$sClass][$iId]['issue_security'] = true;
+						$this->m_bFoundSecurityIssue = true;
+					}
+				}
+
+			}
+		}
+	}
+
+	public function GetIssues()
+	{
+		$aIssues = array();
+		foreach ($this->m_aToDelete as $sClass => $aToDelete)
+		{
+			foreach ($aToDelete as $iId => $aData)
+			{
+				if (isset($aData['issue']))
+				{
+					$aIssues[] = $aData['issue'];
+				}
+			}
+		}
+		foreach ($this->m_aToUpdate as $sClass => $aToUpdate)
+		{
+			foreach ($aToUpdate as $iId => $aData)
+			{
+				if (isset($aData['issue']))
+				{
+					$aIssues[] = $aData['issue'];
+				}
+			}
+		}
+		return $aIssues;
+	}
+
+	public function ListDeletes()
+	{
+		return $this->m_aToDelete;
+	}
+
+	public function ListUpdates()
+	{
+		return $this->m_aToUpdate;
+	}
+
+	public function GetTargetCount()
+	{
+		return $this->m_iToDelete + $this->m_iToUpdate;
+	}
+
+	public function FoundStopper()
+	{
+		return $this->m_bFoundStopper;
+	}
+
+	public function FoundSecurityIssue()
+	{
+		return $this->m_bFoundSecurityIssue;
+	}
+
+	public function FoundManualOperation()
+	{
+		return $this->m_bFoundManualOperation;
+	}
+
+	public function FoundManualDelete()
+	{
+		return $this->m_bFoundManualDelete;
+	}
+
+	public function FoundManualUpdate()
+	{
+	}
+
+	public function AddToDelete($oObject, $iDeletionMode = null)
+	{
+		if (is_null($iDeletionMode))
+		{
+			$bRequestedExplicitely = true;
+			$iDeletionMode = DEL_AUTO;
+		}
+		else
+		{
+			$bRequestedExplicitely = false;
+		}
+
+		$sClass = get_class($oObject);
+		$iId = $oObject->GetKey();
+
+		if (isset($this->m_aToUpdate[$sClass][$iId]))
+		{
+			unset($this->m_aToUpdate[$sClass][$iId]);
+		}
+
+		if (isset($this->m_aToDelete[$sClass][$iId]))
+		{
+			if ($this->m_aToDelete[$sClass][$iId]['requested_explicitely'])
+			{
+				// No change: let it in mode DEL_AUTO
+			}
+			else
+			{
+				$iPrevDeletionMode = $this->m_aToDelete[$sClass][$iId]['mode'];
+				$iNewDeletionMode = self::$m_aModeUpdate[$iPrevDeletionMode][$iDeletionMode];
+				$this->m_aToDelete[$sClass][$iId]['mode'] = $iNewDeletionMode;
+	
+				if ($bRequestedExplicitely)
+				{
+					// This object was in the root list
+					$this->m_aToDelete[$sClass][$iId]['requested_explicitely'] = true;
+					$this->m_aToDelete[$sClass][$iId]['mode'] = DEL_AUTO;
+				}
+			}
+		}
+		else
+		{
+			$this->m_aToDelete[$sClass][$iId] = array(
+				'to_delete' => $oObject,
+				'mode' => $iDeletionMode,
+				'requested_explicitely' => $bRequestedExplicitely,
+			);
+		}
+	}
+
+	public function SetDeletionIssues($oObject, $aIssues, $bSecurityIssue)
+	{
+		if (count($aIssues) > 0)
+		{
+			$sClass = get_class($oObject);
+			$iId = $oObject->GetKey();
+			$this->m_aToDelete[$sClass][$iId]['issue'] = implode(', ', $aIssues);
+			if ($bSecurityIssue)
+			{
+				$this->m_aToDelete[$sClass][$iId]['issue_security'] = true;
+			}
+		}
+	}
+
+	public function AddToUpdate($oObject, $oAttDef)
+	{
+		$sClass = get_class($oObject);
+		$iId = $oObject->GetKey();
+		if (isset($this->m_aToDelete[$sClass][$iId]))
+		{
+			// skip... it should be deleted anyhow !
+		}
+		else
+		{
+			if (!isset($this->m_aToUpdate[$sClass][$iId]))
+			{
+				$this->m_aToUpdate[$sClass][$iId] = array(
+					'to_reset' => $oObject,
+				);
+			}
+			$this->m_aToUpdate[$sClass][$iId]['attributes'][$oAttDef->GetCode()] = $oAttDef;
+		}
+	}
+}
+?>

+ 1 - 1
core/dict.class.inc.php

@@ -163,6 +163,7 @@ class Dict
 	{
 	{
 		$sLocalizedFormat = self::S($sFormatCode);
 		$sLocalizedFormat = self::S($sFormatCode);
 		$aArguments = func_get_args();
 		$aArguments = func_get_args();
+		array_shift($aArguments);
 		
 		
 		if ($sLocalizedFormat == $sFormatCode)
 		if ($sLocalizedFormat == $sFormatCode)
 		{
 		{
@@ -170,7 +171,6 @@ class Dict
 			return $sFormatCode.' - '.implode(', ', $aArguments);
 			return $sFormatCode.' - '.implode(', ', $aArguments);
 		}
 		}
 
 
-		array_shift($aArguments);
 		return vsprintf($sLocalizedFormat, $aArguments);
 		return vsprintf($sLocalizedFormat, $aArguments);
 	}
 	}
 
 

+ 1 - 2
dictionaries/dictionary.itop.core.php

@@ -560,8 +560,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'Core:Synchro:ThisObjectIsSynchronized' => 'This object is synchronized with an external data source',
 	'Core:Synchro:ThisObjectIsSynchronized' => 'This object is synchronized with an external data source',
 	'Core:Synchro:TheObjectWasCreatedBy_Source' => 'The object was <b>created</b> by the external data source %1$s',
 	'Core:Synchro:TheObjectWasCreatedBy_Source' => 'The object was <b>created</b> by the external data source %1$s',
 	'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'The object <b>can be deleted</b> by the external data source %1$s',
 	'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'The object <b>can be deleted</b> by the external data source %1$s',
-
-			
+	'Core:Synchro:TheObjectCannotBeDeletedByUser_Source' => 'You <b>cannot delete the object</b> because it is owned by the external data source %1$s',
 ));
 ));
 
 
 //
 //

+ 43 - 136
pages/UI.php

@@ -28,102 +28,26 @@
  */
  */
 function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 {
 {
-	$bFoundStopper = false;
-	$bFoundManuelOp = false; // A manual operation is needed
-	$bFoundManualDel = false; // Manual deletion (or change an ext key) is needed
-	$bFoundSecurityIssue = false; // Some operation not allowed to the end-user
+	$oDeletionPlan = new DeletionPlan();
 
 
-	$iTotalDelete = 0; // count of object that must be deleted
-	$iTotalReset = 0; // count of object for which an ext key will be reset (to 0)
-	$aTotalDeletedObjs = array();
-	$aTotalResetedObjs = array();
-
-	$aRequestedByUser = array();
 	foreach($aObjects as $oObj)
 	foreach($aObjects as $oObj)
 	{
 	{
-		$aRequestedByUser[] = $oObj->GetKey();
-	}
-	
-	foreach($aObjects as $oObj)
-	{
-		// Evaluate the impact on the DB integrity
-		//
-		$aDeletedObjs = array();
-		$aResetedObjs = array();
-		$oObj->GetDeletionScheme($aDeletedObjs, $aResetedObjs);
-
-		// Evaluate feasibility (user access control)
-		//
-		foreach ($aDeletedObjs as $sRemoteClass => $aDeletes)
+		if ($bDeleteConfirmed)
 		{
 		{
-			$iTotalDelete += count($aDeletes);
-			foreach ($aDeletes as $iId => $aData)
-			{
-				$oToDelete = $aData['to_delete'];
-				$aTotalDeletedObjs[$sRemoteClass][$iId]['to_delete'] = $oToDelete;
-				if (in_array($iId, $aRequestedByUser))
-				{
-					$aTotalDeletedObjs[$sRemoteClass][$iId]['requested_by_user'] = true;
-				}
-				$bDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oToDelete));
-				$aTotalDeletedObjs[$sRemoteClass][$iId]['auto_delete'] = $aData['auto_delete'];
-				if (!$bDeleteAllowed)
-				{
-					$aTotalDeletedObjs[$sRemoteClass][$iId]['issue'] = Dict::S('UI:Delete:NotAllowedToDelete');
-					$bFoundStopper = true;
-					$bFoundSecurityIssue = true;
-				}
-				elseif (isset($aData['issues']) && (count($aData['issues']) > 0))
-				{
-					$sIssueDesc = implode(', ', $aData['issues']);
-					$aTotalDeletedObjs[$sRemoteClass][$iId]['issue'] = $sIssueDesc;
-					$bFoundStopper = true;
-					$bFoundManuelOp = true;
-				}
-	
-				$bAutoDel = $aData['auto_delete'];
-				if (!$bAutoDel)
-				{
-					$bFoundStopper = true;
-					$bFoundManualDel = true;
-				}
-			}
-		}
+			// Prepare the change reporting
+			//
+			$oMyChange = MetaModel::NewObject("CMDBChange");
+			$oMyChange->Set("date", time());
+			$sUserString = CMDBChange::GetCurrentUserName();
+			$oMyChange->Set("userinfo", $sUserString);
+			$oMyChange->DBInsert();
 
 
-		foreach ($aResetedObjs as $sRemoteClass => $aToReset)
+			$oObj->DBDeleteTracked($oMyChange, null, $oDeletionPlan);
+		}
+		else
 		{
 		{
-			$iTotalReset += count($aToReset);
-			foreach ($aToReset as $iId => $aData)
-			{
-				$oToReset = $aData['to_reset'];
-				$aExtKeyLabels = array();
-				$aForbiddenKeys = array(); // keys on which the current user is not allowed to write
-				foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
-				{
-					$bUpdateAllowed = UserRights::IsActionAllowedOnAttribute($sClass, $sRemoteExtKey, UR_ACTION_MODIFY, DBObjectSet::FromObject($oToReset));
-					if (!$bUpdateAllowed)
-					{
-						$bFoundStopper = true;
-						$bFoundSecurityIssue = true;
-						$aForbiddenKeys[] = $aRemoteAttDef->GetLabel();
-					}
-					$aExtKeyLabels[] = $aRemoteAttDef->GetLabel();
-				}
-				$aResetedObjs[$sRemoteClass][$iId]['attributes_list'] = implode(', ', $aExtKeyLabels); 
-				$aTotalResetedObjs[$sRemoteClass][$iId]['attributes_list'] = $aResetedObjs[$sRemoteClass][$iId]['attributes_list'];
-				$aTotalResetedObjs[$sRemoteClass][$iId]['attributes'] = $aResetedObjs[$sRemoteClass][$iId]['attributes'];
-				if (count($aForbiddenKeys) > 0)
-				{
-					$aTotalResetedObjs[$sRemoteClass][$iId]['issue'] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenKeys));
-				}
-				else
-				{
-					$aTotalResetedObjs[$sRemoteClass][$iId]['to_reset'] = $oToReset;
-				}
-			}
+			$oObj->CheckToDelete($oDeletionPlan);
 		}
 		}
-		// Count of dependent objects (+ the current one)
-		$iTotalTargets = $iTotalDelete + $iTotalReset;
 	}
 	}
 	
 	
 	if ($bDeleteConfirmed)
 	if ($bDeleteConfirmed)
@@ -138,37 +62,29 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 			$oP->add("<h1>".Dict::Format('UI:Title:BulkDeletionOf_Count_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass))."</h1>\n");		
 			$oP->add("<h1>".Dict::Format('UI:Title:BulkDeletionOf_Count_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass))."</h1>\n");		
 		}
 		}
 		// Security - do not allow the user to force a forbidden delete by the mean of page arguments...
 		// Security - do not allow the user to force a forbidden delete by the mean of page arguments...
-		if ($bFoundSecurityIssue)
+		if ($oDeletionPlan->FoundSecurityIssue())
 		{
 		{
 			throw new CoreException(Dict::S('UI:Error:NotEnoughRightsToDelete'));
 			throw new CoreException(Dict::S('UI:Error:NotEnoughRightsToDelete'));
 		}
 		}
-		if ($bFoundManuelOp)
+		if ($oDeletionPlan->FoundManualOperation())
 		{
 		{
 			throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseManualOpNeeded'));
 			throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseManualOpNeeded'));
 		}
 		}
-		if ($bFoundManualDel)
+		if ($oDeletionPlan->FoundManualDelete())
 		{
 		{
 			throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseOfDepencies'));
 			throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseOfDepencies'));
 		}
 		}
 
 
-		// Prepare the change reporting
-		//
-		$oMyChange = MetaModel::NewObject("CMDBChange");
-		$oMyChange->Set("date", time());
-		$sUserString = CMDBChange::GetCurrentUserName();
-		$oMyChange->Set("userinfo", $sUserString);
-		$oMyChange->DBInsert();
-
-		// Delete dependencies
+		// Report deletions
 		//
 		//
 		$aDisplayData = array();
 		$aDisplayData = array();
-		foreach ($aTotalDeletedObjs as $sRemoteClass => $aDeletes)
+		foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes)
 		{
 		{
 			foreach ($aDeletes as $iId => $aData)
 			foreach ($aDeletes as $iId => $aData)
 			{
 			{
 				$oToDelete = $aData['to_delete'];
 				$oToDelete = $aData['to_delete'];
 
 
-				if (isset($aData['requested_by_user']))
+				if (isset($aData['requested_explicitely']))
 				{
 				{
 					$sMessage = Dict::S('UI:Delete:Deleted');
 					$sMessage = Dict::S('UI:Delete:Deleted');
 				}
 				}
@@ -181,35 +97,27 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 					'object' => $oToDelete->GetHyperLink(),
 					'object' => $oToDelete->GetHyperLink(),
 					'consequence' => $sMessage,
 					'consequence' => $sMessage,
 				);
 				);
-
-				$oToDelete->DBDeleteTracked($oMyChange);
 			}
 			}
 		}
 		}
 	
 	
-		// Update dependencies
+		// Report updates
 		//
 		//
-		foreach ($aTotalResetedObjs as $sRemoteClass => $aToReset)
+		foreach ($oDeletionPlan->ListUpdates() as $sTargetClass => $aToUpdate)
 		{
 		{
-			foreach ($aToReset as $iId => $aData)
+			foreach ($aToUpdate as $iId => $aData)
 			{
 			{
-				$oToReset = $aData['to_reset'];
+				$oToUpdate = $aData['to_reset'];
 				$aDisplayData[] = array(
 				$aDisplayData[] = array(
-					'class' => MetaModel::GetName(get_class($oToReset)),
-					'object' => $oToReset->GetHyperLink(),
+					'class' => MetaModel::GetName(get_class($oToUpdate)),
+					'object' => $oToUpdate->GetHyperLink(),
 					'consequence' => Dict::Format('UI:Delete:AutomaticResetOf_Fields', $aData['attributes_list']),
 					'consequence' => Dict::Format('UI:Delete:AutomaticResetOf_Fields', $aData['attributes_list']),
 				);
 				);
-
-				foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
-				{
-					$oToReset->Set($sRemoteExtKey, 0);
-					$oToReset->DBUpdateTracked($oMyChange);
-				}
 			}
 			}
 		}
 		}
 
 
 		// Report automatic jobs
 		// Report automatic jobs
 		//
 		//
-		if ($iTotalTargets > 0)
+		if ($oDeletionPlan->GetTargetCount() > 0)
 		{
 		{
 			if (count($aObjects) == 1)
 			if (count($aObjects) == 1)
 			{
 			{
@@ -241,17 +149,17 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 		// Explain what should be done
 		// Explain what should be done
 		//
 		//
 		$aDisplayData = array();
 		$aDisplayData = array();
-		foreach ($aTotalDeletedObjs as $sRemoteClass => $aDeletes)
+		foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes)
 		{
 		{
 			foreach ($aDeletes as $iId => $aData)
 			foreach ($aDeletes as $iId => $aData)
 			{
 			{
 				$oToDelete = $aData['to_delete'];
 				$oToDelete = $aData['to_delete'];
-				$bAutoDel = $aData['auto_delete'];
+				$bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO));
 				if (array_key_exists('issue', $aData))
 				if (array_key_exists('issue', $aData))
 				{
 				{
 					if ($bAutoDel)
 					if ($bAutoDel)
 					{
 					{
-						if (isset($aData['requested_by_user']))
+						if (isset($aData['requested_explicitely']))
 						{
 						{
 							$sConsequence = Dict::Format('UI:Delete:CannotDeleteBecause', $aData['issue']);
 							$sConsequence = Dict::Format('UI:Delete:CannotDeleteBecause', $aData['issue']);
 						}
 						}
@@ -269,7 +177,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 				{
 				{
 					if ($bAutoDel)
 					if ($bAutoDel)
 					{
 					{
-						if (isset($aData['requested_by_user']))
+						if (isset($aData['requested_explicitely']))
 						{
 						{
 	                  $sConsequence = ''; // not applicable
 	                  $sConsequence = ''; // not applicable
 						}
 						}
@@ -290,11 +198,11 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 				);
 				);
 			}
 			}
 		}
 		}
-		foreach ($aTotalResetedObjs as $sRemoteClass => $aToReset)
+		foreach ($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate)
 		{
 		{
-			foreach ($aToReset as $iId => $aData)
+			foreach ($aToUpdate as $iId => $aData)
 			{
 			{
-				$oToReset = $aData['to_reset'];
+				$oToUpdate = $aData['to_reset'];
 				if (array_key_exists('issue', $aData))
 				if (array_key_exists('issue', $aData))
 				{
 				{
 					$sConsequence = Dict::Format('UI:Delete:CannotUpdateBecause_Issue', $aData['issue']);
 					$sConsequence = Dict::Format('UI:Delete:CannotUpdateBecause_Issue', $aData['issue']);
@@ -304,29 +212,29 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 					$sConsequence = Dict::Format('UI:Delete:WillAutomaticallyUpdate_Fields', $aData['attributes_list']);
 					$sConsequence = Dict::Format('UI:Delete:WillAutomaticallyUpdate_Fields', $aData['attributes_list']);
 				}
 				}
 				$aDisplayData[] = array(
 				$aDisplayData[] = array(
-					'class' => MetaModel::GetName(get_class($oToReset)),
-					'object' => $oToReset->GetHyperLink(),
+					'class' => MetaModel::GetName(get_class($oToUpdate)),
+					'object' => $oToUpdate->GetHyperLink(),
 					'consequence' => $sConsequence,
 					'consequence' => $sConsequence,
 				);
 				);
 			}
 			}
 		}
 		}
 
 
-      $iInducedDeletions = $iTotalTargets - count($aObjects);
-		if ($iInducedDeletions > 0)
+      $iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects);
+		if ($iImpactedIndirectly > 0)
 		{
 		{
 			if (count($aObjects) == 1)
 			if (count($aObjects) == 1)
 			{
 			{
 				$oObj = $aObjects[0];
 				$oObj = $aObjects[0];
-				$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iInducedDeletions, $oObj->GetName()));
+				$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly, $oObj->GetName()));
 			}
 			}
 			else
 			else
 			{
 			{
-				$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iInducedDeletions));
+				$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iImpactedIndirectly));
 			}
 			}
 			$oP->p(Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity'));
 			$oP->p(Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity'));
 		}
 		}
 
 
-		if (($iInducedDeletions > 0) || $bFoundStopper)
+		if (($iImpactedIndirectly > 0) || $oDeletionPlan->FoundStopper())
 		{
 		{
 			$aDisplayConfig = array();
 			$aDisplayConfig = array();
 			$aDisplayConfig['class'] = array('label' => 'Class', 'description' => '');
 			$aDisplayConfig['class'] = array('label' => 'Class', 'description' => '');
@@ -335,13 +243,13 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 			$oP->table($aDisplayConfig, $aDisplayData);
 			$oP->table($aDisplayConfig, $aDisplayData);
 		}
 		}
 
 
-		if ($bFoundStopper)
+		if ($oDeletionPlan->FoundStopper())
 		{
 		{
-			if ($bFoundSecurityIssue)
+			if ($oDeletionPlan->FoundSecurityIssue())
 			{
 			{
 				$oP->p(Dict::S('UI:Delete:SorryDeletionNotAllowed'));
 				$oP->p(Dict::S('UI:Delete:SorryDeletionNotAllowed'));
 			}
 			}
-			elseif ($bFoundManualDel)
+			elseif ($oDeletionPlan->FoundManualOperation())
 			{
 			{
 				$oP->p(Dict::S('UI:Delete:PleaseDoTheManualOperations'));
 				$oP->p(Dict::S('UI:Delete:PleaseDoTheManualOperations'));
 			}
 			}
@@ -390,7 +298,6 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
 			}
 			}
 		}
 		}
 	}
 	}
-
 }
 }
 
 
 /**
 /**

+ 23 - 8
synchro/synchrodatasource.class.inc.php

@@ -76,20 +76,33 @@ class SynchroDataSource extends cmdbAbstractObject
 
 
 		MetaModel::Init_AddAttribute(new AttributeLinkedSet("attribute_list", array("linked_class"=>"SynchroAttribute", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeLinkedSet("attribute_list", array("linked_class"=>"SynchroAttribute", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
 		// Not used yet !
 		// Not used yet !
-		MetaModel::Init_AddAttribute(new AttributeEnum("user_delete_policy", array("allowed_values"=>new ValueSetEnum('never,depends,always'), "sql"=>"user_delete_policy", "default_value"=>"always", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("user_delete_policy", array("allowed_values"=>new ValueSetEnum('everybody,administrators,nobody'), "sql"=>"user_delete_policy", "default_value"=>"nobody", "is_null_allowed"=>true, "depends_on"=>array())));
 
 
 		MetaModel::Init_AddAttribute(new AttributeURL("url_icon", array("allowed_values"=>null, "sql"=>"url_icon", "default_value"=>null, "is_null_allowed"=>true, "target"=> '_top', "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeURL("url_icon", array("allowed_values"=>null, "sql"=>"url_icon", "default_value"=>null, "is_null_allowed"=>true, "target"=> '_top', "depends_on"=>array())));
 		// The field below is not a real URL since it can contain placeholders like $replica->primary_key$ which are not syntactically allowed in a real URL
 		// The field below is not a real URL since it can contain placeholders like $replica->primary_key$ which are not syntactically allowed in a real URL
 		MetaModel::Init_AddAttribute(new AttributeString("url_application", array("allowed_values"=>null, "sql"=>"url_application", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeString("url_application", array("allowed_values"=>null, "sql"=>"url_application", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 
 
 		// Display lists
 		// Display lists
-		MetaModel::Init_SetZListItems('details', array('name', 'description', 'url_icon', 'url_application', 'scope_class', /*'scope_restriction', */'status', 'user_id', 'full_load_periodicity', 'reconciliation_policy', 'action_on_zero', 'action_on_one', 'action_on_multiple', 'delete_policy', 'delete_policy_update', 'delete_policy_retention' /*'attribute_list'*/)); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('details', array('name', 'description', 'url_icon', 'url_application', 'scope_class', /*'scope_restriction', */'status', 'user_id', 'full_load_periodicity', 'reconciliation_policy', 'action_on_zero', 'action_on_one', 'action_on_multiple', 'delete_policy', 'delete_policy_update', 'delete_policy_retention', 'user_delete_policy')); // Attributes to be displayed for the complete details
 		MetaModel::Init_SetZListItems('list', array('scope_class', 'status', 'user_id', 'full_load_periodicity')); // Attributes to be displayed for a list
 		MetaModel::Init_SetZListItems('list', array('scope_class', 'status', 'user_id', 'full_load_periodicity')); // Attributes to be displayed for a list
 		// Search criteria
 		// Search criteria
 		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope_class', 'user_id')); // Criteria of the std search form
 		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope_class', 'user_id')); // Criteria of the std search form
 //		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
 //		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
 	}
 	}
 
 
+	public static $m_oCurrentTask = null;
+	public static function GetCurrentTaskId()
+	{
+		if (is_object(self::$m_oCurrentTask))
+		{
+			return self::$m_oCurrentTask->GetKey();
+		}
+		else
+		{
+			return null;
+		}
+	}
+
 	public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
 	public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
 	{
 	{
 		if (!$this->IsNew())
 		if (!$this->IsNew())
@@ -406,13 +419,13 @@ EOF
 		return str_replace($aSearches, $aReplacements, $this->Get('url_application'));
 		return str_replace($aSearches, $aReplacements, $this->Get('url_application'));
 	}
 	}
 	
 	
-	public function GetAttributeFlags($sAttCode)
+	public function GetAttributeFlags($sAttCode, &$aReasons = array())
 	{
 	{
 		if (($sAttCode == 'scope_class') && (!$this->IsNew()))
 		if (($sAttCode == 'scope_class') && (!$this->IsNew()))
 		{
 		{
 			return OPT_ATT_READONLY;
 			return OPT_ATT_READONLY;
 		}
 		}
-		return parent::GetAttributeFlags($sAttCode);
+		return parent::GetAttributeFlags($sAttCode, $aReasons);
 	}
 	}
 		
 		
 	public function UpdateObject($sFormPrefix = '')
 	public function UpdateObject($sFormPrefix = '')
@@ -690,6 +703,7 @@ EOF
 
 
 		$oStatLog->DBInsertTracked($oMyChange);
 		$oStatLog->DBInsertTracked($oMyChange);
 
 
+		self::$m_oCurrentTask = $this;
 		try
 		try
 		{
 		{
 			$this->DoSynchronize($oLastFullLoadStartDate, $oMyChange, $oStatLog);
 			$this->DoSynchronize($oLastFullLoadStartDate, $oMyChange, $oStatLog);
@@ -713,6 +727,7 @@ EOF
 			$oStatLog->Set('last_error', $e->getMessage());
 			$oStatLog->Set('last_error', $e->getMessage());
 			$oStatLog->DBUpdateTracked($oMyChange);
 			$oStatLog->DBUpdateTracked($oMyChange);
 		}
 		}
+		self::$m_oCurrentTask = null;
 		return $oStatLog;
 		return $oStatLog;
 	}
 	}
 
 
@@ -998,7 +1013,7 @@ class SynchroAttribute extends cmdbAbstractObject
 		);
 		);
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_InheritAttributes();
 		MetaModel::Init_InheritAttributes();
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeBoolean("update", array("allowed_values"=>null, "sql"=>"update", "default_value"=>true, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeBoolean("update", array("allowed_values"=>null, "sql"=>"update", "default_value"=>true, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeBoolean("reconcile", array("allowed_values"=>null, "sql"=>"reconcile", "default_value"=>false, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeBoolean("reconcile", array("allowed_values"=>null, "sql"=>"reconcile", "default_value"=>false, "is_null_allowed"=>false, "depends_on"=>array())));
@@ -1113,7 +1128,7 @@ class SynchroLog extends DBObject
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_InheritAttributes();
 		MetaModel::Init_InheritAttributes();
 //		MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 //		MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('running,completed,error'), "sql"=>"status", "default_value"=>"running", "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('running,completed,error'), "sql"=>"status", "default_value"=>"running", "is_null_allowed"=>false, "depends_on"=>array())));
@@ -1244,7 +1259,7 @@ class SynchroReplica extends DBObject implements iDisplay
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_InheritAttributes();
 		MetaModel::Init_InheritAttributes();
 
 
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeExternalField("base_class", array("allowed_values"=>null, "extkey_attcode"=> 'sync_source_id', "target_attcode"=>"scope_class")));
 		MetaModel::Init_AddAttribute(new AttributeExternalField("base_class", array("allowed_values"=>null, "extkey_attcode"=> 'sync_source_id', "target_attcode"=>"scope_class")));
 
 
 		MetaModel::Init_AddAttribute(new AttributeInteger("dest_id", array("allowed_values"=>null, "sql"=>"dest_id", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeInteger("dest_id", array("allowed_values"=>null, "sql"=>"dest_id", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
@@ -1273,7 +1288,7 @@ class SynchroReplica extends DBObject implements iDisplay
 
 
 	// Overload the deletion -> the replica has been created by the mean of a trigger,
 	// Overload the deletion -> the replica has been created by the mean of a trigger,
 	//                          it will be deleted by the mean of a trigger too
 	//                          it will be deleted by the mean of a trigger too
-	public function DBDelete()
+	public function DBDelete(&$oDeletionPlan = null)
 	{
 	{
 		$oDataSource = MetaModel::GetObject('SynchroDataSource', $this->Get('sync_source_id'));
 		$oDataSource = MetaModel::GetObject('SynchroDataSource', $this->Get('sync_source_id'));
 		$sTable = $oDataSource->GetDataTable();
 		$sTable = $oDataSource->GetDataTable();