mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
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:
@@ -102,15 +102,16 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
$oBlock->Display($oPage, -1);
|
||||
|
||||
// Master data sources
|
||||
$sSynchroIcon = '';
|
||||
$oReplicaSet = $this->GetMasterReplica();
|
||||
$bSynchronized = false;
|
||||
$bCreated = false;
|
||||
$bCanBeDeleted = false;
|
||||
$oCreatorTask = null;
|
||||
$bCanBeDeletedByTask = false;
|
||||
$bCanBeDeletedByUser = true;
|
||||
$aMasterSources = array();
|
||||
if ($oReplicaSet->Count() > 0)
|
||||
{
|
||||
$bSynchronized = true;
|
||||
$sTip = "<p>".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."</p>";
|
||||
while($aData = $oReplicaSet->FetchAssoc())
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sLink)."</p>";
|
||||
$bCreated = true;
|
||||
$oCreatorTask = $aData['datasource'];
|
||||
$bCreatedByTask = true;
|
||||
}
|
||||
if ($bCreated)
|
||||
else
|
||||
{
|
||||
$bCreatedByTask = false;
|
||||
}
|
||||
if ($bCreatedByTask)
|
||||
{
|
||||
$sDeletePolicy = $aData['datasource']->Get('delete_policy');
|
||||
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()]['url'] = $sLink;
|
||||
$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>";
|
||||
foreach($aMasterSources as $aStruct)
|
||||
{
|
||||
@@ -2203,7 +2236,7 @@ EOF
|
||||
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
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
@@ -2211,7 +2244,7 @@ EOF
|
||||
$oExtensionInstance->OnDBDelete($this, self::$m_oCurrChange);
|
||||
}
|
||||
|
||||
return parent::DBDeleteTracked_Internal();
|
||||
return parent::DBDeleteTracked_Internal($oDeletionPlan);
|
||||
}
|
||||
|
||||
protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
|
||||
@@ -2223,6 +2256,9 @@ EOF
|
||||
public function DoCheckToWrite()
|
||||
{
|
||||
parent::DoCheckToWrite();
|
||||
|
||||
// Plugins
|
||||
//
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||
@@ -2231,11 +2267,37 @@ EOF
|
||||
$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()
|
||||
{
|
||||
parent::DoCheckToDelete();
|
||||
|
||||
// Plugins
|
||||
//
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||
@@ -2244,6 +2306,16 @@ EOF
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -169,9 +169,9 @@ class appUserPreferences extends DBObject
|
||||
* Overloading this function here to secure a fix done right before the release
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -620,7 +620,14 @@ class BulkChange
|
||||
//
|
||||
if ($oChange)
|
||||
{
|
||||
$oTargetObj->DBUpdateTracked($oChange);
|
||||
try
|
||||
{
|
||||
$oTargetObj->DBUpdateTracked($oChange);
|
||||
}
|
||||
catch(CoreException $e)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -649,12 +656,19 @@ class BulkChange
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(count($aChangedFields));
|
||||
|
||||
|
||||
// Optionaly record the results
|
||||
//
|
||||
if ($oChange)
|
||||
{
|
||||
$oTargetObj->DBUpdateTracked($oChange);
|
||||
try
|
||||
{
|
||||
$oTargetObj->DBUpdateTracked($oChange);
|
||||
}
|
||||
catch(CoreException $e)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -369,28 +369,28 @@ abstract class CMDBObject extends DBObject
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function DBDelete()
|
||||
public function DBDelete(&$oDeletionPlan = null)
|
||||
{
|
||||
if(!self::$m_oCurrChange)
|
||||
{
|
||||
throw new CoreException("DBDelete() could not be used here, please use DBDeleteTracked() instead");
|
||||
}
|
||||
return $this->DBDeleteTracked_Internal();
|
||||
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);
|
||||
|
||||
self::$m_oCurrChange = $oChange;
|
||||
$this->DBDeleteTracked_Internal();
|
||||
$this->DBDeleteTracked_Internal($oDeletionPlan);
|
||||
self::$m_oCurrChange = null;
|
||||
}
|
||||
|
||||
protected function DBDeleteTracked_Internal()
|
||||
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
|
||||
{
|
||||
$prevkey = $this->GetKey();
|
||||
$ret = parent::DBDelete();
|
||||
$ret = parent::DBDelete($oDeletionPlan);
|
||||
$this->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
|
||||
require_once('metamodel.class.php');
|
||||
require_once('deletionplan.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
protected $m_bSecurityIssue = null;
|
||||
protected $m_aCheckIssues = null;
|
||||
protected $m_aDeleteIssues = null;
|
||||
|
||||
@@ -153,7 +156,7 @@ abstract class DBObject
|
||||
protected function Reload()
|
||||
{
|
||||
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))
|
||||
{
|
||||
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
|
||||
* @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
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
@@ -765,6 +768,7 @@ abstract class DBObject
|
||||
$this->DoComputeValues();
|
||||
|
||||
$this->m_aCheckIssues = array();
|
||||
$aChanges = $this->ListChanges();
|
||||
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
|
||||
{
|
||||
@@ -787,6 +791,27 @@ abstract class DBObject
|
||||
// $res contains the error description
|
||||
$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()
|
||||
@@ -809,7 +834,7 @@ abstract class DBObject
|
||||
$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
|
||||
@@ -817,10 +842,57 @@ abstract class DBObject
|
||||
protected function DoCheckToDelete()
|
||||
{
|
||||
$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)
|
||||
{
|
||||
@@ -1079,7 +1151,8 @@ abstract class DBObject
|
||||
list($bRes, $aIssues) = $this->CheckToWrite();
|
||||
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
|
||||
@@ -1176,7 +1249,8 @@ abstract class DBObject
|
||||
list($bRes, $aIssues) = $this->CheckToWrite();
|
||||
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;
|
||||
@@ -1245,24 +1319,16 @@ abstract class DBObject
|
||||
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();
|
||||
|
||||
if (!MetaModel::DBIsReadOnly())
|
||||
{
|
||||
$this->DBDeleteInternal();
|
||||
foreach(MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL) as $sParentClass)
|
||||
{
|
||||
$this->DBDeleteSingleTable($sParentClass);
|
||||
}
|
||||
}
|
||||
|
||||
$this->AfterDelete();
|
||||
@@ -1271,9 +1337,53 @@ abstract class DBObject
|
||||
$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()
|
||||
@@ -1447,7 +1557,7 @@ abstract class DBObject
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
public function GetReferencingObjects()
|
||||
public function GetReferencingObjects($bAllowAllData = false)
|
||||
{
|
||||
$aDependentObjects = array();
|
||||
$aRererencingMe = MetaModel::EnumReferencingClasses(get_class($this));
|
||||
@@ -1460,6 +1570,10 @@ abstract class DBObject
|
||||
|
||||
$oSearch = new DBObjectSearch($sRemoteClass);
|
||||
$oSearch->AddCondition($sExtKeyAttCode, $this->GetKey(), '=');
|
||||
if ($bAllowAllData)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
$oSet = new CMDBObjectSet($oSearch);
|
||||
if ($oSet->Count() > 0)
|
||||
{
|
||||
@@ -1473,15 +1587,13 @@ abstract class DBObject
|
||||
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);
|
||||
$iThisId = $this->GetKey();
|
||||
|
||||
$iDeleteOption = $oDeletionPlan->AddToDelete($this, $iDeleteOption);
|
||||
|
||||
if (array_key_exists($sClass, $aVisited))
|
||||
{
|
||||
if (in_array($iThisId, $aVisited[$sClass]))
|
||||
@@ -1491,13 +1603,16 @@ abstract class DBObject
|
||||
}
|
||||
$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
|
||||
$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 ($aPotentialDeletes as $sRemoteExtKey => $aData)
|
||||
@@ -1512,44 +1627,12 @@ abstract class DBObject
|
||||
if ($oAttDef->IsNullAllowed())
|
||||
{
|
||||
// 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
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
else
|
||||
@@ -1584,6 +1667,11 @@ abstract class DBObject
|
||||
$oSet = $this->GetMasterReplica();
|
||||
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...
|
||||
$oReplica = $aData['replica'];
|
||||
$oSource = $aData['datasource'];
|
||||
@@ -1600,6 +1688,29 @@ abstract class DBObject
|
||||
}
|
||||
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
core/deletionplan.class.inc.php
Normal file
287
core/deletionplan.class.inc.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -163,6 +163,7 @@ class Dict
|
||||
{
|
||||
$sLocalizedFormat = self::S($sFormatCode);
|
||||
$aArguments = func_get_args();
|
||||
array_shift($aArguments);
|
||||
|
||||
if ($sLocalizedFormat == $sFormatCode)
|
||||
{
|
||||
@@ -170,7 +171,6 @@ class Dict
|
||||
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||
}
|
||||
|
||||
array_shift($aArguments);
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
}
|
||||
|
||||
|
||||
@@ -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: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:TheObjectCannotBeDeletedByUser_Source' => 'You <b>cannot delete the object</b> because it is owned by the external data source %1$s',
|
||||
));
|
||||
|
||||
//
|
||||
|
||||
181
pages/UI.php
181
pages/UI.php
@@ -28,102 +28,26 @@
|
||||
*/
|
||||
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)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
$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->DBDeleteTracked($oMyChange, null, $oDeletionPlan);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oObj->CheckToDelete($oDeletionPlan);
|
||||
}
|
||||
// Count of dependent objects (+ the current one)
|
||||
$iTotalTargets = $iTotalDelete + $iTotalReset;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
// 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'));
|
||||
}
|
||||
if ($bFoundManuelOp)
|
||||
if ($oDeletionPlan->FoundManualOperation())
|
||||
{
|
||||
throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseManualOpNeeded'));
|
||||
}
|
||||
if ($bFoundManualDel)
|
||||
if ($oDeletionPlan->FoundManualDelete())
|
||||
{
|
||||
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();
|
||||
foreach ($aTotalDeletedObjs as $sRemoteClass => $aDeletes)
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes)
|
||||
{
|
||||
foreach ($aDeletes as $iId => $aData)
|
||||
{
|
||||
$oToDelete = $aData['to_delete'];
|
||||
|
||||
if (isset($aData['requested_by_user']))
|
||||
if (isset($aData['requested_explicitely']))
|
||||
{
|
||||
$sMessage = Dict::S('UI:Delete:Deleted');
|
||||
}
|
||||
@@ -181,35 +97,27 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
|
||||
'object' => $oToDelete->GetHyperLink(),
|
||||
'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(
|
||||
'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']),
|
||||
);
|
||||
|
||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
|
||||
{
|
||||
$oToReset->Set($sRemoteExtKey, 0);
|
||||
$oToReset->DBUpdateTracked($oMyChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report automatic jobs
|
||||
//
|
||||
if ($iTotalTargets > 0)
|
||||
if ($oDeletionPlan->GetTargetCount() > 0)
|
||||
{
|
||||
if (count($aObjects) == 1)
|
||||
{
|
||||
@@ -241,17 +149,17 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
|
||||
// Explain what should be done
|
||||
//
|
||||
$aDisplayData = array();
|
||||
foreach ($aTotalDeletedObjs as $sRemoteClass => $aDeletes)
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes)
|
||||
{
|
||||
foreach ($aDeletes as $iId => $aData)
|
||||
{
|
||||
$oToDelete = $aData['to_delete'];
|
||||
$bAutoDel = $aData['auto_delete'];
|
||||
$bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO));
|
||||
if (array_key_exists('issue', $aData))
|
||||
{
|
||||
if ($bAutoDel)
|
||||
{
|
||||
if (isset($aData['requested_by_user']))
|
||||
if (isset($aData['requested_explicitely']))
|
||||
{
|
||||
$sConsequence = Dict::Format('UI:Delete:CannotDeleteBecause', $aData['issue']);
|
||||
}
|
||||
@@ -269,7 +177,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
|
||||
{
|
||||
if ($bAutoDel)
|
||||
{
|
||||
if (isset($aData['requested_by_user']))
|
||||
if (isset($aData['requested_explicitely']))
|
||||
{
|
||||
$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))
|
||||
{
|
||||
$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']);
|
||||
}
|
||||
$aDisplayData[] = array(
|
||||
'class' => MetaModel::GetName(get_class($oToReset)),
|
||||
'object' => $oToReset->GetHyperLink(),
|
||||
'class' => MetaModel::GetName(get_class($oToUpdate)),
|
||||
'object' => $oToUpdate->GetHyperLink(),
|
||||
'consequence' => $sConsequence,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$iInducedDeletions = $iTotalTargets - count($aObjects);
|
||||
if ($iInducedDeletions > 0)
|
||||
$iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects);
|
||||
if ($iImpactedIndirectly > 0)
|
||||
{
|
||||
if (count($aObjects) == 1)
|
||||
{
|
||||
$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
|
||||
{
|
||||
$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'));
|
||||
}
|
||||
|
||||
if (($iInducedDeletions > 0) || $bFoundStopper)
|
||||
if (($iImpactedIndirectly > 0) || $oDeletionPlan->FoundStopper())
|
||||
{
|
||||
$aDisplayConfig = array();
|
||||
$aDisplayConfig['class'] = array('label' => 'Class', 'description' => '');
|
||||
@@ -335,13 +243,13 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
|
||||
$oP->table($aDisplayConfig, $aDisplayData);
|
||||
}
|
||||
|
||||
if ($bFoundStopper)
|
||||
if ($oDeletionPlan->FoundStopper())
|
||||
{
|
||||
if ($bFoundSecurityIssue)
|
||||
if ($oDeletionPlan->FoundSecurityIssue())
|
||||
{
|
||||
$oP->p(Dict::S('UI:Delete:SorryDeletionNotAllowed'));
|
||||
}
|
||||
elseif ($bFoundManualDel)
|
||||
elseif ($oDeletionPlan->FoundManualOperation())
|
||||
{
|
||||
$oP->p(Dict::S('UI:Delete:PleaseDoTheManualOperations'));
|
||||
}
|
||||
@@ -390,7 +298,6 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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())));
|
||||
// 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())));
|
||||
// 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())));
|
||||
|
||||
// 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
|
||||
// Search criteria
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!$this->IsNew())
|
||||
@@ -406,13 +419,13 @@ EOF
|
||||
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()))
|
||||
{
|
||||
return OPT_ATT_READONLY;
|
||||
}
|
||||
return parent::GetAttributeFlags($sAttCode);
|
||||
return parent::GetAttributeFlags($sAttCode, $aReasons);
|
||||
}
|
||||
|
||||
public function UpdateObject($sFormPrefix = '')
|
||||
@@ -690,6 +703,7 @@ EOF
|
||||
|
||||
$oStatLog->DBInsertTracked($oMyChange);
|
||||
|
||||
self::$m_oCurrentTask = $this;
|
||||
try
|
||||
{
|
||||
$this->DoSynchronize($oLastFullLoadStartDate, $oMyChange, $oStatLog);
|
||||
@@ -713,6 +727,7 @@ EOF
|
||||
$oStatLog->Set('last_error', $e->getMessage());
|
||||
$oStatLog->DBUpdateTracked($oMyChange);
|
||||
}
|
||||
self::$m_oCurrentTask = null;
|
||||
return $oStatLog;
|
||||
}
|
||||
|
||||
@@ -998,7 +1013,7 @@ class SynchroAttribute extends cmdbAbstractObject
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
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 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())));
|
||||
@@ -1113,7 +1128,7 @@ class SynchroLog extends DBObject
|
||||
MetaModel::Init_Params($aParams);
|
||||
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 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("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())));
|
||||
@@ -1244,7 +1259,7 @@ class SynchroReplica extends DBObject implements iDisplay
|
||||
MetaModel::Init_Params($aParams);
|
||||
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 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,
|
||||
// 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'));
|
||||
$sTable = $oDataSource->GetDataTable();
|
||||
|
||||
Reference in New Issue
Block a user