1<?php 2namespace Elgg; 3 4/** 5 * Events service 6 * 7 * Use elgg()->events 8 */ 9class EventsService extends HooksRegistrationService { 10 use Profilable; 11 12 const OPTION_STOPPABLE = 'stoppable'; 13 14 /** 15 * @var HandlersService 16 */ 17 private $handlers; 18 19 /** 20 * Constructor 21 * 22 * @param HandlersService $handlers Handlers 23 */ 24 public function __construct(HandlersService $handlers) { 25 $this->handlers = $handlers; 26 } 27 28 /** 29 * Get the handlers service in use 30 * 31 * @return HandlersService 32 * @internal 33 */ 34 public function getHandlersService() { 35 return $this->handlers; 36 } 37 38 /** 39 * {@inheritdoc} 40 */ 41 public function registerHandler($name, $type, $callback, $priority = 500) { 42 if (in_array($type, ['member', 'friend', 'attached']) 43 && in_array($name, ['create', 'update', 'delete'])) { 44 _elgg_services()->logger->error("'$name, $type' event is no longer triggered. " 45 . "Update your event registration to use '$name, relationship'"); 46 } 47 48 return parent::registerHandler($name, $type, $callback, $priority); 49 } 50 51 /** 52 * Triggers an Elgg event 53 * 54 * @param string $event The event type 55 * @param string $object_type The object type 56 * @param mixed $object The object involved in the event 57 * @param array $options (internal) options for triggering the event 58 * 59 * @see elgg_trigger_event() 60 * @see elgg_trigger_after_event() 61 * @see elgg_trigger_before_event() 62 * 63 * @return bool 64 */ 65 public function trigger($name, $type, $object = null, array $options = []) { 66 $options = array_merge([ 67 self::OPTION_STOPPABLE => true, 68 ], $options); 69 70 // check for deprecation 71 $this->checkDeprecation($name, $type, $options); 72 73 // get registered handlers 74 $handlers = $this->getOrderedHandlers($name, $type); 75 76 // This starts as a string, but if a handler type-hints an object we convert it on-demand inside 77 // \Elgg\HandlersService::call and keep it alive during all handler calls. We do this because 78 // creating objects for every triggering is expensive. 79 $event = 'event'; 80 /* @var Event|string */ 81 82 foreach ($handlers as $handler) { 83 $handler_description = false; 84 if ($this->timer && $type === 'system' && $name !== 'shutdown') { 85 $handler_description = $this->handlers->describeCallable($handler) . "()"; 86 $this->timer->begin(["[$name,$type]", $handler_description]); 87 } 88 89 list($success, $return, $event) = $this->handlers->call($handler, $event, [$name, $type, $object]); 90 91 if ($handler_description) { 92 $this->timer->end(["[$name,$type]", $handler_description]); 93 } 94 95 if (!$success) { 96 continue; 97 } 98 99 if (!empty($options[self::OPTION_STOPPABLE]) && ($return === false)) { 100 return false; 101 } 102 } 103 104 return true; 105 } 106 107 /** 108 * Trigger a "Before event" indicating a process is about to begin. 109 * 110 * Like regular events, a handler returning false will cancel the process and false 111 * will be returned. 112 * 113 * To register for a before event, append ":before" to the event name when registering. 114 * 115 * @param string $event The event type. The fired event type will be appended with ":before". 116 * @param string $object_type The object type 117 * @param mixed $object The object involved in the event 118 * @param array $options (internal) options for triggering the event 119 * 120 * @return bool False if any handler returned false, otherwise true 121 * 122 * @see EventsService::trigger() 123 * @see EventsService::triggerAfter() 124 * @since 2.0.0 125 */ 126 public function triggerBefore($event, $object_type, $object = null, array $options = []) { 127 return $this->trigger("$event:before", $object_type, $object, $options); 128 } 129 130 /** 131 * Trigger an "After event" indicating a process has finished. 132 * 133 * Unlike regular events, all the handlers will be called, their return values ignored. 134 * 135 * To register for an after event, append ":after" to the event name when registering. 136 * 137 * @param string $event The event type. The fired event type will be appended with ":after". 138 * @param string $object_type The object type 139 * @param mixed $object The object involved in the event 140 * @param array $options (internal) options for triggering the event 141 * 142 * @return true 143 * 144 * @see EventsService::trigger() 145 * @see EventsService::triggerBefore() 146 * @since 2.0.0 147 */ 148 public function triggerAfter($event, $object_type, $object = null, array $options = []) { 149 $options[self::OPTION_STOPPABLE] = false; 150 151 return $this->trigger("$event:after", $object_type, $object, $options); 152 } 153 154 /** 155 * Trigger an sequence of <event>:before, <event>, and <event>:after handlers. 156 * Allows <event>:before to terminate the sequence by returning false from a handler 157 * Allows running a callable on successful <event> before <event>:after is triggered 158 * Returns the result of the callable or bool 159 * 160 * @param string $event The event type 161 * @param string $object_type The object type 162 * @param mixed $object The object involved in the event 163 * @param callable $callable Callable to run on successful event, before event:after 164 * @param array $options (internal) options for triggering the event 165 * 166 * @return mixed 167 */ 168 public function triggerSequence($event, $object_type, $object = null, callable $callable = null, array $options = []) { 169 if (!$this->triggerBefore($event, $object_type, $object, $options)) { 170 return false; 171 } 172 173 $result = $this->trigger($event, $object_type, $object, $options); 174 if (!$result) { 175 return false; 176 } 177 178 if ($callable) { 179 $result = call_user_func($callable, $object); 180 } 181 182 $this->triggerAfter($event, $object_type, $object, $options); 183 184 return $result; 185 } 186 187 /** 188 * Trigger an event sequence normally, but send a notice about deprecated use if any handlers are registered. 189 * 190 * @param string $event The event type 191 * @param string $object_type The object type 192 * @param mixed $object The object involved in the event 193 * @param string $message The deprecation message 194 * @param string $version Human-readable *release* version: 1.9, 1.10, ... 195 * 196 * @return bool 197 * 198 * @see EventsService::trigger() 199 * @see elgg_trigger_deprecated_event() 200 */ 201 public function triggerDeprecated($event, $object_type, $object = null, $message = null, $version = null) { 202 $options = [ 203 self::OPTION_DEPRECATION_MESSAGE => $message, 204 self::OPTION_DEPRECATION_VERSION => $version, 205 ]; 206 return $this->trigger($event, $object_type, $object, $options); 207 } 208 209 /** 210 * Trigger an event normally, but send a notice about deprecated use if any handlers are registered. 211 * 212 * @param string $event The event type 213 * @param string $object_type The object type 214 * @param mixed $object The object involved in the event 215 * @param callable $callable Callable to run on successful event, before event:after 216 * @param string $message The deprecation message 217 * @param string $version Human-readable *release* version: 1.9, 1.10, ... 218 * 219 * @return bool 220 * 221 * @see EventsService::trigger() 222 */ 223 public function triggerDeprecatedSequence($event, $object_type, $object = null, callable $callable = null, string $message = null, string $version = null) { 224 $options = [ 225 self::OPTION_DEPRECATION_MESSAGE => $message, 226 self::OPTION_DEPRECATION_VERSION => $version, 227 ]; 228 return $this->triggerSequence($event, $object_type, $object, $callable, $options); 229 } 230} 231