N°7080 - EVENT_DB_LINKS_CHANGED not fired when deleting a user

This commit is contained in:
Eric Espie
2024-01-04 15:39:38 +01:00
parent 34499a719f
commit 1de390a24d
3 changed files with 171 additions and 64 deletions

View File

@@ -4591,6 +4591,7 @@ HTML;
public function DBUpdate()
{
$this->LogCRUDEnter(__METHOD__);
$res = 0;
try {
if (count($this->ListChanges()) === 0) {
@@ -4651,6 +4652,7 @@ HTML;
if (static::IsCrudStackEmpty()) {
// Avoid signaling the current object that links were modified
static::RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey());
$this->LogCRUDDebug(__METHOD__, var_export(self::$aObjectsAwaitingEventDbLinksChanged, true));
static::FireEventDbLinksChangedForAllObjects();
}
}
@@ -4659,6 +4661,11 @@ HTML;
return $oDeletionPlan;
}
protected function PostDeleteActions(): void
{
parent::PostDeleteActions();
}
/**
* @deprecated 3.1.1 3.2.0 N°6967 We will have only one DBDelete method in the future
*/
@@ -5986,13 +5993,16 @@ JS
}
$sTargetObjectId = $this->Get($sExternalKeyAttCode);
$sTargetClass = $oAttDef->GetTargetClass();
if ($sTargetObjectId > 0) {
self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sTargetObjectId);
$this->LogCRUDDebug(__METHOD__, "Add $sTargetClass:$sTargetObjectId for DBLINKS_CHANGED");
self::RegisterObjectAwaitingEventDbLinksChanged($sTargetClass, $sTargetObjectId);
}
$sPreviousTargetObjectId = $aPreviousValues[$sExternalKeyAttCode] ?? 0;
if ($sPreviousTargetObjectId > 0) {
self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sPreviousTargetObjectId);
$this->LogCRUDDebug(__METHOD__, "Add $sTargetClass:$sPreviousTargetObjectId for DBLINKS_CHANGED");
self::RegisterObjectAwaitingEventDbLinksChanged($sTargetClass, $sPreviousTargetObjectId);
}
}
}
@@ -6042,7 +6052,12 @@ JS
$sClass = get_class($this);
$sId = $this->GetKey();
self::FireEventDbLinksChangedForClassId($sClass, $sId);
$bIsObjectAwaitingEventDbLinksChanged = self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
if (false === $bIsObjectAwaitingEventDbLinksChanged) {
return;
}
self::FireEventDbLinksChangedForObject($this);
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
}
/**
@@ -6074,9 +6089,15 @@ JS
// We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener
// By disabling the event to be fired, we can remove the current object from the attribute !
$oObject = MetaModel::GetObject($sClass, $sId, false);
self::FireEventDbLinksChangedForObject($oObject);
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
}
private static function FireEventDbLinksChangedForObject(DBObject $oObject)
{
self::SetEventDBLinksChangedBlocked(true);
// N°6408 The object can have been deleted
if (!is_null($oObject)) {
self::SetEventDBLinksChangedBlocked(true);
MetaModel::StartReentranceProtection($oObject);
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
MetaModel::StopReentranceProtection($oObject);
@@ -6084,7 +6105,6 @@ JS
$oObject->DBUpdate();
}
}
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
}

View File

@@ -763,6 +763,18 @@ abstract class DBObject implements iDisplay
$this->Set($sAttCode, $sValue);
}
/**
* @return void
* @throws \ReflectionException
*/
protected function PostDeleteActions(): void
{
$this->FireEventAfterDelete();
$oKPI = new ExecutionKPI();
$this->AfterDelete();
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
}
/**
* Compute (and optionally start) the StopWatches deadlines
*
@@ -4203,10 +4215,7 @@ abstract class DBObject implements iDisplay
}
}
$this->FireEventAfterDelete();
$oKPI = new ExecutionKPI();
$this->AfterDelete();
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
$this->PostDeleteActions();
// - Trigger for object pointing to the current object
$this->ActivateOnObjectUpdateTriggersForTargetObjects();
@@ -5386,72 +5395,65 @@ abstract class DBObject implements iDisplay
}
$sClass = get_class($this);
$iThisId = $this->GetKey();
$iRootDeleteOption = $iDeleteOption;
$oDeletionPlan->AddToDelete($this, $iDeleteOption);
try {
if (array_key_exists($sClass, $aVisited)) {
if (in_array($iThisId, $aVisited[$sClass])) {
return;
}
}
$aVisited[$sClass] = $iThisId;
if (array_key_exists($sClass, $aVisited))
{
if (in_array($iThisId, $aVisited[$sClass]))
{
if ($iDeleteOption == DEL_MANUAL) {
// Stop the recursion here
return;
}
}
$aVisited[$sClass] = $iThisId;
// Check the node itself
$this->m_aDeleteIssues = array(); // Ok
$this->FireEventCheckToDelete($oDeletionPlan);
$this->DoCheckToDelete($oDeletionPlan);
$this->CheckToWriteForTargetObjects(true);
$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
if ($iDeleteOption == DEL_MANUAL)
{
// Stop the recursion here
return;
}
// Check the node itself
$this->m_aDeleteIssues = array(); // Ok
$this->FireEventCheckToDelete($oDeletionPlan);
$this->DoCheckToDelete($oDeletionPlan);
$this->CheckToWriteForTargetObjects(true);
$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
// Getting and setting time limit are not symmetric:
// www.php.net/manual/fr/function.set-time-limit.php#72305
$iPreviousTimeLimit = ini_get('max_execution_time');
// Getting and setting time limit are not symmetric:
// www.php.net/manual/fr/function.set-time-limit.php#72305
$iPreviousTimeLimit = ini_get('max_execution_time');
foreach ($aDependentObjects as $aPotentialDeletes) {
foreach ($aPotentialDeletes as $aData) {
set_time_limit(intval($iLoopTimeLimit));
foreach ($aDependentObjects as $aPotentialDeletes)
{
foreach ($aPotentialDeletes as $aData)
{
set_time_limit(intval($iLoopTimeLimit));
/** @var \AttributeExternalKey $oAttDef */
$oAttDef = $aData['attribute'];
$iDeletePropagationOption = $oAttDef->GetDeletionPropagationOption();
/** @var \DBObjectSet $oDepSet */
$oDepSet = $aData['objects'];
$oDepSet->Rewind();
while ($oDependentObj = $oDepSet->fetch())
{
if ($oAttDef->IsNullAllowed())
{
// Optional external key, list to reset
if (($iDeletePropagationOption == DEL_MOVEUP) && ($oAttDef->IsHierarchicalKey()))
{
// Move the child up one level i.e. set the same parent as the current object
$iParentId = $this->Get($oAttDef->GetCode());
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef, $iParentId);
/** @var \AttributeExternalKey $oAttDef */
$oAttDef = $aData['attribute'];
$iDeletePropagationOption = $oAttDef->GetDeletionPropagationOption();
/** @var \DBObjectSet $oDepSet */
$oDepSet = $aData['objects'];
$oDepSet->Rewind();
while ($oDependentObj = $oDepSet->fetch()) {
if ($oAttDef->IsNullAllowed()) {
// Optional external key, list to reset
if (($iDeletePropagationOption == DEL_MOVEUP) && ($oAttDef->IsHierarchicalKey())) {
// Move the child up one level i.e. set the same parent as the current object
$iParentId = $this->Get($oAttDef->GetCode());
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef, $iParentId);
} else {
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef);
}
} else {
// Mandatory external key, list to delete
$oDependentObj->MakeDeletionPlan($oDeletionPlan, $aVisited, $iDeletePropagationOption);
}
else
{
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef);
}
}
else
{
// Mandatory external key, list to delete
$oDependentObj->MakeDeletionPlan($oDeletionPlan, $aVisited, $iDeletePropagationOption);
}
}
}
}
finally {
// Bug N°7080 Root object is deleted last for EVENT_DB_LINKS_CHANGED
$oDeletionPlan->AddToDelete($this, $iRootDeleteOption);
}
set_time_limit(intval($iPreviousTimeLimit));
}

