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

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

View File

@@ -102,15 +102,16 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$oBlock->Display($oPage, -1);
// 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');
}
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>";
}
$sSynchroIcon = '';
if ($bSynchronized)
{
$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');
}
}
/**

View File

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

View File

@@ -619,9 +619,16 @@ class BulkChange
// Optionaly record the results
//
if ($oChange)
{
try
{
$oTargetObj->DBUpdateTracked($oChange);
}
catch(CoreException $e)
{
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue($e->getMessage());
}
}
}
else
{
@@ -653,9 +660,16 @@ class BulkChange
// Optionaly record the results
//
if ($oChange)
{
try
{
$oTargetObj->DBUpdateTracked($oChange);
}
catch(CoreException $e)
{
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue($e->getMessage());
}
}
}
else
{

View File

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

View File

@@ -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;
}
// 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.
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(&$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...
}
}

View File

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

View File

@@ -163,6 +163,7 @@ class Dict
{
$sLocalizedFormat = self::S($sFormatCode);
$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);
}

View File

@@ -560,8 +560,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:Synchro:ThisObjectIsSynchronized' => 'This object is synchronized with an external data source',
'Core:Synchro: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',
));
//

View File

@@ -28,103 +28,27 @@
*/
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
$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();
}
$oDeletionPlan = new DeletionPlan();
foreach($aObjects as $oObj)
{
// Evaluate the impact on the DB integrity
if ($bDeleteConfirmed)
{
// Prepare the change reporting
//
$aDeletedObjs = array();
$aResetedObjs = array();
$oObj->GetDeletionScheme($aDeletedObjs, $aResetedObjs);
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$sUserString = CMDBChange::GetCurrentUserName();
$oMyChange->Set("userinfo", $sUserString);
$oMyChange->DBInsert();
// Evaluate feasibility (user access control)
//
foreach ($aDeletedObjs as $sRemoteClass => $aDeletes)
{
$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;
}
}
}
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));
$oObj->DBDeleteTracked($oMyChange, null, $oDeletionPlan);
}
else
{
$aTotalResetedObjs[$sRemoteClass][$iId]['to_reset'] = $oToReset;
$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)
}
}
}
}
/**

View File

@@ -76,20 +76,33 @@ class SynchroDataSource extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeLinkedSet("attribute_list", array("linked_class"=>"SynchroAttribute", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
// 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();