Event Service

This commit is contained in:
Eric
2020-05-15 20:42:05 +02:00
parent 7e970ee93c
commit 8bfc54e6b4
20 changed files with 1364 additions and 25 deletions

View File

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

View File

@@ -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
//

View File

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

View File

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

View File

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

View File

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

View File

@@ -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());

View File

@@ -254,5 +254,237 @@
</fields>
</class>
</classes>
<events _delta="define">
<event id="ObjectDetails">
<description>An object details is about to be displayed to a user</description>
<available_filters>Class hierarchy of the displayed object</available_filters>
<arguments>
<argument id="object">
<description>The object displayed</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="BeforeInsert">
<description>An object is about to be inserted in the database</description>
<replaces>DBObject::OnInsert</replaces>
<arguments>
<argument id="object">
<description>The object inserted</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="DBObjectKeyReady">
<description>A key (id) has been generated for an object inserted into the database (use GetKey() to read the new key)</description>
<replaces>DBObject::OnObjectKeyReady</replaces>
<arguments>
<argument id="object">
<description>The object inserted</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="AfterInsert">
<description>An object has been inserted into the database</description>
<replaces>DBObject::AfterInsert</replaces>
<arguments>
<argument id="object">
<description>The object inserted</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="BeforeUpdate">
<description>An object is about to be updated in the database</description>
<replaces>DBObject::OnUpdate</replaces>
<arguments>
<argument id="object">
<description>The object updated</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="AfterUpdate">
<description>An object has been updated into the database</description>
<replaces>DBObject::AfterUpdate</replaces>
<arguments>
<argument id="object">
<description>The object updated</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="BeforeDelete">
<description>An object is about to be deleted in the database</description>
<replaces>DBObject::OnDelete</replaces>
<arguments>
<argument id="object">
<description>The object deleted</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="AfterDelete">
<description>An object has been deleted into the database</description>
<replaces>DBObject::AfterDelete</replaces>
<arguments>
<argument id="object">
<description>The object deleted</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="BeforeApplyStimulus">
<description>A stimulus is about to be applied to an object</description>
<arguments>
<argument id="object">
<description>The object where the stimulus is to be applied</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="AfterApplyStimulus">
<description>A stimulus has been applied to an object</description>
<arguments>
<argument id="object">
<description>The object where the stimulus has been applied</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="DBObjectLoaded">
<description>An object has been loaded from the database</description>
<arguments>
<argument id="object">
<description>The object loaded</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="DBObjectNew">
<description>An object has been created in memory</description>
<arguments>
<argument id="object">
<description>The object created</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="DBObjectReload">
<description>An object has been re-loaded from the database</description>
<arguments>
<argument id="object">
<description>The object re-loaded</description>
<type>DBObject</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="OnCheckToWrite">
<description>Check an object before it is written into the database</description>
<replaces>cmdbAbstractObject::DoCheckToWrite</replaces>
<arguments>
<argument id="object">
<description>The object to check</description>
<type>DBObject</type>
</argument>
<argument id="error_messages">
<description>Array of strings where all the errors found during the object checking are added</description>
<type>array</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="DoCheckToDelete">
<description>Check an object before it is deleted from the database</description>
<replaces>cmdbAbstractObject::DoCheckToDelete</replaces>
<arguments>
<argument id="object">
<description>The object to check</description>
<type>DBObject</type>
</argument>
<argument id="error_messages">
<description>Array of strings where all the errors found during the object checking are added</description>
<type>array</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
<event id="DownloadDocument">
<description>A document has been downloaded from the GUI</description>
<arguments>
<argument id="object">
<description>The object containing the document</description>
<type>DBObject</type>
</argument>
<argument id="document">
<description>The document downloaded</description>
<type>ormDocument</type>
</argument>
<argument id="debug_info">
<description>Debug string</description>
<type>string</type>
</argument>
</arguments>
</event>
</events>
</meta>
</itop_design>
</itop_design>

View File

@@ -15,6 +15,8 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
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);
}
}

View File

@@ -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.'")');
}
}
}

View File

@@ -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());

View File

@@ -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),

View File

@@ -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',

View File

@@ -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',

View File

