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
|
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik;
use Piwik\Plugin;
/**
* This class allows code to post events from anywhere in Piwik and for
* plugins to associate callbacks to be executed when events are posted.
*
* @method static \Piwik\EventDispatcher getInstance()
*/
class EventDispatcher extends Singleton
{
// implementation details for postEvent
const EVENT_CALLBACK_GROUP_FIRST = 0;
const EVENT_CALLBACK_GROUP_SECOND = 1;
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;
/**
* Constructor.
*/
public function __construct($pluginManager = null)
{
$this->pluginManager = $pluginManager;
}
/**
* 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 ($pending) {
$this->pendingEvents[] = array($eventName, $params);
}
if (empty($plugins)) {
$plugins = $this->getPluginManager()->getPluginsLoadedAndActivated();
}
$callbacks = array();
// collect all callbacks to execute
foreach ($plugins as $plugin) {
if (is_string($plugin)) {
$plugin = $this->getPluginManager()->getLoadedPlugin($plugin);
}
$hooks = $plugin->getListHooksRegistered();
if (isset($hooks[$eventName])) {
list($pluginFunction, $callbackGroup) = $this->getCallbackFunctionAndGroupNumber($hooks[$eventName]);
$callbacks[$callbackGroup][] = is_string($pluginFunction) ? array($plugin, $pluginFunction) : $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;
}
/**
* Removes all registered extra observers for an event name. Only used for testing.
*
* @param string $eventName
*/
public function clearObservers($eventName)
{
$this->extraObservers[$eventName] = array();
}
/**
* Removes all registered extra observers. Only used for testing.
*/
public function clearAllObservers()
{
foreach ($this->extraObservers as $eventName => $eventObservers) {
if (strpos($eventName, 'Log.format') === 0) {
continue;
}
$this->extraObservers[$eventName] = array();
}
}
/**
* Re-posts all pending events to the given plugin.
*
* @param Plugin $plugin
*/
public function postPendingEventsTo($plugin)
{
foreach ($this->pendingEvents as $eventInfo) {
list($eventName, $eventParams) = $eventInfo;
$this->postEvent($eventName, $eventParams, $pending = false, array($plugin));
}
}
private function getCallbackFunctionAndGroupNumber($hookInfo)
{
if (is_array($hookInfo)
&& !empty($hookInfo['function'])
) {
$pluginFunction = $hookInfo['function'];
if (!empty($hookInfo['before'])) {
$callbackGroup = self::EVENT_CALLBACK_GROUP_FIRST;
} else if (!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);
}
/**
* Returns the Plugin\Manager instance used by the event dispatcher.
*
* @return Plugin\Manager
*/
private function getPluginManager()
{
if ($this->pluginManager === null) {
$this->pluginManager = Plugin\Manager::getInstance();
}
return $this->pluginManager;
}
}
|