1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core\event; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 /** 22 * New event manager class. 23 * 24 * @package core 25 * @since Moodle 2.6 26 * @copyright 2013 Petr Skoda {@link http://skodak.org} 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 30 /** 31 * Class used for event dispatching. 32 * 33 * Note: Do NOT use directly in your code, it is intended to be used from 34 * base event class only. 35 */ 36 class manager { 37 /** @var array buffer of event for dispatching */ 38 protected static $buffer = array(); 39 40 /** @var array buffer for events that were not sent to external observers when DB transaction in progress */ 41 protected static $extbuffer = array(); 42 43 /** @var bool evert dispatching already in progress - prevents nesting */ 44 protected static $dispatching = false; 45 46 /** @var array cache of all observers */ 47 protected static $allobservers = null; 48 49 /** @var bool should we reload observers after the test? */ 50 protected static $reloadaftertest = false; 51 52 /** 53 * Trigger new event. 54 * 55 * @internal to be used only from \core\event\base::trigger() method. 56 * @param \core\event\base $event 57 * 58 * @throws \coding_Exception if used directly. 59 */ 60 public static function dispatch(\core\event\base $event) { 61 if (during_initial_install()) { 62 return; 63 } 64 if (!$event->is_triggered() or $event->is_dispatched()) { 65 throw new \coding_exception('Illegal event dispatching attempted.'); 66 } 67 68 self::$buffer[] = $event; 69 70 if (self::$dispatching) { 71 return; 72 } 73 74 self::$dispatching = true; 75 self::process_buffers(); 76 self::$dispatching = false; 77 } 78 79 /** 80 * Notification from DML layer. 81 * @internal to be used from DML layer only. 82 */ 83 public static function database_transaction_commited() { 84 if (self::$dispatching or empty(self::$extbuffer)) { 85 return; 86 } 87 88 self::$dispatching = true; 89 self::process_buffers(); 90 self::$dispatching = false; 91 } 92 93 /** 94 * Notification from DML layer. 95 * @internal to be used from DML layer only. 96 */ 97 public static function database_transaction_rolledback() { 98 self::$extbuffer = array(); 99 } 100 101 protected static function process_buffers() { 102 global $DB, $CFG; 103 self::init_all_observers(); 104 105 while (self::$buffer or self::$extbuffer) { 106 107 $fromextbuffer = false; 108 $addedtoextbuffer = false; 109 110 if (self::$extbuffer and !$DB->is_transaction_started()) { 111 $fromextbuffer = true; 112 $event = reset(self::$extbuffer); 113 unset(self::$extbuffer[key(self::$extbuffer)]); 114 115 } else if (self::$buffer) { 116 $event = reset(self::$buffer); 117 unset(self::$buffer[key(self::$buffer)]); 118 119 } else { 120 return; 121 } 122 123 $observingclasses = self::get_observing_classes($event); 124 foreach ($observingclasses as $observingclass) { 125 if (!isset(self::$allobservers[$observingclass])) { 126 continue; 127 } 128 foreach (self::$allobservers[$observingclass] as $observer) { 129 if ($observer->internal) { 130 if ($fromextbuffer) { 131 // Do not send buffered external events to internal handlers, 132 // they processed them already. 133 continue; 134 } 135 } else { 136 if ($DB->is_transaction_started()) { 137 if ($fromextbuffer) { 138 // Weird! 139 continue; 140 } 141 // Do not notify external observers while in DB transaction. 142 if (!$addedtoextbuffer) { 143 self::$extbuffer[] = $event; 144 $addedtoextbuffer = true; 145 } 146 continue; 147 } 148 } 149 150 if (isset($observer->includefile) and file_exists($observer->includefile)) { 151 include_once($observer->includefile); 152 } 153 if (is_callable($observer->callable)) { 154 try { 155 call_user_func($observer->callable, $event); 156 } catch (\Exception $e) { 157 // Observers are notified before installation and upgrade, this may throw errors. 158 if (empty($CFG->upgraderunning)) { 159 // Ignore errors during upgrade, otherwise warn developers. 160 debugging("Exception encountered in event observer '$observer->callable': ".$e->getMessage(), DEBUG_DEVELOPER, $e->getTrace()); 161 } 162 } 163 } else { 164 debugging("Can not execute event observer '$observer->callable'"); 165 } 166 } 167 } 168 169 // TODO: Invent some infinite loop protection in case events cross-trigger one another. 170 } 171 } 172 173 /** 174 * Returns list of classes related to this event. 175 * @param \core\event\base $event 176 * @return array 177 */ 178 protected static function get_observing_classes(\core\event\base $event) { 179 $classname = get_class($event); 180 $observers = array('\\'.$classname); 181 while ($classname = get_parent_class($classname)) { 182 $observers[] = '\\'.$classname; 183 } 184 $observers = array_reverse($observers, false); 185 186 return $observers; 187 } 188 189 /** 190 * Initialise the list of observers. 191 */ 192 protected static function init_all_observers() { 193 global $CFG; 194 195 if (is_array(self::$allobservers)) { 196 return; 197 } 198 199 if (!PHPUNIT_TEST and !during_initial_install()) { 200 $cache = \cache::make('core', 'observers'); 201 $cached = $cache->get('all'); 202 $dirroot = $cache->get('dirroot'); 203 if ($dirroot === $CFG->dirroot and is_array($cached)) { 204 self::$allobservers = $cached; 205 return; 206 } 207 } 208 209 self::$allobservers = array(); 210 211 $plugintypes = \core_component::get_plugin_types(); 212 $plugintypes = array_merge(array('core' => 'not used'), $plugintypes); 213 $systemdone = false; 214 foreach ($plugintypes as $plugintype => $ignored) { 215 if ($plugintype === 'core') { 216 $plugins['core'] = "$CFG->dirroot/lib"; 217 } else { 218 $plugins = \core_component::get_plugin_list($plugintype); 219 } 220 221 foreach ($plugins as $plugin => $fulldir) { 222 if (!file_exists("$fulldir/db/events.php")) { 223 continue; 224 } 225 $observers = null; 226 include("$fulldir/db/events.php"); 227 if (!is_array($observers)) { 228 continue; 229 } 230 self::add_observers($observers, "$fulldir/db/events.php", $plugintype, $plugin); 231 } 232 } 233 234 self::order_all_observers(); 235 236 if (!PHPUNIT_TEST and !during_initial_install()) { 237 $cache->set('all', self::$allobservers); 238 $cache->set('dirroot', $CFG->dirroot); 239 } 240 } 241 242 /** 243 * Add observers. 244 * @param array $observers 245 * @param string $file 246 * @param string $plugintype Plugin type of the observer. 247 * @param string $plugin Plugin of the observer. 248 */ 249 protected static function add_observers(array $observers, $file, $plugintype = null, $plugin = null) { 250 global $CFG; 251 252 foreach ($observers as $observer) { 253 if (empty($observer['eventname']) or !is_string($observer['eventname'])) { 254 debugging("Invalid 'eventname' detected in $file observer definition", DEBUG_DEVELOPER); 255 continue; 256 } 257 if ($observer['eventname'] === '*') { 258 $observer['eventname'] = '\core\event\base'; 259 } 260 if (strpos($observer['eventname'], '\\') !== 0) { 261 $observer['eventname'] = '\\'.$observer['eventname']; 262 } 263 if (empty($observer['callback'])) { 264 debugging("Invalid 'callback' detected in $file observer definition", DEBUG_DEVELOPER); 265 continue; 266 } 267 $o = new \stdClass(); 268 $o->callable = $observer['callback']; 269 if (!isset($observer['priority'])) { 270 $o->priority = 0; 271 } else { 272 $o->priority = (int)$observer['priority']; 273 } 274 if (!isset($observer['internal'])) { 275 $o->internal = true; 276 } else { 277 $o->internal = (bool)$observer['internal']; 278 } 279 if (empty($observer['includefile'])) { 280 $o->includefile = null; 281 } else { 282 if ($CFG->admin !== 'admin' and strpos($observer['includefile'], '/admin/') === 0) { 283 $observer['includefile'] = preg_replace('|^/admin/|', '/'.$CFG->admin.'/', $observer['includefile']); 284 } 285 $observer['includefile'] = $CFG->dirroot . '/' . ltrim($observer['includefile'], '/'); 286 if (!file_exists($observer['includefile'])) { 287 debugging("Invalid 'includefile' detected in $file observer definition", DEBUG_DEVELOPER); 288 continue; 289 } 290 $o->includefile = $observer['includefile']; 291 } 292 $o->plugintype = $plugintype; 293 $o->plugin = $plugin; 294 self::$allobservers[$observer['eventname']][] = $o; 295 } 296 } 297 298 /** 299 * Reorder observers to allow quick lookup of observer for each event. 300 */ 301 protected static function order_all_observers() { 302 foreach (self::$allobservers as $classname => $observers) { 303 \core_collator::asort_objects_by_property($observers, 'priority', \core_collator::SORT_NUMERIC); 304 self::$allobservers[$classname] = array_reverse($observers); 305 } 306 } 307 308 /** 309 * Returns all observers in the system. This is only for use for reporting on the list of observers in the system. 310 * 311 * @access private 312 * @return array An array of stdClass with all core observer details. 313 */ 314 public static function get_all_observers() { 315 self::init_all_observers(); 316 return self::$allobservers; 317 } 318 319 /** 320 * Replace all standard observers. 321 * @param array $observers 322 * @return array 323 * 324 * @throws \coding_Exception if used outside of unit tests. 325 */ 326 public static function phpunit_replace_observers(array $observers) { 327 if (!PHPUNIT_TEST) { 328 throw new \coding_exception('Cannot override event observers outside of phpunit tests!'); 329 } 330 331 self::phpunit_reset(); 332 self::$allobservers = array(); 333 self::$reloadaftertest = true; 334 335 self::add_observers($observers, 'phpunit'); 336 self::order_all_observers(); 337 338 return self::$allobservers; 339 } 340 341 /** 342 * Reset everything if necessary. 343 * @private 344 * 345 * @throws \coding_Exception if used outside of unit tests. 346 */ 347 public static function phpunit_reset() { 348 if (!PHPUNIT_TEST) { 349 throw new \coding_exception('Cannot reset event manager outside of phpunit tests!'); 350 } 351 self::$buffer = array(); 352 self::$extbuffer = array(); 353 self::$dispatching = false; 354 if (!self::$reloadaftertest) { 355 self::$allobservers = null; 356 } 357 self::$reloadaftertest = false; 358 } 359 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body