@@ -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 <execute> 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 .= <<<EOF
protected function RegisterEvents()
{
parent::RegisterEvents();
{$sEvents}
}
EOF;
}
if ($oArchive = $oProperties->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 <execute> 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 = <<<PHP
class $sClassName
{
$sMethods
}
$sRegister
PHP;
$fOrder = 0;
$this->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'));
}
}

View File

@@ -0,0 +1,298 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service;
use Closure;
use ContextTag;
use Exception;
use ExecutionKPI;
use IssueLog;
define('LOG_EVENT_SERVICE_CHANNEL', 'EventService');
class Event
{
private static $aEvents = array();
private static $iEventIdCounter = 0;
/**
* Register a callback for a specific event
*
* @param string $sEvent corresponding event
* @param callable $callback The callback to call
* @param array|string|null $sEventSource event filtering depending on the source of the event
* @param array $aCallbackData optional data given by the registrar to the callback
* @param array|string|null $context context filter
* @param float $fPriority optional priority for callback order
*
* @return string Id of the registration (used for unregistering)
*
* @throws \Exception
*/
public static function Register($sEvent, callable $callback, $sEventSource = null, $aCallbackData = array(), $context = null, $fPriority = 0.0)
{
is_callable($callback, false, $sName);
$aEventCallbacks = isset(self::$aEvents[$sEvent]) ? self::$aEvents[$sEvent] : array();
$sId = 'event_'.self::$iEventIdCounter++;
$aEventCallbacks[] = array(
'id' => $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;
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service;
/**
* Data given to the Event Service callbacks
* Class EventServiceData
*
* @package Combodo\iTop\Service
*/
class EventData
{
private $sEvent;
private $sEventSource;
private $mEventData;
private $mCallbackData;
/**
* EventServiceData constructor.
*
* @param string $sEvent
* @param string $sEventSource
* @param array $aEventData
* @param array $aCallbackData
*/
public function __construct($sEvent, $sEventSource, $aEventData, $aCallbackData)
{
$this->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;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service;
class EventName
{
// Core
//
// OrmDocument
const DOWNLOAD_DOCUMENT = 'DownloadDocument';
// DBObject
const DB_OBJECT_LOADED = 'DBObjectLoaded';
const DB_OBJECT_RELOAD = 'DBObjectReload';
const DB_OBJECT_NEW = 'DBObjectNew';
const BEFORE_INSERT = 'BeforeInsert';
const DB_OBJECT_KEY_READY = 'DBObjectKeyReady';
const AFTER_INSERT = 'AfterInsert';
const BEFORE_UPDATE = 'BeforeUpdate';
const AFTER_UPDATE = 'AfterUpdate';
const BEFORE_DELETE = 'BeforeDelete';
const AFTER_DELETE = 'AfterDelete';
const BEFORE_APPLY_STIMULUS = 'BeforeApplyStimulus';
const AFTER_APPLY_STIMULUS = 'AfterApplyStimulus';
const ON_CHECK_TO_WRITE = 'OnCheckToWrite';
const ON_CHECK_TO_DELETE = 'OnCheckToDelete';
const OBJECT_DETAILS = 'ObjectDetails';
// Application
//
const LOGIN = 'OnLogin';
}

View File

@@ -59,6 +59,9 @@
<testsuite name="Status">
<directory>status</directory>
</testsuite>
<testsuite name="Service">
<directory>service</directory>
</testsuite>
<testsuite name="CoreExtensions">
<directory>coreExtensions</directory>
</testsuite>

406
test/service/EventTest.php Normal file
View File

@@ -0,0 +1,406 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Service;
use Combodo\iTop\Service\Event;
use Combodo\iTop\Service\EventData;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use ContextTag;
use TypeError;
/**
* Class EventTest
*
* @package Combodo\iTop\Test\UnitTest\Service
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class EventTest extends ItopTestCase
{
const USE_TRANSACTION = false;
const CREATE_TEST_ORG = false;
private static $iEventCalls;
protected function setUp()
{
parent::setUp();
self::$iEventCalls = 0;
}
/**
* @dataProvider BadCallbackProvider
*
* @param $callback
*
* @throws \Exception
*/
public function testRegisterBadCallback($callback)
{
$this->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);
}
}
}
}