Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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          $observers = array('\core\event\base', '\\'.get_class($event));
     180          return $observers;
     181  
     182          // Note if we ever decide to observe events by parent class name use the following code instead.
     183          /*
     184          $classname = get_class($event);
     185          $observers = array('\\'.$classname);
     186          while ($classname = get_parent_class($classname)) {
     187              $observers[] = '\\'.$classname;
     188          }
     189          $observers = array_reverse($observers, false);
     190  
     191          return $observers;
     192          */
     193      }
     194  
     195      /**
     196       * Initialise the list of observers.
     197       */
     198      protected static function init_all_observers() {
     199          global $CFG;
     200  
     201          if (is_array(self::$allobservers)) {
     202              return;
     203          }
     204  
     205          if (!PHPUNIT_TEST and !during_initial_install()) {
     206              $cache = \cache::make('core', 'observers');
     207              $cached = $cache->get('all');
     208              $dirroot = $cache->get('dirroot');
     209              if ($dirroot === $CFG->dirroot and is_array($cached)) {
     210                  self::$allobservers = $cached;
     211                  return;
     212              }
     213          }
     214  
     215          self::$allobservers = array();
     216  
     217          $plugintypes = \core_component::get_plugin_types();
     218          $plugintypes = array_merge(array('core' => 'not used'), $plugintypes);
     219          $systemdone = false;
     220          foreach ($plugintypes as $plugintype => $ignored) {
     221              if ($plugintype === 'core') {
     222                  $plugins['core'] = "$CFG->dirroot/lib";
     223              } else {
     224                  $plugins = \core_component::get_plugin_list($plugintype);
     225              }
     226  
     227              foreach ($plugins as $plugin => $fulldir) {
     228                  if (!file_exists("$fulldir/db/events.php")) {
     229                      continue;
     230                  }
     231                  $observers = null;
     232                  include("$fulldir/db/events.php");
     233                  if (!is_array($observers)) {
     234                      continue;
     235                  }
     236                  self::add_observers($observers, "$fulldir/db/events.php", $plugintype, $plugin);
     237              }
     238          }
     239  
     240          self::order_all_observers();
     241  
     242          if (!PHPUNIT_TEST and !during_initial_install()) {
     243              $cache->set('all', self::$allobservers);
     244              $cache->set('dirroot', $CFG->dirroot);
     245          }
     246      }
     247  
     248      /**
     249       * Add observers.
     250       * @param array $observers
     251       * @param string $file
     252       * @param string $plugintype Plugin type of the observer.
     253       * @param string $plugin Plugin of the observer.
     254       */
     255      protected static function add_observers(array $observers, $file, $plugintype = null, $plugin = null) {
     256          global $CFG;
     257  
     258          foreach ($observers as $observer) {
     259              if (empty($observer['eventname']) or !is_string($observer['eventname'])) {
     260                  debugging("Invalid 'eventname' detected in $file observer definition", DEBUG_DEVELOPER);
     261                  continue;
     262              }
     263              if ($observer['eventname'] === '*') {
     264                  $observer['eventname'] = '\core\event\base';
     265              }
     266              if (strpos($observer['eventname'], '\\') !== 0) {
     267                  $observer['eventname'] = '\\'.$observer['eventname'];
     268              }
     269              if (empty($observer['callback'])) {
     270                  debugging("Invalid 'callback' detected in $file observer definition", DEBUG_DEVELOPER);
     271                  continue;
     272              }
     273              $o = new \stdClass();
     274              $o->callable = $observer['callback'];
     275              if (!isset($observer['priority'])) {
     276                  $o->priority = 0;
     277              } else {
     278                  $o->priority = (int)$observer['priority'];
     279              }
     280              if (!isset($observer['internal'])) {
     281                  $o->internal = true;
     282              } else {
     283                  $o->internal = (bool)$observer['internal'];
     284              }
     285              if (empty($observer['includefile'])) {
     286                  $o->includefile = null;
     287              } else {
     288                  if ($CFG->admin !== 'admin' and strpos($observer['includefile'], '/admin/') === 0) {
     289                      $observer['includefile'] = preg_replace('|^/admin/|', '/'.$CFG->admin.'/', $observer['includefile']);
     290                  }
     291                  $observer['includefile'] = $CFG->dirroot . '/' . ltrim($observer['includefile'], '/');
     292                  if (!file_exists($observer['includefile'])) {
     293                      debugging("Invalid 'includefile' detected in $file observer definition", DEBUG_DEVELOPER);
     294                      continue;
     295                  }
     296                  $o->includefile = $observer['includefile'];
     297              }
     298              $o->plugintype = $plugintype;
     299              $o->plugin = $plugin;
     300              self::$allobservers[$observer['eventname']][] = $o;
     301          }
     302      }
     303  
     304      /**
     305       * Reorder observers to allow quick lookup of observer for each event.
     306       */
     307      protected static function order_all_observers() {
     308          foreach (self::$allobservers as $classname => $observers) {
     309              \core_collator::asort_objects_by_property($observers, 'priority', \core_collator::SORT_NUMERIC);
     310              self::$allobservers[$classname] = array_reverse($observers);
     311          }
     312      }
     313  
     314      /**
     315       * Returns all observers in the system. This is only for use for reporting on the list of observers in the system.
     316       *
     317       * @access private
     318       * @return array An array of stdClass with all core observer details.
     319       */
     320      public static function get_all_observers() {
     321          self::init_all_observers();
     322          return self::$allobservers;
     323      }
     324  
     325      /**
     326       * Replace all standard observers.
     327       * @param array $observers
     328       * @return array
     329       *
     330       * @throws \coding_Exception if used outside of unit tests.
     331       */
     332      public static function phpunit_replace_observers(array $observers) {
     333          if (!PHPUNIT_TEST) {
     334              throw new \coding_exception('Cannot override event observers outside of phpunit tests!');
     335          }
     336  
     337          self::phpunit_reset();
     338          self::$allobservers = array();
     339          self::$reloadaftertest = true;
     340  
     341          self::add_observers($observers, 'phpunit');
     342          self::order_all_observers();
     343  
     344          return self::$allobservers;
     345      }
     346  
     347      /**
     348       * Reset everything if necessary.
     349       * @private
     350       *
     351       * @throws \coding_Exception if used outside of unit tests.
     352       */
     353      public static function phpunit_reset() {
     354          if (!PHPUNIT_TEST) {
     355              throw new \coding_exception('Cannot reset event manager outside of phpunit tests!');
     356          }
     357          self::$buffer = array();
     358          self::$extbuffer = array();
     359          self::$dispatching = false;
     360          if (!self::$reloadaftertest) {
     361              self::$allobservers = null;
     362          }
     363          self::$reloadaftertest = false;
     364      }
     365  }
    

    Search This Site: