diff --git a/application/datamodel.application.xml b/application/datamodel.application.xml
index 25653c760..8b043a29f 100644
--- a/application/datamodel.application.xml
+++ b/application/datamodel.application.xml
@@ -188,300 +188,347 @@
An object insert in the database has been requested. All changes to the object will be persisted automatically.
+
+ cmdbAbstractObject
+
DBObject::OnInsert
-
-
+
+
The object inserted
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object is about to be inserted in the database (no change possible)
+
+ cmdbAbstractObject
+
DBObject::OnInsert
-
-
+
+
The object inserted
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object has been inserted into the database (but not reloaded). All changes to the object will be persisted automatically.
+
+ cmdbAbstractObject
+
DBObject::AfterInsert
-
-
+
+
The object inserted
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object update has been requested. All changes to the object will be persisted automatically.
+
+ cmdbAbstractObject
+
DBObject::OnUpdate, DBObject::DoComputeValues
-
-
+
+
The object updated
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object is about to be updated in the database (no change possible)
+
+ cmdbAbstractObject
+
DBObject::OnUpdate
-
-
+
+
The object updated
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object has been updated into the database and reloaded. All changes to the object will be persisted automatically.
+
+ cmdbAbstractObject
+
DBObject::AfterUpdate
-
-
+
+
The object updated
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object is about to be deleted in the database
+
+ cmdbAbstractObject
+
DBObject::OnDelete
-
-
+
+
The object deleted
DBObject
-
-
+
+
Debug string
string
-
-
+
+
An object has been deleted into the database
+
+ cmdbAbstractObject
+
DBObject::AfterDelete
-
-
+
+
The object deleted
DBObject
-
-
+
+
Debug string
string
-
-
+
+
A stimulus is about to be applied to an object
-
-
+
+ cmdbAbstractObject
+
+
+
The object where the stimulus is targeted
DBObject
-
-
+
+
Current stimulus applied
string
-
-
+
+
Object previous state
string
-
-
+
+
Object new state
string
-
-
+
+
The object must be saved in the database
boolean
-
-
+
+
Debug string
string
-
-
+
+
A stimulus has been applied to an object
-
-
+
+ cmdbAbstractObject
+
+
+
The object where the stimulus is targeted
DBObject
-
-
+
+
Current stimulus applied
string
-
-
+
+
Object previous state
string
-
-
+
+
Object new state
string
-
-
+
+
The object is asked to be saved in the database
boolean
-
-
+
+
Debug string
string
-
-
+
+
A stimulus has failed
-
-
+
+ cmdbAbstractObject
+
+
+
The action that failed to apply the stimulus
string
-
-
+
+
The object where the stimulus is targeted
DBObject
-
-
+
+
Current stimulus applied
string
-
-
+
+
Object previous state
string
-
-
+
+
Object new state
string
-
-
+
+
The object must be saved in the database
boolean
-
-
+
+
Debug string
string
-
-
+
+
An object has been re-loaded from the database
-
-
+
+ cmdbAbstractObject
+
+
+
The object re-loaded
DBObject
-
-
+
+
Debug string
string
-
-
+
+
Check an object before it is written into the database (no change possible)
+
+ cmdbAbstractObject
+
cmdbAbstractObject::DoCheckToWrite
-
-
+
+
The object to check
DBObject
-
-
+
+
Array of strings where all the errors found during the object checking are added
array
-
-
+
+
Debug string
string
-
-
+
+
Check an object before it is deleted from the database (no change possible)
+
+ cmdbAbstractObject
+
cmdbAbstractObject::DoCheckToDelete
-
-
+
+
The object to check
DBObject
-
-
+
+
Array of strings where all the errors found during the object checking are added
array
-
-
+
+
Debug string
string
-
-
+
+
A document has been downloaded from the GUI
-
-
+
+ ormDocument
+
+
+
The object containing the document
DBObject
-
-
+
+
The document downloaded
ormDocument
-
-
+
+
Debug string
string
-
-
+
+
The current page is completely displayed
- Class hierarchy of the displayed page
-
-
+
+ CLIPage
+ WebPage
+
+
+
The page displayed
Page
-
-
+
+
Debug string
string
-
-
+
+
Inform the listeners about the connection states
-
-
-
+
+
The login step result code (LoginWebPage::EXIT_CODE_...)
integer
-
-
+
+
Current login state (LoginWebPage::LOGIN_STATE_CONNECTED...)
string
-
-
+
+
diff --git a/core/designdocument.class.inc.php b/core/designdocument.class.inc.php
index a9448859d..4384c2a7d 100644
--- a/core/designdocument.class.inc.php
+++ b/core/designdocument.class.inc.php
@@ -26,8 +26,8 @@
namespace Combodo\iTop;
-use \DOMDocument;
-use \DOMFormatException;
+use DOMDocument;
+use DOMFormatException;
/**
* Class \Combodo\iTop\DesignDocument
@@ -175,6 +175,26 @@ class DesignElement extends \DOMElement
return $this->ownerDocument->GetNodes($sXPath, $this);
}
+ public static function ToArray(DesignElement $oNode)
+ {
+ $aRes = [];
+
+ if ($oNode->GetNodes('./*')->length == 0) {
+ return $oNode->GetText('');
+ }
+ foreach ($oNode->GetNodes('./*') as $oSubNode) {
+ /** @var \Combodo\iTop\DesignElement $oSubNode */
+ $aSubArray = DesignElement::ToArray($oSubNode);
+ if ($oSubNode->hasAttribute('id')) {
+ $aRes[$oSubNode->getAttribute('id')] = $aSubArray;
+ } else {
+ $aRes[$oSubNode->tagName] = $aSubArray;
+ }
+ }
+
+ return $aRes;
+ }
+
/**
* Create an HTML representation of the DOM, for debugging purposes
*
diff --git a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml
index ebaed8844..07cb4c904 100755
--- a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml
+++ b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml
@@ -291,34 +291,40 @@
An attachment has been updated in database.
Attachment::AfterUpdate
-
-
+
+ Attachment
+
+
+
The attachment updated
DBObject
-
-
+
+
Array of all the attributes changed
array
-
-
+
+
Debug string
string
-
-
+
+
An attachment has been deleted from database.
Attachment::AfterDelete
-
-
+
+ Attachment
+
+
+
The attachment deleted
DBObject
-
-
+
+
Debug string
string
-
-
+
+
diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php
index 9b88bc07e..e8fadd120 100644
--- a/dictionaries/en.dictionary.itop.ui.php
+++ b/dictionaries/en.dictionary.itop.ui.php
@@ -804,6 +804,11 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
'UI:Schema:DisplaySelector/Code' => 'Code~~',
'UI:Schema:Attribute/Filter' => 'Filter~~',
'UI:Schema:DefaultNullValue' => 'Default null : "%1$s"~~',
+ 'UI:Schema:Events' => 'Events',
+ 'UI:Schema:Events:Defined' => 'Defined events',
+ 'UI:Schema:Events:NoEvent' => 'No event defined',
+ 'UI:Schema:Events:Listeners' => 'Event listeners',
+ 'UI:Schema:Events:NoListener' => 'No event listener',
'UI:LinksWidget:Autocomplete+' => 'Type the first 3 characters...',
'UI:Edit:SearchQuery' => 'Select a predefined query',
'UI:Edit:TestQuery' => 'Test query',
diff --git a/pages/schema.php b/pages/schema.php
index eb9c1fd43..a807ddf8c 100644
--- a/pages/schema.php
+++ b/pages/schema.php
@@ -12,6 +12,7 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockF
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentWithSideContent;
+use Combodo\iTop\Service\EventService;
require_once('../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
@@ -264,7 +265,58 @@ function DisplayTriggers($oPage, $sClass)
cmdbAbstractObject::DisplaySet($oPage, $oSet, array('block_id' => 'triggers'));
}
+function DisplayEvents(WebPage $oPage, $sClass)
+{
+ $aEvents = EventService::GetEventsByClass($sClass);
+ $aColumns = [
+ 'event' => ['label' => 'Event'],
+ 'description' => ['label' => 'Description'],
+ ];
+ $aRows = [];
+ foreach ($aEvents as $sEvent => $aEventInfo) {
+ $aDesc = $aEventInfo['description'];
+ $aRows[] = [
+ 'event' => $sEvent,
+ 'description' => $aDesc['description'] ?? '',
+ ];
+ }
+ $oTable = DataTableUIBlockFactory::MakeForStaticData(Dict::S('UI:Schema:Events:Defined'), $aColumns, $aRows);
+ $oPage->AddSubBlock($oTable);
+ $oObject = MetaModel::NewObject($sClass);
+ $aSources = [$oObject->GetObjectUniqId()];
+ foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
+ $aSources[] = $sParentClass;
+ }
+ $aListeners = [];
+ foreach (array_keys($aEvents) as $sEvent) {
+ $aListeners = array_merge($aListeners, EventService::GetListeners($sEvent, $aSources));
+ }
+ $aColumns = [
+ 'event' => ['label' => 'Event'],
+ 'listener' => ['label' => 'Listener'],
+ 'priority' => ['label' => 'Priority'],
+ 'module' => ['label' => 'Module'],
+ ];
+ $aRows = [];
+ foreach ($aListeners as $aListener) {
+ if (is_object($aListener['callback'][0])) {
+ $sListener = get_class($aListener['callback'][0]).'->'.$aListener['callback'][1].'(Combodo\iTop\Service\EventData $oEventData)';
+ } else {
+ $sListener = $aListener['callback'][0].'::'.$aListener['callback'][1].'(Combodo\iTop\Service\EventData $oEventData)';
+ }
+ $aRows[] = [
+ 'event' => $aListener['event'],
+ 'listener' => $sListener,
+ 'priority' => $aListener['priority'],
+ 'module' => $aListener['module'],
+ ];
+ }
+
+ $oTable = DataTableUIBlockFactory::MakeForStaticData(Dict::S('UI:Schema:Events:Listeners'), $aColumns, $aRows);
+ $oPage->AddSubBlock($oTable);
+
+}
/**
* Display the list of classes from the business model
*/
@@ -1061,6 +1113,9 @@ EOF
$oPage->SetCurrentTab('UI:Schema:Triggers');
DisplayTriggers($oPage, $sClass);
+ $oPage->SetCurrentTab('UI:Schema:Events');
+ DisplayEvents($oPage, $sClass);
+
$oPage->SetCurrentTab();
$oPage->SetCurrentTabContainer();
}
diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php
index 417c2f064..9928d1f95 100644
--- a/setup/compiler.class.inc.php
+++ b/setup/compiler.class.inc.php
@@ -1078,23 +1078,13 @@ EOF
protected function CompileEvent(DesignElement $oEvent, string $sModuleName)
{
$sName = $oEvent->getAttribute('id');
- $oDescription = $oEvent->GetOptionalElement('description');
- $sDescription = empty($oDescription) ? '' : $oDescription->GetText('');
- $aArguments = [];
- foreach ($oEvent->GetNodes('./arguments/argument') as $oArgumentNode) {
- $aArg = [];
- $sArgId = $oArgumentNode->getAttribute('id');
- $oArgDesc = $oArgumentNode->GetOptionalElement('description');
- $aArg['description'] = empty($oArgDesc) ? '' : $oArgDesc->GetText('');
- $oArgType = $oArgumentNode->GetOptionalElement('type');
- $aArg['type'] = empty($oArgType) ? '' : $oArgType->GetText('');
- $aArguments[$sArgId] = $aArg;
- }
- $sArguments = var_export($aArguments, true);
+ $aEventDescription = DesignElement::ToArray($oEvent);
+
+ $sDescription = var_export($aEventDescription, true);
$sConstant = $sName;
$sOutput = "define('$sConstant', '$sName');\n";
- $sOutput .= "Combodo\iTop\Service\EventService::RegisterEvent('$sName', '$sDescription', $sArguments, '$sModuleName');\n";
+ $sOutput .= "Combodo\iTop\Service\EventService::RegisterEvent('$sName', $sDescription, '$sModuleName');\n";
return $sOutput;
}
@@ -1329,7 +1319,7 @@ EOF
}
$sListenerPriority = (float)($oListener->GetChildText('priority', '0'));
- $sEvents .= "\n Combodo\iTop\Service\EventService::RegisterListener(\"$sEventName\", $sEventListener, \$this->m_sObjectUniqId, \"$sListenerId\", null, $sListenerPriority);";
+ $sEvents .= "\n Combodo\iTop\Service\EventService::RegisterListener(\"$sEventName\", $sEventListener, \$this->m_sObjectUniqId, \"$sListenerId\", null, $sListenerPriority, '$sModuleRelativeDir');";
}
}
@@ -3698,7 +3688,7 @@ EOF;
$sEventSource = $aHook['source'];
$sContext = $aHook['context'];
$sPriority = $aHook['priority'];
- $sRegister .= "\nCombodo\iTop\Service\EventService::RegisterListener(\"$sEventName\", '$sClassName::$sCallback', $sEventSource, null, $sContext, $sPriority);";
+ $sRegister .= "\nCombodo\iTop\Service\EventService::RegisterListener(\"$sEventName\", '$sClassName::$sCallback', $sEventSource, null, $sContext, $sPriority, '$sModuleId');";
$sCallbackFct = $aHook['content'];
$sMethods .= "\n {$sCallbackFct}\n\n";
}
diff --git a/sources/Application/Service/EventService.php b/sources/Application/Service/EventService.php
index 3f09ea672..3bb1f4bb2 100644
--- a/sources/Application/Service/EventService.php
+++ b/sources/Application/Service/EventService.php
@@ -12,13 +12,13 @@ use Exception;
use ExecutionKPI;
use IssueLog;
use LogChannels;
+use ReflectionClass;
class EventService
{
public static $aEventListeners = [];
private static $iEventIdCounter = 0;
private static $aEventDescription = [];
- Private static $aReentranceProtection = [];
/**
* Register a callback for a specific event
@@ -33,20 +33,24 @@ class EventService
* @return string Id of the registration (used for unregistering)
*
*/
- public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, $aCallbackData = [], $context = null, float $fPriority = 0.0): string
+ public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
{
- is_callable($callback, false, $sName);
+ if (!is_callable($callback, false, $sName)) {
+ return false;
+ }
$aEventCallbacks = self::$aEventListeners[$sEvent] ?? [];
$sId = 'event_'.self::$iEventIdCounter++;
$aEventCallbacks[] = array(
'id' => $sId,
+ 'event' => $sEvent,
'callback' => $callback,
'source' => $sEventSource,
'name' => $sName,
'data' => $aCallbackData,
'context' => $context,
'priority' => $fPriority,
+ 'module' => $sModuleId,
);
usort($aEventCallbacks, function ($a, $b) {
$fPriorityA = $a['priority'];
@@ -96,31 +100,15 @@ class EventService
return;
}
- foreach (self::$aEventListeners[$sEvent] as $aEventCallback) {
- if (!self::MatchEventSource($aEventCallback['source'], $eventSource)) {
- continue;
- }
+ foreach (self::GetListeners($sEvent, $eventSource) as $aEventCallback) {
if (!self::MatchContext($aEventCallback['context'])) {
continue;
}
$sName = $aEventCallback['name'];
- // Reentrance protection
- if (isset(self::$aReentranceProtection[$aEventCallback['id']])) {
- if (!self::$aReentranceProtection[$aEventCallback['id']]) {
- unset(self::$aReentranceProtection[$aEventCallback['id']]);
- }
- IssueLog::Debug("Reentrance protection for '$sLogEventName'$sSource for '$sName'", LogChannels::EVENT_SERVICE);
- continue;
- }
IssueLog::Debug("Fire event '$sLogEventName'$sSource calling '$sName'", LogChannels::EVENT_SERVICE);
try {
- if (is_callable($aEventCallback['callback'])) {
- $oEventData->SetCallbackData($aEventCallback['data']);
- call_user_func($aEventCallback['callback'], $oEventData);
- } else {
- IssueLog::Debug("Callback '$sName' not a callable anymore, unregister", LogChannels::EVENT_SERVICE);
- self::UnRegisterCallback($aEventCallback['id']);
- }
+ $oEventData->SetCallbackData($aEventCallback['data']);
+ call_user_func($aEventCallback['callback'], $oEventData);
}
catch (Exception $e) {
IssueLog::Error("Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with error: ".$e->getMessage());
@@ -130,6 +118,20 @@ class EventService
$oKPI->ComputeStats('FireEvent', $sEvent);
}
+ public static function GetListeners($sEvent, $eventSource)
+ {
+ $aListeners = [];
+ if (isset(self::$aEventListeners[$sEvent])) {
+ foreach (self::$aEventListeners[$sEvent] as $aEventCallback) {
+ if (self::MatchEventSource($aEventCallback['source'], $eventSource)) {
+ $aListeners[] = $aEventCallback;
+ }
+ }
+ }
+
+ return $aListeners;
+ }
+
private static function MatchEventSource($srcRegistered, $srcEvent): bool
{
if (empty($srcRegistered)) {
@@ -208,7 +210,7 @@ class EventService
*
* @param string $sId the callback registration id
*/
- public static function UnRegisterCallback(string $sId)
+ public static function UnRegisterListener(string $sId)
{
$bRemoved = self::Browse(function ($sEvent, $idx, $aEventCallback) use ($sId) {
if ($aEventCallback['id'] == $sId) {
@@ -232,7 +234,7 @@ class EventService
*
* @param string $sEvent event to unregister
*/
- public static function UnRegisterEvent(string $sEvent)
+ public static function UnRegisterEventListeners(string $sEvent)
{
if (!isset(self::$aEventListeners[$sEvent])) {
IssueLog::Trace("No registration found for event '$sEvent'", LogChannels::EVENT_SERVICE);
@@ -274,7 +276,7 @@ class EventService
}
// For information only
- public static function RegisterEvent(string $sEvent, string $sDescription, array $aArguments, string $sModule)
+ public static function RegisterEvent(string $sEvent, array $aDescription, string $sModule)
{
if (isset(self::$aEventDescription[$sEvent])) {
$sPrevious = self::$aEventDescription[$sEvent]['module'];
@@ -282,14 +284,29 @@ class EventService
}
self::$aEventDescription[$sEvent] = [
- 'constant'=> 'EVENT_SERVICE_'.strtoupper(self::FromCamelCase($sEvent)),
'name'=> $sEvent,
- 'description' => $sDescription,
- 'arguments' => $aArguments,
+ 'description' => $aDescription,
'module' => $sModule,
];
}
+ public static function GetEventsByClass($sClass)
+ {
+ $aRes = [];
+ $oClass = new ReflectionClass($sClass);
+ foreach (self::$aEventDescription as $sEvent => $aEventInfo) {
+ if (isset($aEventInfo['description']['sources'])) {
+ foreach ($aEventInfo['description']['sources'] as $sSource) {
+ if ($sClass == $sSource || $oClass->isSubclassOf($sSource)) {
+ $aRes[$sEvent] = $aEventInfo;
+ }
+ }
+ }
+ }
+
+ return $aRes;
+ }
+
// Intentionally duplicated from SetupUtils, not yet loaded when RegisterEvent is called
private static function FromCamelCase($sInput) {
$sPattern = '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!';
diff --git a/test/service/EventTest.php b/test/service/EventTest.php
index 0bfe93c3f..432a59b2a 100644
--- a/test/service/EventTest.php
+++ b/test/service/EventTest.php
@@ -228,7 +228,7 @@ class EventTest extends ItopTestCase
EventService::FireEvent(new EventData('event2'));
$this->assertEquals(4, self::$iEventCalls);
- EventService::UnRegisterEvent('event1');
+ EventService::UnRegisterEventListeners('event1');
EventService::FireEvent(new EventData('event1'));
$this->assertEquals(4, self::$iEventCalls);
@@ -282,7 +282,7 @@ class EventTest extends ItopTestCase
EventService::FireEvent(new EventData('event2'));
$this->assertEquals(4, self::$iEventCalls);
- EventService::UnRegisterCallback($sIdToRemove);
+ EventService::UnRegisterListener($sIdToRemove);
EventService::FireEvent(new EventData('event1'));
$this->assertEquals(6, self::$iEventCalls);