diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php
index 7b1baa97d..81a6d9c66 100644
--- a/application/applicationextension.inc.php
+++ b/application/applicationextension.inc.php
@@ -299,6 +299,7 @@ abstract class AbstractPreferencesExtension implements iPreferencesExtension
*
* @api
* @package Extensibility
+ * @deprecated
*/
interface iApplicationUIExtension
{
@@ -441,6 +442,7 @@ interface iApplicationUIExtension
* @api
* @package Extensibility
* @since 2.7.0
+ * @deprecated
*/
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
{
@@ -1770,4 +1772,4 @@ class RestUtils
interface iModuleExtension
{
public function __construct();
-}
\ No newline at end of file
+}
diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index d0298dc2c..9978ece10 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
*/
+use Combodo\iTop\Service\EventName;
+
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
define('HILIGHT_CLASS_CRITICAL', 'red');
@@ -4162,6 +4164,7 @@ EOF
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
}
}
+ $this->FireEvent(EventName::ON_CHECK_TO_WRITE, array('error_messages' => &$this->m_aCheckIssues));
// User rights
//
@@ -4210,6 +4213,7 @@ EOF
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
}
}
+ $this->FireEvent(EventName::ON_CHECK_TO_DELETE, array('error_messages' => &$this->m_aDeleteIssues));
// User rights
//
diff --git a/application/loginbasic.class.inc.php b/application/loginbasic.class.inc.php
index 660b45cba..c62336be8 100644
--- a/application/loginbasic.class.inc.php
+++ b/application/loginbasic.class.inc.php
@@ -56,6 +56,7 @@ class LoginBasic extends AbstractLoginFSMExtension
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
{
+ $_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
diff --git a/application/loginexternal.class.inc.php b/application/loginexternal.class.inc.php
index d4fcb7182..bbdede7c4 100644
--- a/application/loginexternal.class.inc.php
+++ b/application/loginexternal.class.inc.php
@@ -40,6 +40,7 @@ class LoginExternal extends AbstractLoginFSMExtension
$sAuthUser = $this->GetAuthUser();
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
{
+ $_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
@@ -86,4 +87,4 @@ class LoginExternal extends AbstractLoginFSMExtension
/** @var string $sAuthUser */
return $sAuthUser; // Retrieve the value
}
-}
\ No newline at end of file
+}
diff --git a/application/loginform.class.inc.php b/application/loginform.class.inc.php
index a4fb99db5..973a54396 100644
--- a/application/loginform.class.inc.php
+++ b/application/loginform.class.inc.php
@@ -68,6 +68,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
{
+ $_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
diff --git a/application/loginurl.class.inc.php b/application/loginurl.class.inc.php
index 8a215e8f5..4fb3bbee4 100644
--- a/application/loginurl.class.inc.php
+++ b/application/loginurl.class.inc.php
@@ -55,6 +55,7 @@ class LoginURL extends AbstractLoginFSMExtension
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
{
+ $_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
@@ -90,4 +91,4 @@ class LoginURL extends AbstractLoginFSMExtension
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
-}
\ No newline at end of file
+}
diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php
index ab594b21d..1d48af07e 100644
--- a/application/loginwebpage.class.inc.php
+++ b/application/loginwebpage.class.inc.php
@@ -24,6 +24,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
+use Combodo\iTop\Service\Event;
+use Combodo\iTop\Service\EventName;
+
/**
* Web page used for displaying the login form
*/
@@ -447,6 +450,7 @@ class LoginWebPage extends NiceWebPage
$_SESSION['login_state'] = self::LOGIN_STATE_START;
}
$sLoginState = $_SESSION['login_state'];
+ $bFireEvent = ($sLoginState != self::LOGIN_STATE_CONNECTED);
$sSessionLog = '';
if ($bLoginDebug)
@@ -487,10 +491,15 @@ class LoginWebPage extends NiceWebPage
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
if ($iResponse == self::LOGIN_FSM_RETURN)
{
+ if ($bFireEvent)
+ {
+ Event::FireEvent(EventName::LOGIN, null, array('code' => $iErrorCode, 'state' => $sLoginState));
+ }
return $iErrorCode; // Asked to exit FSM, generally login OK
}
if ($iResponse == self::LOGIN_FSM_ERROR)
{
+ Event::FireEvent(EventName::LOGIN, null, array('code' => $iErrorCode, 'state' => $sLoginState));
$sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error
// An error was detected, skip the other plugins turn
break;
@@ -504,6 +513,7 @@ class LoginWebPage extends NiceWebPage
}
catch (Exception $e)
{
+ Event::FireEvent(EventName::LOGIN, null, array('state' => $_SESSION['login_state']));
IssueLog::Error($e->getTraceAsString());
static::ResetSession();
die($e->getMessage());
diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml
index 624854e32..fd500739c 100644
--- a/core/datamodel.core.xml
+++ b/core/datamodel.core.xml
@@ -254,5 +254,237 @@
+
+
+ An object details is about to be displayed to a user
+ Class hierarchy of the displayed object
+
+
+ The object displayed
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object is about to be inserted in the database
+ DBObject::OnInsert
+
+
+ The object inserted
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ A key (id) has been generated for an object inserted into the database (use GetKey() to read the new key)
+ DBObject::OnObjectKeyReady
+
+
+ The object inserted
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object has been inserted into the database
+ DBObject::AfterInsert
+
+
+ The object inserted
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object is about to be updated in the database
+ DBObject::OnUpdate
+
+
+ The object updated
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object has been updated into the database
+ DBObject::AfterUpdate
+
+
+ The object updated
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object is about to be deleted in the database
+ DBObject::OnDelete
+
+
+ The object deleted
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object has been deleted into the database
+ DBObject::AfterDelete
+
+
+ The object deleted
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ A stimulus is about to be applied to an object
+
+
+ The object where the stimulus is to be applied
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ A stimulus has been applied to an object
+
+
+ The object where the stimulus has been applied
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object has been loaded from the database
+
+
+ The object loaded
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object has been created in memory
+
+
+ The object created
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ An object has been re-loaded from the database
+
+
+ The object re-loaded
+ DBObject
+
+
+ Debug string
+ string
+
+
+
+
+ Check an object before it is written into the database
+ 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
+ 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
+
+
+ The object containing the document
+ DBObject
+
+
+ The document downloaded
+ ormDocument
+
+
+ Debug string
+ string
+
+
+
+
-
\ No newline at end of file
+
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 6b1745a12..c908ca7aa 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -15,6 +15,8 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see
+use Combodo\iTop\Service\Event;
+use Combodo\iTop\Service\EventName;
/**
* All objects to be displayed in the application (either as a list or as details)
@@ -153,9 +155,14 @@ abstract class DBObject implements iDisplay
protected $m_aSynchroData = null;
protected $m_sHighlightCode = null;
protected $m_aCallbacks = array();
+ /**
+ * @var string local events suffix
+ */
+ protected $m_sEventUniqId = '';
+ protected static $m_iEventUniqCounter = 0;
- /**
+ /**
* DBObject constructor.
*
* You should preferably use MetaModel::NewObject() instead of this constructor.
@@ -179,6 +186,9 @@ abstract class DBObject implements iDisplay
$this->m_bFullyLoaded = $this->IsFullyLoaded();
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
+ $this->m_sEventUniqId = 'DataModel_'.self::$m_iEventUniqCounter++;
+ $this->RegisterEvents();
+ $this->FireEvent(EventName::DB_OBJECT_LOADED);
return;
}
// Creation of a brand new object
@@ -205,6 +215,14 @@ abstract class DBObject implements iDisplay
}
$this->UpdateMetaAttributes();
+
+ $this->m_sEventUniqId = 'DataModel_'.self::$m_iEventUniqCounter++;
+ $this->RegisterEvents();
+ $this->FireEvent(EventName::DB_OBJECT_NEW);
+ }
+
+ protected function RegisterEvents()
+ {
}
/**
@@ -351,6 +369,7 @@ abstract class DBObject implements iDisplay
public function Reload($bAllowAllData = false)
{
assert($this->m_bIsInDB);
+ $this->FireEvent(EventName::DB_OBJECT_RELOAD);
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, true /* AllowAllData */);
if (empty($aRow))
{
@@ -2687,6 +2706,7 @@ abstract class DBObject implements iDisplay
// Ensure the update of the values (we are accessing the data directly)
$this->DoComputeValues();
$this->OnInsert();
+ $this->FireEvent(EventName::BEFORE_INSERT);
if ($this->m_iKey < 0)
{
@@ -2759,6 +2779,7 @@ abstract class DBObject implements iDisplay
}
$this->OnObjectKeyReady();
+ $this->FireEvent(EventName::DB_OBJECT_KEY_READY);
$this->DBWriteLinks();
$this->WriteExternalAttributes();
@@ -2789,6 +2810,7 @@ abstract class DBObject implements iDisplay
}
$this->AfterInsert();
+ $this->FireEvent(EventName::AFTER_INSERT);
// Activate any existing trigger
$sClass = get_class($this);
@@ -3037,6 +3059,8 @@ abstract class DBObject implements iDisplay
}
}
$this->OnUpdate();
+ $this->FireEvent(EventName::BEFORE_UPDATE);
+
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
@@ -3239,6 +3263,7 @@ abstract class DBObject implements iDisplay
try
{
$this->AfterUpdate();
+ $this->FireEvent(EventName::AFTER_UPDATE);
// Reload to get the external attributes
if ($bHasANewExternalKeyValue)
@@ -3357,6 +3382,7 @@ abstract class DBObject implements iDisplay
}
$this->OnDelete();
+ $this->FireEvent(EventName::BEFORE_DELETE);
// Activate any existing trigger
$sClass = get_class($this);
@@ -3458,6 +3484,8 @@ abstract class DBObject implements iDisplay
}
$this->AfterDelete();
+ $this->FireEvent(EventName::AFTER_DELETE);
+
$this->m_bIsInDB = false;
// Fix for N°926: do NOT reset m_iKey as it can be used to have it for reporting purposes (see the REST service to delete
@@ -3643,6 +3671,8 @@ abstract class DBObject implements iDisplay
$aTransitionDef = $aStateTransitions[$sStimulusCode];
+ $this->FireEvent(EventName::BEFORE_APPLY_STIMULUS);
+
// Change the state before proceeding to the actions, this is necessary because an action might
// trigger another stimuli (alternative: push the stimuli into a queue)
$sPreviousState = $this->Get($sStateAttCode);
@@ -3754,6 +3784,8 @@ abstract class DBObject implements iDisplay
/** @var \Trigger $oTrigger */
$oTrigger->DoActivate($this->ToArgs('this'));
}
+
+ $this->FireEvent(EventName::AFTER_APPLY_STIMULUS);
}
else
{
@@ -5384,5 +5416,23 @@ abstract class DBObject implements iDisplay
}
return $oExpression->Evaluate($aArgs);
}
+
+ /**
+ * @param $sEvent
+ * @param array $aEventData
+ *
+ * @throws \CoreException
+ */
+ public function FireEvent($sEvent, $aEventData = array())
+ {
+ $aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
+ $aEventData['object'] = $this;
+ $aEventSources = array($this->m_sEventUniqId);
+ foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass)
+ {
+ $aEventSources[] = $sClass;
+ }
+ Event::FireEvent($sEvent, $aEventSources, $aEventData);
+ }
}
diff --git a/core/log.class.inc.php b/core/log.class.inc.php
index dfb0fa3e6..b25ba37d0 100644
--- a/core/log.class.inc.php
+++ b/core/log.class.inc.php
@@ -603,16 +603,24 @@ abstract class LogAPI
}
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
+ {
+ if (self::CanLog($sLevel, $sChannel))
+ {
+ static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
+ }
+ }
+
+ public static function CanLog($sLevel, $sChannel = null)
{
if (! static::$m_oFileLog)
{
- return;
+ return false;
}
if (! isset(self::$aLevelsPriority[$sLevel]))
{
IssueLog::Error("invalid log level '{$sLevel}'");
- return;
+ return false;
}
if (is_null($sChannel))
@@ -621,25 +629,21 @@ abstract class LogAPI
}
$sMinLogLevel = self::GetMinLogLevel($sChannel);
-
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
{
- return;
+ return false;
}
- if (is_string($sMinLogLevel))
+ if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
{
- if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
- {
- throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
- }
- elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
- {
- //priority too low regarding the conf, do not log this
- return;
- }
+ throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
+ }
+ elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
+ {
+ //priority too low regarding the conf, do not log this
+ return false;
}
- static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
+ return true;
}
/**
@@ -830,4 +834,4 @@ class LogFileRotationProcess implements iScheduledProcess
throw new ProcessException(self::class.' : The configured filename builder is invalid (log_filename_builder_impl="'.$sLogFileNameBuilder.'")');
}
-}
\ No newline at end of file
+}
diff --git a/core/ormdocument.class.inc.php b/core/ormdocument.class.inc.php
index aa8b91eba..96e001c33 100644
--- a/core/ormdocument.class.inc.php
+++ b/core/ormdocument.class.inc.php
@@ -25,6 +25,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
+use Combodo\iTop\Service\EventName;
+use Combodo\iTop\Service\Event;
+
/**
* ormDocument
@@ -188,7 +191,6 @@ class ormDocument
* @param string $sContentDisposition Either 'inline' or 'attachment'
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
- * @return none
*/
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
{
@@ -207,6 +209,12 @@ class ormDocument
$oDocument = $oObj->Get($sAttCode);
if (is_object($oDocument))
{
+ $aEventData = array(
+ 'debug_info' => $oDocument->GetFileName(),
+ 'object' => $oObj,
+ 'document' => $oDocument,
+ );
+ Event::FireEvent(EventName::DOWNLOAD_DOCUMENT, $sClass, $aEventData);
$oPage->TrashUnexpectedOutput();
$oPage->SetContentType($oDocument->GetMimeType());
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php
index c8efc1b78..a9318690b 100644
--- a/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php
@@ -28,6 +28,8 @@ use BinaryExpression;
use Combodo\iTop\Portal\Brick\CreateBrick;
use Combodo\iTop\Portal\Helper\ApplicationHelper;
use Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
+use Combodo\iTop\Service\Event;
+use Combodo\iTop\Service\EventName;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
@@ -121,6 +123,8 @@ class ObjectController extends BrickController
$sOperation = $oRequestManipulator->ReadParam('operation', '');
+ $oObject->FireEvent(EventName::OBJECT_DETAILS);
+
$aData = array('sMode' => 'view');
$aData['form'] = $oObjectFormHandler->HandleForm($oRequest, $aData['sMode'], $sObjectClass, $sObjectId);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass),
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index 8f8e6b200..f8ac9d928 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -150,6 +150,9 @@ return array(
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
+ 'Combodo\\iTop\\Service\\Event' => $baseDir . '/sources/application/Service/Event.php',
+ 'Combodo\\iTop\\Service\\EventData' => $baseDir . '/sources/application/Service/EventData.php',
+ 'Combodo\\iTop\\Service\\EventName' => $baseDir . '/sources/application/Service/EventName.php',
'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php',
'Config' => $baseDir . '/core/config.class.inc.php',
'ConfigException' => $baseDir . '/core/config.class.inc.php',
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index 818c364ae..d8e1818b7 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -380,6 +380,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
+ 'Combodo\\iTop\\Service\\Event' => __DIR__ . '/../..' . '/sources/application/Service/Event.php',
+ 'Combodo\\iTop\\Service\\EventData' => __DIR__ . '/../..' . '/sources/application/Service/EventData.php',
+ 'Combodo\\iTop\\Service\\EventName' => __DIR__ . '/../..' . '/sources/application/Service/EventName.php',
'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
'ConfigException' => __DIR__ . '/../..' . '/core/config.class.inc.php',
diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php
index dd9b32bce..adb31b42e 100644
--- a/setup/compiler.class.inc.php
+++ b/setup/compiler.class.inc.php
@@ -241,8 +241,9 @@ class MFCompiler
$this->Log("Root class (with child classes): ".$oClass->getAttribute('id'));
$this->aRootClasses[$oClass->getAttribute('id')] = $oClass;
}
-
- $this->LoadSnippets();
+
+ $this->LoadSnippets();
+ $this->LoadHooks();
// Compile, module by module
//
@@ -1119,6 +1120,67 @@ EOF
$aClassParams['indexes'] = var_export($aIndexes, true);
}
+ $sEvents = '';
+ $sMethods = '';
+ $oHooks = $oClass->GetOptionalElement('hooks');
+ if ($oHooks)
+ {
+ foreach($oHooks->getElementsByTagName('hook') as $oHook)
+ {
+ /** @var \DOMElement $oHook */
+ $sEventName = $oHook->getAttribute('id');
+ $oListeners = $oHook->GetOptionalElement('listeners');
+ if ($oListeners)
+ {
+ foreach ($oListeners->getElementsByTagName('listener') as $oListener)
+ {
+ /** @var \DOMElement $oListener */
+ $sListenerId = $oListener->getAttribute('id');
+ $oCallback = $oListener->GetUniqueElement('callback', false);
+ if (is_object($oCallback))
+ {
+ $sCallback = $oCallback->GetText();
+ }
+ else
+ {
+ $oExecute = $oListener->GetUniqueElement('execute', true);
+ $sExecute = trim($oExecute->GetText());
+ $sCallback = "EventHook_{$sEventName}_{$sListenerId}";
+ $sCallbackFct = preg_replace('@^function\s*\(@', "public function {$sCallback}(", $sExecute);
+ if ($sExecute == $sCallbackFct)
+ {
+ throw new DOMFormatException("Malformed tag in class: {$sClass} hook: {$sEventName} listener: {$sListenerId}");
+ }
+ $sMethods .= "\n {$sCallbackFct}\n\n";
+ }
+ if (strpos($sCallback, '::') === false)
+ {
+ $sEventListener = 'array($this, \''.$sCallback.'\')';
+ }
+ else
+ {
+ $sEventListener = "'{$sCallback}'";
+ }
+
+ $sListenerPriority = (float)($oListener->GetChildText('priority', '0'));
+ $sEvents .= "\n Combodo\iTop\Service\Event::Register(\"$sEventName\", $sEventListener, \$this->m_sEventUniqId, \"$sListenerId\", null, $sListenerPriority);";
+ }
+ }
+ }
+ }
+
+ if (!empty($sEvents))
+ {
+ $sMethods .= <<GetOptionalElement('archive'))
{
$bEnabled = $this->GetPropBoolean($oArchive, 'enabled', false);
@@ -1862,7 +1924,6 @@ EOF
}
// Methods
- $sMethods = "";
$oMethods = $oClass->GetUniqueElement('methods');
foreach($oMethods->getElementsByTagName('method') as $oMethod)
{
@@ -1993,6 +2054,7 @@ $sLifecycle
$sHighlightScale
$sZlists;
EOF;
+
// some other stuff (magical attributes like friendlyName) are done in MetaModel::InitClasses and though not present in the
// generated PHP
$aPHP[$sClassName] = $this->GeneratePhpCodeForClass($sClassName, $sParentClass, $sClassParams, $sInitMethodCalls,
@@ -2930,6 +2992,130 @@ EOF;
foreach($this->aSnippets as $sModuleId => $void)
{
uasort($this->aSnippets[$sModuleId]['before'], array(get_class($this), 'SortOnRank'));
+ uasort($this->aSnippets[$sModuleId]['after'], array(get_class($this), 'SortOnRank'));
+ }
+ }
+
+ /**
+ * @throws \DOMFormatException
+ */
+ protected function LoadHooks()
+ {
+ $sClassName = 'GlobalEventHooks';
+ $sModuleId = '_core_';
+ if (!array_key_exists($sModuleId, $this->aSnippets))
+ {
+ $this->aSnippets[$sModuleId] = array('before' => array(), 'after' => array());
+ }
+ $oHooks = $this->oFactory->GetNodes('/itop_design/hooks/hook');
+ $aHooks = array();
+ foreach($oHooks as $oHook)
+ {
+ /** @var \DOMElement $oHook */
+ $sEventName = $oHook->getAttribute('id');
+ $oListeners = $oHook->GetOptionalElement('listeners');
+ if ($oListeners)
+ {
+ foreach ($oListeners->getElementsByTagName('listener') as $oListener)
+ {
+ /** @var \DOMElement $oListener */
+ $sListenerId = $oListener->getAttribute('id');
+ $oExecute = $oListener->GetUniqueElement('execute', true);
+ $sExecute = trim($oExecute->GetText());
+ $sCallback = "{$sEventName}_{$sListenerId}";
+ $sCallbackFct = preg_replace('@^function\s*\(@', "public static function {$sCallback}(", $sExecute);
+ if ($sExecute == $sCallbackFct)
+ {
+ throw new DOMFormatException("Malformed tag in hook: {$sEventName} listener: {$sListenerId}");
+ }
+ $fPriority = (float)($oListener->GetChildText('priority', '0'));
+
+ $aFilters = array();
+ $oFilters = $oListener->GetOptionalElement('filters');
+ if ($oFilters)
+ {
+ foreach ($oFilters->getElementsByTagName('filter') as $oFilter)
+ {
+ $aFilters[] = $oFilter->GetText();
+ }
+ }
+ if (empty($aFilters))
+ {
+ $sEventSource = 'null';
+ }
+ else
+ {
+ $sEventSource = 'array("'.implode('", "', $aFilters).'")';
+ }
+
+ $aContexts = array();
+ $oContexts = $oListener->GetOptionalElement('Contexts');
+ if ($oContexts)
+ {
+ foreach ($oContexts->getElementsByTagName('Context') as $oContext)
+ {
+ $aContexts[] = $oContext->GetText();
+ }
+ }
+ if (empty($aContexts))
+ {
+ $sContext = 'null';
+ }
+ else
+ {
+ $sContext = 'array("'.implode('", "', $aContexts).'")';
+ }
+
+ $aHooks[] = array(
+ 'event_name' => $sEventName,
+ 'callback' => $sCallback,
+ 'content' => $sCallbackFct,
+ 'priority' => $fPriority,
+ 'source' => $sEventSource,
+ 'context' => $sContext,
+ );
+ }
+ }
+ }
+
+ if (empty($aHooks))
+ {
+ return;
+ }
+
+ $sRegister = '';
+ $sMethods = '';
+ foreach ($aHooks as $aHook)
+ {
+ $sCallback = $aHook['callback'];
+ $sEventName = $aHook['event_name'];
+ $sEventSource = $aHook['source'];
+ $sContext = $aHook['context'];
+ $sPriority = $aHook['priority'];
+ $sRegister .= "\nCombodo\iTop\Service\Event::Register(\"$sEventName\", '$sClassName::$sCallback', $sEventSource, null, $sContext, $sPriority);";
+ $sCallbackFct = $aHook['content'];
+ $sMethods .= "\n {$sCallbackFct}\n\n";
+ }
+
+ $sContent = <<aSnippets[$sModuleId]['after'][] = array(
+ 'rank' => $fOrder,
+ 'content' => $sContent,
+ 'snippet_id' => $sClassName,
+ );
+ foreach($this->aSnippets as $sModuleId => $void)
+ {
+ uasort($this->aSnippets[$sModuleId]['after'], array(get_class($this), 'SortOnRank'));
}
}
diff --git a/sources/application/Service/Event.php b/sources/application/Service/Event.php
new file mode 100644
index 000000000..edb0f2924
--- /dev/null
+++ b/sources/application/Service/Event.php
@@ -0,0 +1,298 @@
+ $sId,
+ 'callback' => $callback,
+ 'source' => $sEventSource,
+ 'name' => $sName,
+ 'data' => $aCallbackData,
+ 'context' => $context,
+ 'priority' => $fPriority,
+ );
+ usort($aEventCallbacks, function ($a, $b) {
+ $fPriorityA = $a['priority'];
+ $fPriorityB = $b['priority'];
+ if ($fPriorityA == $fPriorityB) {
+ return 0;
+ }
+ return ($fPriorityA < $fPriorityB) ? -1 : 1;
+ });
+ self::$aEvents[$sEvent] = $aEventCallbacks;
+
+ if (IssueLog::CanLog(IssueLog::LEVEL_DEBUG, LOG_EVENT_SERVICE_CHANNEL))
+ {
+ $iTotalRegistrations = 0;
+ foreach (self::$aEvents as $aEvent)
+ {
+ $iTotalRegistrations += count($aEvent);
+ }
+ $sEventName = "$sEvent:".self::GetSourcesAsString($sEventSource);
+ IssueLog::Trace("Registering event '$sEventName' for '$sName' with id '$sId' (total $iTotalRegistrations)", LOG_EVENT_SERVICE_CHANNEL);
+ }
+ return $sId;
+ }
+
+ /**
+ * Fire an event. Call all the callbacks registered for this event.
+ *
+ * @param string $sEvent event to trigger
+ * @param string|array $eventSource source of the event
+ * @param array $aEventData event related data
+ *
+ * @throws \Exception from the callback
+ */
+ public static function FireEvent($sEvent, $eventSource = null, $aEventData = array())
+ {
+ $oKPI = new ExecutionKPI();
+ $sSource = isset($aEventData['debug_info']) ? " {$aEventData['debug_info']}" : '';
+ $sEventName = "$sEvent:".self::GetSourcesAsString($eventSource);
+ IssueLog::Trace("Fire event '$sEventName'$sSource", LOG_EVENT_SERVICE_CHANNEL);
+ if (!isset(self::$aEvents[$sEvent]))
+ {
+ IssueLog::Trace("No registration found for event '$sEvent'", LOG_EVENT_SERVICE_CHANNEL);
+ $oKPI->ComputeStats('FireEvent', $sEvent);
+ return;
+ }
+
+ foreach (self::$aEvents[$sEvent] as $aEventCallback)
+ {
+ if (!self::MatchEventSource($aEventCallback['source'], $eventSource))
+ {
+ continue;
+ }
+ if (!self::MatchContext($aEventCallback['context']))
+ {
+ continue;
+ }
+ $sName = $aEventCallback['name'];
+ IssueLog::Debug("Fire event '$sEventName'$sSource calling '{$sName}'", LOG_EVENT_SERVICE_CHANNEL);
+ try
+ {
+ if (is_callable($aEventCallback['callback']))
+ {
+ call_user_func($aEventCallback['callback'], new EventData($sEvent, $eventSource, $aEventData, $aEventCallback['data']));
+ }
+ else
+ {
+ IssueLog::Debug("Callback '{$sName}' not a callable anymore, unregister", LOG_EVENT_SERVICE_CHANNEL);
+ self::UnRegisterCallback($aEventCallback['id']);
+ }
+ }
+ catch (Exception $e)
+ {
+ IssueLog::Error("Event '$sEventName' for '{$sName}' id {$aEventCallback['id']} failed with error: ".$e->getMessage());
+ throw $e;
+ }
+ }
+ $oKPI->ComputeStats('FireEvent', $sEvent);
+ }
+
+ private static function MatchEventSource($srcRegistered, $srcEvent)
+ {
+ if (empty($srcRegistered))
+ {
+ // no filtering
+ return true;
+ }
+ if (empty($srcEvent))
+ {
+ // no match (the registered source is not empty)
+ return false;
+ }
+ if (is_string($srcRegistered))
+ {
+ $aSrcRegistered = array($srcRegistered);
+ }
+ elseif (is_array($srcRegistered))
+ {
+ $aSrcRegistered = $srcRegistered;
+ }
+ else
+ {
+ $aSrcRegistered = array();
+ }
+
+ if (is_string($srcEvent))
+ {
+ $aSrcEvent = array($srcEvent);
+ }
+ elseif (is_array($srcEvent))
+ {
+ $aSrcEvent = $srcEvent;
+ }
+ else
+ {
+ $aSrcEvent = array();
+ }
+
+ foreach ($aSrcRegistered as $sSrcRegistered)
+ {
+ foreach ($aSrcEvent as $sSrcEvent)
+ {
+ if ($sSrcRegistered == $sSrcEvent)
+ {
+ // sources matches
+ return true;
+ }
+ }
+ }
+ // no match
+ return false;
+ }
+
+ private static function MatchContext($registeredContext)
+ {
+ if (empty($registeredContext))
+ {
+ return true;
+ }
+ if (is_string($registeredContext))
+ {
+ $aContexts = array($registeredContext);
+ }
+ elseif (is_array($registeredContext))
+ {
+ $aContexts = $registeredContext;
+ }
+ else
+ {
+ return false;
+ }
+ foreach ($aContexts as $sContext)
+ {
+ if (ContextTag::Check($sContext))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static function GetSourcesAsString($srcRegistered)
+ {
+ if (empty($srcRegistered))
+ {
+ return '';
+ }
+ if (is_string($srcRegistered))
+ {
+ return $srcRegistered;
+ }
+ if (is_array($srcRegistered))
+ {
+ $sStr = implode(',', $srcRegistered);
+ }
+ return '';
+ }
+
+ /**
+ * Unregister a previously registered callback
+ *
+ * @param string $sId the callback registration id
+ */
+ public static function UnRegisterCallback($sId)
+ {
+ $bRemoved = self::Browse(function ($sEvent, $idx, $aEventCallback) use ($sId) {
+ if ($aEventCallback['id'] == $sId)
+ {
+ $sName = self::$aEvents[$sEvent][$idx]['name'];
+ unset (self::$aEvents[$sEvent][$idx]);
+ IssueLog::Trace("Unregistered callback '{$sName}' id {$sId}' on event '{$sEvent}'", LOG_EVENT_SERVICE_CHANNEL);
+ return false;
+ }
+ return true;
+ });
+
+ if (!$bRemoved)
+ {
+ IssueLog::Trace("No registration found for callback '{$sId}'", LOG_EVENT_SERVICE_CHANNEL);
+ }
+ }
+
+ /**
+ * Unregister an event
+ *
+ * @param string $sEvent event to unregister
+ */
+ public static function UnRegisterEvent($sEvent)
+ {
+ if (!isset(self::$aEvents[$sEvent]))
+ {
+ IssueLog::Trace("No registration found for event '$sEvent'", LOG_EVENT_SERVICE_CHANNEL);
+ return;
+ }
+
+ unset(self::$aEvents[$sEvent]);
+ IssueLog::Trace("Unregistered all the callbacks on event '{$sEvent}'", LOG_EVENT_SERVICE_CHANNEL);
+ }
+
+ /**
+ * Unregister all the events
+ */
+ public static function UnRegisterAll()
+ {
+ self::$aEvents = array();
+ IssueLog::Trace("Unregistered all events", LOG_EVENT_SERVICE_CHANNEL);
+ }
+
+ /**
+ * Browse all the registrations
+ *
+ * @param \Closure $callback function($sEvent, $idx, $aEventCallback) to call (return false to interrupt the browsing)
+ *
+ * @return bool true if interrupted else false
+ */
+ private static function Browse(closure $callback)
+ {
+ foreach (self::$aEvents as $sEvent => $aCallbackList)
+ {
+ foreach ($aCallbackList as $idx => $aEventCallback)
+ {
+ if (call_user_func($callback, $sEvent, $idx, $aEventCallback) === false)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/sources/application/Service/EventData.php b/sources/application/Service/EventData.php
new file mode 100644
index 000000000..b3ee713d6
--- /dev/null
+++ b/sources/application/Service/EventData.php
@@ -0,0 +1,84 @@
+sEvent = $sEvent;
+ $this->mEventData = $aEventData;
+ $this->sEventSource = $sEventSource;
+ $this->mCallbackData = $aCallbackData;
+ }
+
+ /**
+ * @return string
+ */
+ public function GetEvent()
+ {
+ return $this->sEvent;
+ }
+
+ public function Get($sParam)
+ {
+ if (is_array($this->mEventData) && isset($this->mEventData[$sParam]))
+ {
+ return $this->mEventData[$sParam];
+ }
+
+ if (is_array($this->mCallbackData) && isset($this->mCallbackData[$sParam]))
+ {
+ return $this->mCallbackData[$sParam];
+ }
+ return null;
+ }
+
+ /**
+ * @return string
+ */
+ public function GetEventSource()
+ {
+ return $this->sEventSource;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function GetEventData()
+ {
+ return $this->mEventData;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function GetCallbackData()
+ {
+ return $this->mCallbackData;
+ }
+}
diff --git a/sources/application/Service/EventName.php b/sources/application/Service/EventName.php
new file mode 100644
index 000000000..9f3836835
--- /dev/null
+++ b/sources/application/Service/EventName.php
@@ -0,0 +1,38 @@
+
status
+
+ service
+
coreExtensions
diff --git a/test/service/EventTest.php b/test/service/EventTest.php
new file mode 100644
index 000000000..db0a491d2
--- /dev/null
+++ b/test/service/EventTest.php
@@ -0,0 +1,406 @@
+expectException(TypeError::class);
+ Event::Register('event', $callback);
+ }
+
+ public function BadCallbackProvider()
+ {
+ return array(
+ array('toto'),
+ array('EventTest::toto'),
+ array(array('EventTest', 'toto')),
+ array(array($this, 'toto')),
+ );
+ }
+
+ public function testNoParameterCallbackFunction()
+ {
+ $sId = Event::Register('event', function () { $this->debug("Closure: event received !!!"); self::IncrementCallCount(); });
+ $this->debug("Registered $sId");
+ Event::FireEvent('event');
+ $this->assertEquals(1, self::$iEventCalls);
+ }
+
+ /**
+ * @dataProvider GoodCallbackProvider
+ *
+ * @param $callback
+ *
+ * @throws \Exception
+ */
+ public function testMethodCallbackFunction(callable $callback)
+ {
+ $sId = Event::Register('event', $callback);
+ $this->debug("Registered $sId");
+ Event::FireEvent('event');
+ $this->assertEquals(1, self::$iEventCalls);
+ Event::FireEvent('event');
+ $this->assertEquals(2, self::$iEventCalls);
+ }
+
+ public function GoodCallbackProvider()
+ {
+ $oReceiver = new TestEventReceiver();
+ return array(
+ 'method' => array(array($oReceiver, 'OnEvent1')),
+ 'static' => array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent1'),
+ 'static2' => array(array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver', 'OnStaticEvent1')),
+ );
+ }
+
+ public function testBrokenCallback()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event_a', array($oReceiver, 'BrokenCallback'));
+
+ $this->expectException(TypeError::class);
+ Event::FireEvent('event_a');
+ }
+
+ public function testRemovedCallback()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event_a', array($oReceiver, 'OnEvent1'));
+
+ $oReceiver = null;
+ gc_collect_cycles();
+
+ Event::FireEvent('event_a');
+ $this->assertEquals(1, self::$iEventCalls);
+ }
+
+ public function testMultiEvent()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event_a', array($oReceiver, 'OnEvent1'));
+ Event::Register('event_a', array($oReceiver, 'OnEvent2'));
+ Event::Register('event_a', array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver', 'OnStaticEvent1'));
+ Event::Register('event_a', 'Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent2');
+
+ Event::Register('event_b', array($oReceiver, 'OnEvent1'));
+ Event::Register('event_b', array($oReceiver, 'OnEvent2'));
+ Event::Register('event_b', array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver', 'OnStaticEvent1'));
+ Event::Register('event_b', 'Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent2');
+
+ Event::FireEvent('event_a');
+ $this->assertEquals(4, self::$iEventCalls);
+ Event::FireEvent('event_b');
+ $this->assertEquals(8, self::$iEventCalls);
+ }
+
+ public function testMultiSameEvent()
+ {
+ $oReceiver = new TestEventReceiver();
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+
+ Event::FireEvent('event1');
+ $this->assertEquals(4, self::$iEventCalls);
+ }
+
+ public function testData()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event1', array($oReceiver, 'OnEventWithData'), '');
+ Event::Register('event1', array($oReceiver, 'OnEventWithData'), '');
+ Event::FireEvent('event1', '', array('text' => 'Event Data 1'));
+ $this->assertEquals(2, self::$iEventCalls);
+ }
+
+ public function testPriority()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event1', array($oReceiver, 'OnEvent1'), '', null, null, 0);
+ Event::Register('event1', array($oReceiver, 'OnEvent2'), '', null, null, 1);
+
+ Event::Register('event2', array($oReceiver, 'OnEvent1'), '', null, null, 1);
+ Event::Register('event2', array($oReceiver, 'OnEvent2'), '', null, null, 0);
+
+ Event::FireEvent('event1');
+ $this->assertEquals(2, self::$iEventCalls);
+ Event::FireEvent('event2');
+ $this->assertEquals(4, self::$iEventCalls);
+ }
+
+ public function testContext()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event1', array($oReceiver, 'OnEvent1'), '', null, null, 0);
+ Event::Register('event1', array($oReceiver, 'OnEvent2'), '', null, 'test_context', 1);
+ Event::FireEvent('event1');
+ $this->assertEquals(1, self::$iEventCalls);
+ ContextTag::AddContext('test_context');
+ Event::FireEvent('event1');
+ $this->assertEquals(3, self::$iEventCalls);
+ }
+
+ public function testEventSource()
+ {
+ $oReceiver = new TestEventReceiver();
+ Event::Register('event1', array($oReceiver, 'OnEvent1'), 'A', null, null, 0);
+ Event::Register('event1', array($oReceiver, 'OnEvent2'), 'A', null, null, 1);
+ Event::Register('event1', 'Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent1', null, null, null, 2);
+
+ Event::Register('event2', array($oReceiver, 'OnEvent1'), 'A', null, null, 1);
+ Event::Register('event2', 'Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent1', null, null, null, 2);
+ Event::Register('event2', array($oReceiver, 'OnEvent2'), 'B', null, null, 0);
+
+ Event::FireEvent('event1', 'A');
+ $this->assertEquals(3, self::$iEventCalls);
+ Event::FireEvent('event2', 'A');
+ $this->assertEquals(5, self::$iEventCalls);
+ Event::FireEvent('event1');
+ $this->assertEquals(6, self::$iEventCalls);
+ Event::FireEvent('event2');
+ $this->assertEquals(7, self::$iEventCalls);
+ Event::FireEvent('event2', array('A', 'B'));
+ $this->assertEquals(10, self::$iEventCalls);
+
+ }
+
+
+ public function testUnRegisterEvent()
+ {
+ $oReceiver = new TestEventReceiver();
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event2', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+
+ Event::FireEvent('event1');
+ $this->assertEquals(3, self::$iEventCalls);
+
+ Event::FireEvent('event2');
+ $this->assertEquals(4, self::$iEventCalls);
+
+ Event::UnRegisterEvent('event1');
+
+ Event::FireEvent('event1');
+ $this->assertEquals(4, self::$iEventCalls);
+
+ Event::FireEvent('event2');
+ $this->assertEquals(5, self::$iEventCalls);
+ }
+
+ public function testUnRegisterAll()
+ {
+ $oReceiver = new TestEventReceiver();
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event2', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+
+ Event::FireEvent('event1');
+ $this->assertEquals(3, self::$iEventCalls);
+
+ Event::FireEvent('event2');
+ $this->assertEquals(4, self::$iEventCalls);
+
+ Event::UnRegisterAll();
+
+ Event::FireEvent('event1');
+ $this->assertEquals(4, self::$iEventCalls);
+
+ Event::FireEvent('event2');
+ $this->assertEquals(4, self::$iEventCalls);
+ }
+
+ public function testUnRegisterCallback()
+ {
+ $oReceiver = new TestEventReceiver();
+ $sIdToRemove = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sIdToRemove");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event1', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+ $sId = Event::Register('event2', array($oReceiver, 'OnEvent1'));
+ $this->debug("Registered $sId");
+
+ Event::FireEvent('event1');
+ $this->assertEquals(3, self::$iEventCalls);
+
+ Event::FireEvent('event2');
+ $this->assertEquals(4, self::$iEventCalls);
+
+ Event::UnRegisterCallback($sIdToRemove);
+
+ Event::FireEvent('event1');
+ $this->assertEquals(6, self::$iEventCalls);
+
+ Event::FireEvent('event2');
+ $this->assertEquals(7, self::$iEventCalls);
+ }
+
+ public static function IncrementCallCount()
+ {
+ self::$iEventCalls++;
+ }
+
+ /**
+ * static version of the debug to be accessible from other objects
+ *
+ * @param $sMsg
+ */
+ public static function DebugStatic($sMsg)
+ {
+ if (DEBUG_UNIT_TEST)
+ {
+ if (is_string($sMsg))
+ {
+ echo "$sMsg\n";
+ }
+ else
+ {
+ print_r($sMsg);
+ }
+ }
+ }
+}
+
+class TestEventReceiver
+{
+
+ // Event callbacks
+ public function OnEvent1(EventData $oData)
+ {
+ $sEvent = $oData->GetEvent();
+ $this->Debug(__METHOD__.": received event '{$sEvent}'");
+ EventTest::IncrementCallCount();
+ }
+
+ // Event callbacks
+ public function BrokenCallback(array $aData)
+ {
+ $sEvent = $aData['event'];
+ $this->Debug(__METHOD__.": received event '{$sEvent}'");
+ EventTest::IncrementCallCount();
+ }
+
+ // Event callbacks
+ public function OnEvent2(EventData $oData)
+ {
+ $sEvent = $oData->GetEvent();
+ $this->Debug(__METHOD__.": received event '{$sEvent}'");
+ EventTest::IncrementCallCount();
+ }
+
+ public function OnEventWithData(EventData $oData)
+ {
+ $sEvent = $oData->GetEvent();
+ $mEventData = $oData->GetEventData();
+ $this->Debug(__METHOD__.": received event '{$sEvent}'");
+ EventTest::IncrementCallCount();
+ $this->Debug($mEventData);
+ }
+
+ // Event callbacks
+ public static function OnStaticEvent1(EventData $oData)
+ {
+ $sEvent = $oData->GetEvent();
+ self::DebugStatic(__METHOD__.": received event '{$sEvent}'");
+ EventTest::IncrementCallCount();
+ }
+
+ // Event callbacks
+ public static function OnStaticEvent2(EventData $oData)
+ {
+ $sEvent = $oData->GetEvent();
+ self::DebugStatic(__METHOD__.": received event '{$sEvent}'");
+ EventTest::IncrementCallCount();
+ }
+
+ /**
+ * static version of the debug to be accessible from other objects
+ *
+ * @param $sMsg
+ */
+ public static function DebugStatic($sMsg)
+ {
+ if (DEBUG_UNIT_TEST)
+ {
+ if (is_string($sMsg))
+ {
+ echo "$sMsg\n";
+ }
+ else
+ {
+ print_r($sMsg);
+ }
+ }
+ }
+ /**
+ * static version of the debug to be accessible from other objects
+ *
+ * @param $sMsg
+ */
+ public function Debug($sMsg)
+ {
+ if (DEBUG_UNIT_TEST)
+ {
+ if (is_string($sMsg))
+ {
+ echo "$sMsg\n";
+ }
+ else
+ {
+ print_r($sMsg);
+ }
+ }
+ }
+
+}