From 8ff40fb4b8bd096de394b4738d3f82e6855ec33d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 8 Apr 2011 11:29:25 +0000 Subject: [PATCH] Deletion: entirely reviewed to take into account the objects being synchronized by an external data source, or the plugins constraints. SVN:trunk[1194] --- application/cmdbabstract.class.inc.php | 102 ++++++-- application/user.preferences.class.inc.php | 4 +- core/bulkchange.class.inc.php | 20 +- core/cmdbobject.class.inc.php | 12 +- core/dbobject.class.php | 251 +++++++++++++----- core/deletionplan.class.inc.php | 287 +++++++++++++++++++++ core/dict.class.inc.php | 2 +- dictionaries/dictionary.itop.core.php | 3 +- pages/UI.php | 181 ++++--------- synchro/synchrodatasource.class.inc.php | 31 ++- 10 files changed, 649 insertions(+), 244 deletions(-) create mode 100644 core/deletionplan.class.inc.php 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();