1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik;
use Piwik\Container\StaticContainer;
/**
* This class allows code to post events from anywhere in Piwik and for
* plugins to associate callbacks to be executed when events are posted.
*/
class EventDispatcher
{
/**
* @return EventDispatcher
*/
public static function getInstance()
{
return StaticContainer::get('Piwik\EventDispatcher');
}
// implementation details for postEvent
public const EVENT_CALLBACK_GROUP_FIRST = 0;
public const EVENT_CALLBACK_GROUP_SECOND = 1;
public const EVENT_CALLBACK_GROUP_THIRD = 2;
/**
* Array of observers (callbacks attached to events) that are not methods
* of plugin classes.
*
* @var array
*/
private $extraObservers = array();
/**
* Array storing information for all pending events. Each item in the array
* will be an array w/ two elements:
*
* array(
* 'Event.Name', // the event name
* array('event', 'parameters') // the parameters to pass to event observers
* )
*
* @var array
*/
private $pendingEvents = array();
/**
* Plugin\Manager instance used to get list of loaded plugins.
*
* @var \Piwik\Plugin\Manager
*/
private $pluginManager;
private $pluginHooks = array();
public static $_SKIP_EVENTS_IN_TESTS = false; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* Constructor.
*/
public function __construct(Plugin\Manager $pluginManager, array $observers = array())
{
$this->pluginManager = $pluginManager;
foreach ($observers as $observerInfo) {
list($eventName, $callback) = $observerInfo;
$this->extraObservers[$eventName][] = $callback;
}
}
/**
* Triggers an event, executing all callbacks associated with it.
*
* @param string $eventName The name of the event, ie, API.getReportMetadata.
* @param array $params The parameters to pass to each callback when executing.
* @param bool $pending Whether this event should be posted again for plugins
* loaded after the event is fired.
* @param array|null $plugins The plugins to post events to. If null, the event
* is posted to all plugins. The elements of this array
* can be either the Plugin objects themselves
* or their string names.
*/
public function postEvent($eventName, $params, $pending = false, $plugins = null)
{
if (self::$_SKIP_EVENTS_IN_TESTS) {
return;
}
if ($pending) {
$this->pendingEvents[] = array($eventName, $params);
}
$manager = $this->pluginManager;
if (empty($plugins)) {
$plugins = $manager->getPluginsLoadedAndActivated();
} else {
$pluginMap = [];
foreach ($plugins as $plugin) {
if (is_string($plugin)) {
$plugin = $this->pluginManager->getLoadedPlugin($plugin);
}
$pluginMap[$plugin->getPluginName()] = $plugin;
}
$plugins = $pluginMap;
}
$callbacks = array();
// collect all callbacks to execute
foreach ($plugins as $pluginName => $plugin) {
if (!isset($this->pluginHooks[$pluginName])) {
$this->pluginHooks[$pluginName] = $plugin->registerEvents();
}
$hooks = $this->pluginHooks[$pluginName];
if (isset($hooks[$eventName])) {
list($pluginFunction, $callbackGroup) = $this->getCallbackFunctionAndGroupNumber($hooks[$eventName]);
if (is_string($pluginFunction)) {
$callbacks[$callbackGroup][] = [$plugin, $pluginFunction];
} else {
$callbacks[$callbackGroup][] = $pluginFunction;
}
}
}
if (isset($this->extraObservers[$eventName])) {
foreach ($this->extraObservers[$eventName] as $callbackInfo) {
list($callback, $callbackGroup) = $this->getCallbackFunctionAndGroupNumber($callbackInfo);
$callbacks[$callbackGroup][] = $callback;
}
}
// sort callbacks by their importance
ksort($callbacks);
// execute callbacks in order
foreach ($callbacks as $callbackGroup) {
foreach ($callbackGroup as $callback) {
call_user_func_array($callback, $params);
}
}
}
/**
* Associates a callback that is not a plugin class method with an event
* name.
*
* @param string $eventName
* @param array|callable $callback This can be a normal PHP callback or an array
* that looks like this:
* array(
* 'function' => $callback,
* 'before' => true
* )
* or this:
* array(
* 'function' => $callback,
* 'after' => true
* )
* If 'before' is set, the callback will be executed
* before normal & 'after' ones. If 'after' then it
* will be executed after normal ones.
*/
public function addObserver($eventName, $callback)
{
$this->extraObservers[$eventName][] = $callback;
}
/**
* Re-posts all pending events to the given plugin.
*
* @param Plugin $plugin
*/
public function postPendingEventsTo($plugin)
{
foreach ($this->pendingEvents as $eventInfo) {
[$eventName, $eventParams] = $eventInfo;
$this->postEvent($eventName, $eventParams, $pending = false, array($plugin));
}
}
/**
* @internal For testing purpose only
*/
public function clearCache()
{
$this->pluginHooks = [];
}
private function getCallbackFunctionAndGroupNumber($hookInfo)
{
if (
is_array($hookInfo)
&& !empty($hookInfo['function'])
) {
$pluginFunction = $hookInfo['function'];
if (!empty($hookInfo['before'])) {
$callbackGroup = self::EVENT_CALLBACK_GROUP_FIRST;
} elseif (!empty($hookInfo['after'])) {
$callbackGroup = self::EVENT_CALLBACK_GROUP_THIRD;
} else {
$callbackGroup = self::EVENT_CALLBACK_GROUP_SECOND;
}
} else {
$pluginFunction = $hookInfo;
$callbackGroup = self::EVENT_CALLBACK_GROUP_SECOND;
}
return array($pluginFunction, $callbackGroup);
}
}
|