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

SVN:trunk[1194]
This commit is contained in:
Romain Quetiez
2011-04-08 11:29:25 +00:00
parent 037d6cc4ba
commit 8ff40fb4b8
10 changed files with 649 additions and 244 deletions

View File

@@ -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; $oCreatorTask = null;
$bCanBeDeleted = false; $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>"; $oCreatorTask = $aData['datasource'];
$bCreated = true; $bCreatedByTask = true;
} }
if ($bCreated) else
{
$bCreatedByTask = false;
}
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; $bCanBeDeletedByTask = true;
$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sLink)."</p>"; }
$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');
} }
}
if (is_object($oCreatorTask))
$sSynchroIcon = ''; {
if ($bSynchronized) $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');
}
} }
/** /**

View File

@@ -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);
} }
} }
?> ?>

View File

@@ -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

View File

@@ -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;
} }

View File

@@ -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 final public function CheckToDelete(&$oDeletionPlan)
// Todo - split the "DeleteObject()" function (UI.php) and move the generic part in cmdbAbstractObject, etc. {
$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() private function DBDeleteSingleObject()
{
$sClass = get_class($this);
foreach(MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL) as $sParentClass)
{
$this->DBDeleteSingleTable($sParentClass);
}
}
// Delete a record
public function DBDelete()
{ {
$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;
} }
/** private function MakeDeletionPlan(&$oDeletionPlan, $aVisited = array(), $iDeleteOption = null)
* $aDeletedObjs = array(); // [class][key] => structure
* $aResetedObjs = array(); // [class][key] => object
*/
public function GetDeletionScheme(&$aDeletedObjs, &$aResetedObjs, $aVisited = array())
{ {
$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; if ($iDeleteOption == DEL_MANUAL)
$aDeletedObjs[$sClass][$iThisId]['auto_delete'] = true; {
// 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])) $oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef);
{
$aResetedObjs[$sRemoteClass][$iId]['to_reset'] = $oDependentObj;
}
$aResetedObjs[$sRemoteClass][$iId]['attributes'][$sRemoteExtKey] = $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])) $oDependentObj->MakeDeletionPlan($oDeletionPlan, $aVisited, $iDeletePropagationOption);
{
$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;
}
}
} }
} }
} }
@@ -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); //$aParentClasses = MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL);
$sClassesList = "'".implode("','", $aParentClasses)."'"; //$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"; $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_id' => $this->GetKey())); $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...
}
} }

View File

@@ -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;
}
}
}
?>

View File

@@ -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);
} }

View File

@@ -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',
)); ));
// //

View File

@@ -28,102 +28,26 @@
*/ */
function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed) function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
{ {
$bFoundStopper = false; $oDeletionPlan = new DeletionPlan();
$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
$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(); if ($bDeleteConfirmed)
}
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)
{ {
$iTotalDelete += count($aDeletes); // Prepare the change reporting
foreach ($aDeletes as $iId => $aData) //
{ $oMyChange = MetaModel::NewObject("CMDBChange");
$oToDelete = $aData['to_delete']; $oMyChange->Set("date", time());
$aTotalDeletedObjs[$sRemoteClass][$iId]['to_delete'] = $oToDelete; $sUserString = CMDBChange::GetCurrentUserName();
if (in_array($iId, $aRequestedByUser)) $oMyChange->Set("userinfo", $sUserString);
{ $oMyChange->DBInsert();
$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;
}
}
}
foreach ($aResetedObjs as $sRemoteClass => $aToReset) $oObj->DBDeleteTracked($oMyChange, null, $oDeletionPlan);
{ }
$iTotalReset += count($aToReset); else
foreach ($aToReset as $iId => $aData) {
{ $oObj->CheckToDelete($oDeletionPlan);
$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;
}
}
} }
// 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 // Report deletions
//
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$sUserString = CMDBChange::GetCurrentUserName();
$oMyChange->Set("userinfo", $sUserString);
$oMyChange->DBInsert();
// Delete dependencies
// //
$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)), 'class' => MetaModel::GetName(get_class($oToUpdate)),
'object' => $oToReset->GetHyperLink(), '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)), 'class' => MetaModel::GetName(get_class($oToUpdate)),
'object' => $oToReset->GetHyperLink(), 'object' => $oToUpdate->GetHyperLink(),
'consequence' => $sConsequence, 'consequence' => $sConsequence,
); );
} }
} }
$iInducedDeletions = $iTotalTargets - count($aObjects); $iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects);
if ($iInducedDeletions > 0) 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)
} }
} }
} }
} }
/** /**

View File

@@ -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();