Elgg  Version master
EventsService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
9 
16 
17  use Loggable;
18  use Profilable;
19 
20  const REG_KEY_PRIORITY = 0;
21  const REG_KEY_INDEX = 1;
22  const REG_KEY_HANDLER = 2;
23 
24  const OPTION_STOPPABLE = 'stoppable';
25 
29  protected $handlers;
30 
34  protected $next_index = 0;
35 
39  protected $registrations = [];
40 
44  protected $backups = [];
45 
51  public function __construct(HandlersService $handlers) {
52  $this->handlers = $handlers;
53  }
54 
69  public function trigger(string $name, string $type, $object = null, array $options = []): bool {
70  $options = array_merge([
71  self::OPTION_STOPPABLE => true,
72  ], $options);
73 
74  // get registered handlers
75  $handlers = $this->getOrderedHandlers($name, $type);
76 
77  // This starts as a string, but if a handler type-hints an object we convert it on-demand inside
78  // \Elgg\HandlersService::call and keep it alive during all handler calls. We do this because
79  // creating objects for every triggering is expensive.
80  /* @var $event Event|string */
81  $event = 'event';
82  foreach ($handlers as $handler) {
83  $handler_description = false;
84  if ($this->hasTimer() && $type === 'system' && $name !== 'shutdown') {
85  $handler_description = $this->handlers->describeCallable($handler) . '()';
86  $this->beginTimer(["[{$name},{$type}]", $handler_description]);
87  }
88 
89  list($success, $return, $event) = $this->handlers->call($handler, $event, [
90  $name,
91  $type,
92  null,
93  [
94  'object' => $object,
95  '_elgg_sequence_id' => elgg_extract('_elgg_sequence_id', $options),
96  ],
97  ]);
98 
99  if ($handler_description) {
100  $this->endTimer(["[{$name},{$type}]", $handler_description]);
101  }
102 
103  if (!$success) {
104  continue;
105  }
106 
107  if (!empty($options[self::OPTION_STOPPABLE]) && ($return === false)) {
108  return false;
109  }
110  }
111 
112  return true;
113  }
114 
127  public function triggerResults(string $name, string $type, array $params = [], $value = null) {
128  // This starts as a string, but if a handler type-hints an object we convert it on-demand inside
129  // \Elgg\HandlersService::call and keep it alive during all handler calls. We do this because
130  // creating objects for every triggering is expensive.
131  /* @var $event Event|string */
132  $event = 'event';
133  foreach ($this->getOrderedHandlers($name, $type) as $handler) {
134  list($success, $return, $event) = $this->handlers->call($handler, $event, [$name, $type, $value, $params]);
135 
136  if (!$success) {
137  continue;
138  }
139 
140  if ($return !== null) {
141  $value = $return;
142  $event->setValue($value);
143  }
144  }
145 
146  return $value;
147  }
148 
168  public function triggerBefore(string $name, string $type, $object = null, array $options = []): bool {
169  return $this->trigger("{$name}:before", $type, $object, $options);
170  }
171 
190  public function triggerAfter(string $name, string $type, $object = null, array $options = []): void {
191  $options[self::OPTION_STOPPABLE] = false;
192 
193  $this->trigger("{$name}:after", $type, $object, $options);
194  }
195 
210  public function triggerSequence(string $name, string $type, $object = null, callable $callable = null, array $options = []): bool {
211  // generate a unique ID to identify this sequence
212  $options['_elgg_sequence_id'] = uniqid("{$name}{$type}", true);
213 
214  if (!$this->triggerBefore($name, $type, $object, $options)) {
215  return false;
216  }
217 
218  $result = $this->trigger($name, $type, $object, $options);
219  if ($result === false) {
220  return false;
221  }
222 
223  if ($callable) {
224  $result = call_user_func($callable, $object);
225  }
226 
227  $this->triggerAfter($name, $type, $object, $options);
228 
229  return $result;
230  }
231 
246  public function triggerResultsSequence(string $name, string $type, array $params = [], $value = null, callable $callable = null, array $options = []) {
247  // generate a unique ID to identify this sequence
248  $unique_id = uniqid("{$name}{$type}results", true);
249  $options['_elgg_sequence_id'] = $unique_id;
250  $params['_elgg_sequence_id'] = $unique_id;
251 
252  if (!$this->triggerBefore($name, $type, $params, $options)) {
253  return false;
254  }
255 
256  $result = $this->triggerResults($name, $type, $params, $value);
257  if ($result === false) {
258  return false;
259  }
260 
261  if ($callable) {
262  $result = call_user_func($callable, $params);
263  }
264 
265  $this->triggerAfter($name, $type, $params, $options);
266 
267  return $result;
268  }
269 
283  public function triggerDeprecated(string $name, string $type, $object = null, string $message = '', string $version = ''): bool {
284  $message = "The '{$name}', '{$type}' event is deprecated. {$message}";
285  $this->checkDeprecation($name, $type, $message, $version);
286 
287  return $this->trigger($name, $type, $object);
288  }
289 
304  public function triggerDeprecatedResults(string $name, string $type, array $params = [], $returnvalue = null, string $message = '', string $version = '') {
305  $message = "The '{$name}', '{$type}' event is deprecated. {$message}";
306  $this->checkDeprecation($name, $type, $message, $version);
307 
308  return $this->triggerResults($name, $type, $params, $returnvalue);
309  }
310 
324  public function registerHandler(string $name, string $type, $callback, int $priority = 500): bool {
325  if (empty($name) || empty($type) || !is_callable($callback, true)) {
326  return false;
327  }
328 
329  if (($name == 'view' || $name == 'view_vars') && $type !== 'all') {
330  $type = ViewsService::canonicalizeViewName($type);
331  }
332 
333  $services = _elgg_services();
334  if (in_array($this->getLogger()->getLevel(false), [LogLevel::WARNING, LogLevel::NOTICE, LogLevel::INFO, LogLevel::DEBUG])) {
335  if (!$services->handlers->isCallable($callback)) {
336  $this->getLogger()->warning('Handler: ' . $services->handlers->describeCallable($callback) . ' is not callable');
337  }
338  }
339 
340  $this->registrations[$name][$type][] = [
341  self::REG_KEY_PRIORITY => $priority,
342  self::REG_KEY_INDEX => $this->next_index,
343  self::REG_KEY_HANDLER => $callback,
344  ];
345  $this->next_index++;
346 
347  return true;
348  }
349 
360  public function unregisterHandler(string $name, string $type, $callback): void {
361  if (($name === 'view' || $name === 'view_vars') && $type !== 'all') {
362  $type = ViewsService::canonicalizeViewName($type);
363  }
364 
365  if (empty($this->registrations[$name][$type])) {
366  return;
367  }
368 
369  $matcher = $this->getMatcher($callback);
370 
371  foreach ($this->registrations[$name][$type] as $i => $registration) {
372  if ($matcher instanceof MethodMatcher) {
373  if (!$matcher->matches($registration[self::REG_KEY_HANDLER])) {
374  continue;
375  }
376  } elseif ($registration[self::REG_KEY_HANDLER] != $callback) {
377  continue;
378  }
379 
380  unset($this->registrations[$name][$type][$i]);
381  return;
382  }
383  }
384 
393  public function clearHandlers(string $name, string $type): void {
394  unset($this->registrations[$name][$type]);
395  }
396 
411  public function getAllHandlers(): array {
412  $ret = [];
413  foreach ($this->registrations as $name => $types) {
414  foreach ($types as $type => $registrations) {
415  foreach ($registrations as $registration) {
416  $priority = $registration[self::REG_KEY_PRIORITY];
417  $ret[$name][$type][$priority][] = $registration[self::REG_KEY_HANDLER];
418  }
419  }
420  }
421 
422  return $ret;
423  }
424 
435  public function hasHandler(string $name, string $type): bool {
436  return !empty($this->registrations[$name][$type]);
437  }
438 
447  public function getOrderedHandlers(string $name, string $type): array {
448  $registrations = [];
449 
450  if (!empty($this->registrations[$name][$type])) {
451  if ($name !== 'all' && $type !== 'all') {
452  array_splice($registrations, count($registrations), 0, $this->registrations[$name][$type]);
453  }
454  }
455 
456  if (!empty($this->registrations['all'][$type])) {
457  if ($type !== 'all') {
458  array_splice($registrations, count($registrations), 0, $this->registrations['all'][$type]);
459  }
460  }
461 
462  if (!empty($this->registrations[$name]['all'])) {
463  if ($name !== 'all') {
464  array_splice($registrations, count($registrations), 0, $this->registrations[$name]['all']);
465  }
466  }
467 
468  if (!empty($this->registrations['all']['all'])) {
469  array_splice($registrations, count($registrations), 0, $this->registrations['all']['all']);
470  }
471 
472  usort($registrations, function ($a, $b) {
473  // priority first
474  if ($a[self::REG_KEY_PRIORITY] < $b[self::REG_KEY_PRIORITY]) {
475  return -1;
476  }
477 
478  if ($a[self::REG_KEY_PRIORITY] > $b[self::REG_KEY_PRIORITY]) {
479  return 1;
480  }
481 
482  // then insertion order
483  return ($a[self::REG_KEY_INDEX] < $b[self::REG_KEY_INDEX]) ? -1 : 1;
484  });
485 
486  $handlers = [];
487  foreach ($registrations as $registration) {
488  $handlers[] = $registration[self::REG_KEY_HANDLER];
489  }
490 
491  return $handlers;
492  }
493 
501  protected function getMatcher($spec): ?MethodMatcher {
502  if (is_string($spec) && str_contains($spec, '::')) {
503  list ($type, $method) = explode('::', $spec, 2);
504  return new MethodMatcher($type, $method);
505  }
506 
507  if (!is_array($spec) || empty($spec[0]) || empty($spec[1]) || !is_string($spec[1])) {
508  return null;
509  }
510 
511  if (is_object($spec[0])) {
512  $spec[0] = get_class($spec[0]);
513  }
514 
515  if (!is_string($spec[0])) {
516  return null;
517  }
518 
519  return new MethodMatcher($spec[0], $spec[1]);
520  }
521 
531  public function backup(): void {
532  $this->backups[] = $this->registrations;
533  $this->registrations = [];
534  }
535 
541  public function restore(): void {
542  $backup = array_pop($this->backups);
543  if (is_array($backup)) {
544  $this->registrations = $backup;
545  }
546  }
547 
558  protected function checkDeprecation(string $name, string $type, string $message, string $version): void {
559  $message = trim($message);
560  if (empty($message)) {
561  return;
562  }
563 
564  if (!$this->hasHandler($name, $type)) {
565  return;
566  }
567 
568  $this->logDeprecatedMessage($message, $version);
569  }
570 }
trait Profilable
Make an object accept a timer.
Definition: Profilable.php:12
$params
Saves global plugin settings.
Definition: save.php:13
registerHandler(string $name, string $type, $callback, int $priority=500)
Register a callback as a event handler.
Helpers for providing callable-based APIs.
hasHandler(string $name, string $type)
Is a handler registered for this specific name and type? "all" handlers are not considered.
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
triggerAfter(string $name, string $type, $object=null, array $options=[])
Trigger an "After event" indicating a process has finished.
$version
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
Events service.
getAllHandlers()
Returns all registered handlers as array( $name => array( $type => array( $priority => array( callbac...
$type
Definition: delete.php:22
hasTimer()
Has a timer been set.
Definition: Profilable.php:34
__construct(HandlersService $handlers)
Constructor.
triggerBefore(string $name, string $type, $object=null, array $options=[])
Trigger a "Before event" indicating a process is about to begin.
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof\ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
$options
Elgg admin footer.
Definition: footer.php:6
triggerDeprecatedResults(string $name, string $type, array $params=[], $returnvalue=null, string $message= '', string $version= '')
Trigger an event sequence normally, but send a notice about deprecated use if any handlers are regist...
$value
Definition: generic.php:51
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
trigger(string $name, string $type, $object=null, array $options=[])
Triggers an Elgg event.
triggerResultsSequence(string $name, string $type, array $params=[], $value=null, callable $callable=null, array $options=[])
Trigger an sequence of <event>:before, <event>, and <event>:after handlers.
triggerDeprecated(string $name, string $type, $object=null, string $message= '', string $version= '')
Trigger an event sequence normally, but send a notice about deprecated use if any handlers are regist...
getOrderedHandlers(string $name, string $type)
Returns an ordered array of handlers registered for $name and $type.
restore()
Restore backed up event registrations (after tests)
triggerSequence(string $name, string $type, $object=null, callable $callable=null, array $options=[])
Trigger an sequence of <event>:before, <event>, and <event>:after handlers.
getLogger()
Returns logger.
Definition: Loggable.php:37
if($email instanceof\Elgg\Email) $object
Definition: body.php:24
Identify a static/dynamic method callable, even if contains an object to which you don&#39;t have a refer...
beginTimer(array $keys)
Start the timer (when enabled)
Definition: Profilable.php:46
backup()
Temporarily remove all event registrations (before tests)
checkDeprecation(string $name, string $type, string $message, string $version)
Check if handlers are registered on a deprecated event.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:346
unregisterHandler(string $name, string $type, $callback)
Unregister a callback as an event handler.
$handler
Definition: add.php:7
getMatcher($spec)
Create a matcher for the given callable (if it&#39;s for a static or dynamic method)
clearHandlers(string $name, string $type)
Clears all callback registrations for an event.
triggerResults(string $name, string $type, array $params=[], $value=null)
Triggers a event that is allowed to return a mixed result.
$priority
endTimer(array $keys)
Ends the timer (when enabled)
Definition: Profilable.php:62
logDeprecatedMessage(string $message, string $version)
Sends a message about deprecated use of a function, view, etc.
Definition: Loggable.php:80