diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index a29a55b15..1b72fe87d 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -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 = "
".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."
";
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 .= "".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sLink)."
";
- $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 .= "".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sLink)."
";
+ $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 = "".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sTaskUrl)."
";
+ }
+ else
+ {
+ $sTip = "".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."
";
+ }
+ if ($bCanBeDeletedByTask)
+ {
+ $sTip .= "".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."
";
+ }
+ }
+ else
+ {
+ $sTip = "".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."
";
+ }
+
$sTip .= "".Dict::S('Core:Synchro:ListOfDataSources')."
";
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');
+ }
}
/**
diff --git a/application/user.preferences.class.inc.php b/application/user.preferences.class.inc.php
index 591e5e563..3d40db10a 100644
--- a/application/user.preferences.class.inc.php
+++ b/application/user.preferences.class.inc.php
@@ -169,9 +169,9 @@ class appUserPreferences extends DBObject
* Overloading this function here to secure a fix done right before the release
* 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);
}
}
?>
diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php
index 1a8de4b92..39c81514f 100644
--- a/core/bulkchange.class.inc.php
+++ b/core/bulkchange.class.inc.php
@@ -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
diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
index ff573d9e4..090a49373 100644
--- a/core/cmdbobject.class.inc.php
+++ b/core/cmdbobject.class.inc.php
@@ -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;
}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 8773bdeb2..1e7d7c749 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -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...
+ }
}
diff --git a/core/deletionplan.class.inc.php b/core/deletionplan.class.inc.php
new file mode 100644
index 000000000..3064e5172
--- /dev/null
+++ b/core/deletionplan.class.inc.php
@@ -0,0 +1,287 @@
+
+ * @author Romain Quetiez
+ * @author Denis Flaven
+ * @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;
+ }
+ }
+}
+?>
diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php
index 02c43a4c3..eabe49435 100644
--- a/core/dict.class.inc.php
+++ b/core/dict.class.inc.php
@@ -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);
}
diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php
index 51bb641a6..1a45b71b2 100644
--- a/dictionaries/dictionary.itop.core.php
+++ b/dictionaries/dictionary.itop.core.php
@@ -560,8 +560,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:Synchro:ThisObjectIsSynchronized' => 'This object is synchronized with an external data source',
'Core:Synchro:TheObjectWasCreatedBy_Source' => 'The object was created by the external data source %1$s',
'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'The object can be deleted by the external data source %1$s',
-
-
+ 'Core:Synchro:TheObjectCannotBeDeletedByUser_Source' => 'You cannot delete the object because it is owned by the external data source %1$s',
));
//
diff --git a/pages/UI.php b/pages/UI.php
index 22c148467..2fef75577 100644
--- a/pages/UI.php
+++ b/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("".Dict::Format('UI:Title:BulkDeletionOf_Count_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass))."
\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)
}
}
}
-
}
/**
diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php
index 7fe96acf3..de73db23e 100644
--- a/synchro/synchrodatasource.class.inc.php
+++ b/synchro/synchrodatasource.class.inc.php
@@ -76,20 +76,33 @@ class SynchroDataSource extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeLinkedSet("attribute_list", array("linked_class"=>"SynchroAttribute", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
// 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();