View File

@@ -14,12 +14,22 @@ use DBObject;
use DBObject\MockDBObjectWithCRUDEventListener;
use DBObjectSet;
use DBSearch;
use IssueLog;
use lnkFunctionalCIToTicket;
use lnkPersonToTeam;
use MetaModel;
use ormLinkSet;
use Person;
use Server;
use Team;
use UserRequest;
use utils;
use const EVENT_DB_AFTER_DELETE;
use const EVENT_DB_AFTER_WRITE;
use const EVENT_DB_BEFORE_WRITE;
use const EVENT_DB_CHECK_TO_DELETE;
use const EVENT_DB_CHECK_TO_WRITE;
use const EVENT_DB_COMPUTE_VALUES;
use const EVENT_DB_LINKS_CHANGED;
class CRUDEventTest extends ItopDataTestCase
@@ -30,12 +40,32 @@ class CRUDEventTest extends ItopDataTestCase
// Count the events by name
private static array $aEventCalls = [];
private static int $iEventCalls = 0;
private static string $sLogFile = 'log/test_error_CRUDEventTest.log';
protected function setUp(): void
{
static::$aEventCalls = [];
static::$iEventCalls = 0;
static::CleanCallCount();
parent::setUp();
static::$DEBUG_UNIT_TEST = false;
if (static::$DEBUG_UNIT_TEST) {
echo "--- logging in ".APPROOT.static::$sLogFile."\n\n";
@unlink(APPROOT.static::$sLogFile);
IssueLog::Enable(APPROOT.static::$sLogFile);
$oConfig = utils::GetConfig();
$oConfig->Set('log_level_min', ['DMCRUD' => 'Trace']);
}
}
protected function tearDown(): void
{
if (is_file(APPROOT.static::$sLogFile)) {
$sLog = file_get_contents(APPROOT.static::$sLogFile);
echo "--- error.log\n$sLog\n\n";
@unlink(APPROOT.static::$sLogFile);
}
parent::tearDown();
}
public static function IncrementCallCount(string $sEvent)
@@ -44,6 +74,13 @@ class CRUDEventTest extends ItopDataTestCase
self::$iEventCalls++;
}
public static function CleanCallCount()
{
self::$aEventCalls = [];
self::$iEventCalls = 0;
}
/**
* Check that the 3 events EVENT_DB_COMPUTE_VALUES, EVENT_DB_CHECK_TO_WRITE and EVENT_DB_AFTER_WRITE are called on insert
*
@@ -343,6 +380,54 @@ class CRUDEventTest extends ItopDataTestCase
$this->assertEquals(16, self::$iEventCalls);
}
public function testDBDeleteUR()
{
// Prepare the link set
$sLinkedClass = lnkFunctionalCIToTicket::class;
$aLinkedObjectsArray = [];
$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
$oLinkSet = new ormLinkSet(UserRequest::class, 'functionalcis_list', $oSet);
// Create the 3 servers
for ($i = 0; $i < 3; $i++) {
$oServer = $this->CreateServer($i);
$this->assertIsObject($oServer);
// Add the person to the link
$oLink = MetaModel::NewObject(lnkFunctionalCIToTicket::class, ['functionalci_id' => $oServer->GetKey()]);
$oLinkSet->AddItem($oLink);
}
$this->debug("\n-------------> Insert Starts HERE\n");
$oEventReceiver = new CRUDEventReceiver($this);
$oEventReceiver->RegisterCRUDListeners();
$oUserRequest = MetaModel::NewObject(UserRequest::class, array_merge($this->GetUserRequestParams(0), ['functionalcis_list' => $oLinkSet]));
$oUserRequest->DBInsert();
$this->assertIsObject($oUserRequest);
// 1 insert for UserRequest, 3 insert for lnkFunctionalCIToTicket
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_COMPUTE_VALUES]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_CHECK_TO_WRITE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_BEFORE_WRITE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_AFTER_WRITE]);
$this->assertEquals(1, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
$this->assertEquals(17, self::$iEventCalls);
$this->debug("\n-------------> Delete Starts HERE\n");
$oEventReceiver->CleanCallbacks();
self::CleanCallCount();
$oUserRequest->DBDelete();
// 1 delete for UserRequest, 3 delete for lnkFunctionalCIToTicket
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_CHECK_TO_DELETE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_AFTER_DELETE]);
$this->assertEquals(1, self::$aEventCalls[EVENT_DB_LINKS_CHANGED] ?? 0);
$this->assertEquals(9, self::$iEventCalls);
}
/**
* The test creates a team containing one Person.
* During the insert of the lnkPersonToTeam a modification is done on the link,