[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> eventslib.php (source)

   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  /**
  18   * Library of functions for events manipulation.
  19   *
  20   * The public API is all at the end of this file.
  21   *
  22   * @package core
  23   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  24   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Loads the events definitions for the component (from file). If no
  31   * events are defined for the component, we simply return an empty array.
  32   *
  33   * @access protected To be used from eventslib only
  34   *
  35   * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
  36   * @return array Array of capabilities or empty array if not exists
  37   */
  38  function events_load_def($component) {
  39      global $CFG;
  40      if ($component === 'unittest') {
  41          $defpath = $CFG->dirroot.'/lib/tests/fixtures/events.php';
  42      } else {
  43          $defpath = core_component::get_component_directory($component).'/db/events.php';
  44      }
  45  
  46      $handlers = array();
  47  
  48      if (file_exists($defpath)) {
  49          require($defpath);
  50      }
  51  
  52      // make sure the definitions are valid and complete; tell devs what is wrong
  53      foreach ($handlers as $eventname => $handler) {
  54          if ($eventname === 'reset') {
  55              debugging("'reset' can not be used as event name.");
  56              unset($handlers['reset']);
  57              continue;
  58          }
  59          if (!is_array($handler)) {
  60              debugging("Handler of '$eventname' must be specified as array'");
  61              unset($handlers[$eventname]);
  62              continue;
  63          }
  64          if (!isset($handler['handlerfile'])) {
  65              debugging("Handler of '$eventname' must include 'handlerfile' key'");
  66              unset($handlers[$eventname]);
  67              continue;
  68          }
  69          if (!isset($handler['handlerfunction'])) {
  70              debugging("Handler of '$eventname' must include 'handlerfunction' key'");
  71              unset($handlers[$eventname]);
  72              continue;
  73          }
  74          if (!isset($handler['schedule'])) {
  75              $handler['schedule'] = 'instant';
  76          }
  77          if ($handler['schedule'] !== 'instant' and $handler['schedule'] !== 'cron') {
  78              debugging("Handler of '$eventname' must include valid 'schedule' type (instant or cron)'");
  79              unset($handlers[$eventname]);
  80              continue;
  81          }
  82          if (!isset($handler['internal'])) {
  83              $handler['internal'] = 1;
  84          }
  85          $handlers[$eventname] = $handler;
  86      }
  87  
  88      return $handlers;
  89  }
  90  
  91  /**
  92   * Gets the capabilities that have been cached in the database for this
  93   * component.
  94   *
  95   * @access protected To be used from eventslib only
  96   *
  97   * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
  98   * @return array of events
  99   */
 100  function events_get_cached($component) {
 101      global $DB;
 102  
 103      $cachedhandlers = array();
 104  
 105      if ($storedhandlers = $DB->get_records('events_handlers', array('component'=>$component))) {
 106          foreach ($storedhandlers as $handler) {
 107              $cachedhandlers[$handler->eventname] = array (
 108                  'id'              => $handler->id,
 109                  'handlerfile'     => $handler->handlerfile,
 110                  'handlerfunction' => $handler->handlerfunction,
 111                  'schedule'        => $handler->schedule,
 112                  'internal'        => $handler->internal);
 113          }
 114      }
 115  
 116      return $cachedhandlers;
 117  }
 118  
 119  /**
 120   * Updates all of the event definitions within the database.
 121   *
 122   * Unfortunately this isn't as simple as removing them all and then readding
 123   * the updated event definitions. Chances are queued items are referencing the
 124   * existing definitions.
 125   *
 126   * Note that the absence of the db/events.php event definition file
 127   * will cause any queued events for the component to be removed from
 128   * the database.
 129   *
 130   * @category event
 131   * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
 132   * @return boolean always returns true
 133   */
 134  function events_update_definition($component='moodle') {
 135      global $DB;
 136  
 137      // load event definition from events.php
 138      $filehandlers = events_load_def($component);
 139  
 140      // load event definitions from db tables
 141      // if we detect an event being already stored, we discard from this array later
 142      // the remaining needs to be removed
 143      $cachedhandlers = events_get_cached($component);
 144  
 145      foreach ($filehandlers as $eventname => $filehandler) {
 146          if (!empty($cachedhandlers[$eventname])) {
 147              if ($cachedhandlers[$eventname]['handlerfile'] === $filehandler['handlerfile'] &&
 148                  $cachedhandlers[$eventname]['handlerfunction'] === serialize($filehandler['handlerfunction']) &&
 149                  $cachedhandlers[$eventname]['schedule'] === $filehandler['schedule'] &&
 150                  $cachedhandlers[$eventname]['internal'] == $filehandler['internal']) {
 151                  // exact same event handler already present in db, ignore this entry
 152  
 153                  unset($cachedhandlers[$eventname]);
 154                  continue;
 155  
 156              } else {
 157                  // same event name matches, this event has been updated, update the datebase
 158                  $handler = new stdClass();
 159                  $handler->id              = $cachedhandlers[$eventname]['id'];
 160                  $handler->handlerfile     = $filehandler['handlerfile'];
 161                  $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
 162                  $handler->schedule        = $filehandler['schedule'];
 163                  $handler->internal        = $filehandler['internal'];
 164  
 165                  $DB->update_record('events_handlers', $handler);
 166  
 167                  unset($cachedhandlers[$eventname]);
 168                  continue;
 169              }
 170  
 171          } else {
 172              // if we are here, this event handler is not present in db (new)
 173              // add it
 174              $handler = new stdClass();
 175              $handler->eventname       = $eventname;
 176              $handler->component       = $component;
 177              $handler->handlerfile     = $filehandler['handlerfile'];
 178              $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
 179              $handler->schedule        = $filehandler['schedule'];
 180              $handler->status          = 0;
 181              $handler->internal        = $filehandler['internal'];
 182  
 183              $DB->insert_record('events_handlers', $handler);
 184          }
 185      }
 186  
 187      // clean up the left overs, the entries in cached events array at this points are deprecated event handlers
 188      // and should be removed, delete from db
 189      events_cleanup($component, $cachedhandlers);
 190  
 191      events_get_handlers('reset');
 192  
 193      return true;
 194  }
 195  
 196  /**
 197   * Remove all event handlers and queued events
 198   *
 199   * @category event
 200   * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
 201   */
 202  function events_uninstall($component) {
 203      $cachedhandlers = events_get_cached($component);
 204      events_cleanup($component, $cachedhandlers);
 205  
 206      events_get_handlers('reset');
 207  }
 208  
 209  /**
 210   * Deletes cached events that are no longer needed by the component.
 211   *
 212   * @access protected To be used from eventslib only
 213   *
 214   * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
 215   * @param array $cachedhandlers array of the cached events definitions that will be
 216   * @return int number of unused handlers that have been removed
 217   */
 218  function events_cleanup($component, $cachedhandlers) {
 219      global $DB;
 220  
 221      $deletecount = 0;
 222      foreach ($cachedhandlers as $eventname => $cachedhandler) {
 223          if ($qhandlers = $DB->get_records('events_queue_handlers', array('handlerid'=>$cachedhandler['id']))) {
 224              //debugging("Removing pending events from queue before deleting of event handler: $component - $eventname");
 225              foreach ($qhandlers as $qhandler) {
 226                  events_dequeue($qhandler);
 227              }
 228          }
 229          $DB->delete_records('events_handlers', array('eventname'=>$eventname, 'component'=>$component));
 230          $deletecount++;
 231      }
 232  
 233      return $deletecount;
 234  }
 235  
 236  /****************** End of Events handler Definition code *******************/
 237  
 238  /**
 239   * Puts a handler on queue
 240   *
 241   * @access protected To be used from eventslib only
 242   *
 243   * @param stdClass $handler event handler object from db
 244   * @param stdClass $event event data object
 245   * @param string $errormessage The error message indicating the problem
 246   * @return int id number of new queue handler
 247   */
 248  function events_queue_handler($handler, $event, $errormessage) {
 249      global $DB;
 250  
 251      if ($qhandler = $DB->get_record('events_queue_handlers', array('queuedeventid'=>$event->id, 'handlerid'=>$handler->id))) {
 252          debugging("Please check code: Event id $event->id is already queued in handler id $qhandler->id");
 253          return $qhandler->id;
 254      }
 255  
 256      // make a new queue handler
 257      $qhandler = new stdClass();
 258      $qhandler->queuedeventid  = $event->id;
 259      $qhandler->handlerid      = $handler->id;
 260      $qhandler->errormessage   = $errormessage;
 261      $qhandler->timemodified   = time();
 262      if ($handler->schedule === 'instant' and $handler->status == 1) {
 263          $qhandler->status     = 1; //already one failed attempt to dispatch this event
 264      } else {
 265          $qhandler->status     = 0;
 266      }
 267  
 268      return $DB->insert_record('events_queue_handlers', $qhandler);
 269  }
 270  
 271  /**
 272   * trigger a single event with a specified handler
 273   *
 274   * @access protected To be used from eventslib only
 275   *
 276   * @param stdClass $handler This shoudl be a row from the events_handlers table.
 277   * @param stdClass $eventdata An object containing information about the event
 278   * @param string $errormessage error message indicating problem
 279   * @return bool|null True means event processed, false means retry event later; may throw exception, NULL means internal error
 280   */
 281  function events_dispatch($handler, $eventdata, &$errormessage) {
 282      global $CFG;
 283  
 284      $function = unserialize($handler->handlerfunction);
 285  
 286      if (is_callable($function)) {
 287          // oki, no need for includes
 288  
 289      } else if (file_exists($CFG->dirroot.$handler->handlerfile)) {
 290          include_once($CFG->dirroot.$handler->handlerfile);
 291  
 292      } else {
 293          $errormessage = "Handler file of component $handler->component: $handler->handlerfile can not be found!";
 294          return null;
 295      }
 296  
 297      // checks for handler validity
 298      if (is_callable($function)) {
 299          $result = call_user_func($function, $eventdata);
 300          if ($result === false) {
 301              $errormessage = "Handler function of component $handler->component: $handler->handlerfunction requested resending of event!";
 302              return false;
 303          }
 304          return true;
 305  
 306      } else {
 307          $errormessage = "Handler function of component $handler->component: $handler->handlerfunction not callable function or class method!";
 308          return null;
 309      }
 310  }
 311  
 312  /**
 313   * given a queued handler, call the respective event handler to process the event
 314   *
 315   * @access protected To be used from eventslib only
 316   *
 317   * @param stdClass $qhandler events_queued_handler row from db
 318   * @return boolean true means event processed, false means retry later, NULL means fatal failure
 319   */
 320  function events_process_queued_handler($qhandler) {
 321      global $DB;
 322  
 323      // get handler
 324      if (!$handler = $DB->get_record('events_handlers', array('id'=>$qhandler->handlerid))) {
 325          debugging("Error processing queue handler $qhandler->id, missing handler id: $qhandler->handlerid");
 326          //irrecoverable error, remove broken queue handler
 327          events_dequeue($qhandler);
 328          return NULL;
 329      }
 330  
 331      // get event object
 332      if (!$event = $DB->get_record('events_queue', array('id'=>$qhandler->queuedeventid))) {
 333          // can't proceed with no event object - might happen when two crons running at the same time
 334          debugging("Error processing queue handler $qhandler->id, missing event id: $qhandler->queuedeventid");
 335          //irrecoverable error, remove broken queue handler
 336          events_dequeue($qhandler);
 337          return NULL;
 338      }
 339  
 340      // call the function specified by the handler
 341      try {
 342          $errormessage = 'Unknown error';
 343          if (events_dispatch($handler, unserialize(base64_decode($event->eventdata)), $errormessage)) {
 344              //everything ok
 345              events_dequeue($qhandler);
 346              return true;
 347          }
 348      } catch (Exception $e) {
 349          // the problem here is that we do not want one broken handler to stop all others,
 350          // cron handlers are very tricky because the needed data might have been deleted before the cron execution
 351          $errormessage = "Handler function of component $handler->component: $handler->handlerfunction threw exception :" .
 352                  $e->getMessage() . "\n" . format_backtrace($e->getTrace(), true);
 353          if (!empty($e->debuginfo)) {
 354              $errormessage .= $e->debuginfo;
 355          }
 356      }
 357  
 358      //dispatching failed
 359      $qh = new stdClass();
 360      $qh->id           = $qhandler->id;
 361      $qh->errormessage = $errormessage;
 362      $qh->timemodified = time();
 363      $qh->status       = $qhandler->status + 1;
 364      $DB->update_record('events_queue_handlers', $qh);
 365  
 366      debugging($errormessage);
 367  
 368      return false;
 369  }
 370  
 371  /**
 372   * Removes this queued handler from the events_queued_handler table
 373   *
 374   * Removes events_queue record from events_queue if no more references to this event object exists
 375   *
 376   * @access protected To be used from eventslib only
 377   *
 378   * @param stdClass $qhandler A row from the events_queued_handler table
 379   */
 380  function events_dequeue($qhandler) {
 381      global $DB;
 382  
 383      // first delete the queue handler
 384      $DB->delete_records('events_queue_handlers', array('id'=>$qhandler->id));
 385  
 386      // if no more queued handler is pointing to the same event - delete the event too
 387      if (!$DB->record_exists('events_queue_handlers', array('queuedeventid'=>$qhandler->queuedeventid))) {
 388          $DB->delete_records('events_queue', array('id'=>$qhandler->queuedeventid));
 389      }
 390  }
 391  
 392  /**
 393   * Returns handlers for given event. Uses caching for better perf.
 394   *
 395   * @access protected To be used from eventslib only
 396   *
 397   * @staticvar array $handlers
 398   * @param string $eventname name of event or 'reset'
 399   * @return array|false array of handlers or false otherwise
 400   */
 401  function events_get_handlers($eventname) {
 402      global $DB;
 403      static $handlers = array();
 404  
 405      if ($eventname === 'reset') {
 406          $handlers = array();
 407          return false;
 408      }
 409  
 410      if (!array_key_exists($eventname, $handlers)) {
 411          $handlers[$eventname] = $DB->get_records('events_handlers', array('eventname'=>$eventname));
 412      }
 413  
 414      return $handlers[$eventname];
 415  }
 416  
 417  /**
 418   * Events cron will try to empty the events queue by processing all the queued events handlers
 419   *
 420   * @access public Part of the public API
 421   * @category event
 422   * @param string $eventname empty means all
 423   * @return int number of dispatched events
 424   */
 425  function events_cron($eventname='') {
 426      global $DB;
 427  
 428      $failed = array();
 429      $processed = 0;
 430  
 431      if ($eventname) {
 432          $sql = "SELECT qh.*
 433                    FROM {events_queue_handlers} qh, {events_handlers} h
 434                   WHERE qh.handlerid = h.id AND h.eventname=?
 435                ORDER BY qh.id";
 436          $params = array($eventname);
 437      } else {
 438          $sql = "SELECT *
 439                    FROM {events_queue_handlers}
 440                ORDER BY id";
 441          $params = array();
 442      }
 443  
 444      $rs = $DB->get_recordset_sql($sql, $params);
 445      foreach ($rs as $qhandler) {
 446          if (isset($failed[$qhandler->handlerid])) {
 447              // do not try to dispatch any later events when one already asked for retry or ended with exception
 448              continue;
 449          }
 450          $status = events_process_queued_handler($qhandler);
 451          if ($status === false) {
 452              // handler is asking for retry, do not send other events to this handler now
 453              $failed[$qhandler->handlerid] = $qhandler->handlerid;
 454          } else if ($status === NULL) {
 455              // means completely broken handler, event data was purged
 456              $failed[$qhandler->handlerid] = $qhandler->handlerid;
 457          } else {
 458              $processed++;
 459          }
 460      }
 461      $rs->close();
 462  
 463      // remove events that do not have any handlers waiting
 464      $sql = "SELECT eq.id
 465                FROM {events_queue} eq
 466                LEFT JOIN {events_queue_handlers} qh ON qh.queuedeventid = eq.id
 467               WHERE qh.id IS NULL";
 468      $rs = $DB->get_recordset_sql($sql);
 469      foreach ($rs as $event) {
 470          //debugging('Purging stale event '.$event->id);
 471          $DB->delete_records('events_queue', array('id'=>$event->id));
 472      }
 473      $rs->close();
 474  
 475      return $processed;
 476  }
 477  
 478  /**
 479   * Do not call directly, this is intended to be used from new event base only.
 480   *
 481   * @private
 482   * @param string $eventname name of the event
 483   * @param mixed $eventdata event data object
 484   * @return int number of failed events
 485   */
 486  function events_trigger_legacy($eventname, $eventdata) {
 487      global $CFG, $USER, $DB;
 488  
 489      $failedcount = 0; // number of failed events.
 490  
 491      // pull out all registered event handlers
 492      if ($handlers = events_get_handlers($eventname)) {
 493          foreach ($handlers as $handler) {
 494              $errormessage = '';
 495  
 496              if ($handler->schedule === 'instant') {
 497                  if ($handler->status) {
 498                      //check if previous pending events processed
 499                      if (!$DB->record_exists('events_queue_handlers', array('handlerid'=>$handler->id))) {
 500                          // ok, queue is empty, lets reset the status back to 0 == ok
 501                          $handler->status = 0;
 502                          $DB->set_field('events_handlers', 'status', 0, array('id'=>$handler->id));
 503                          // reset static handler cache
 504                          events_get_handlers('reset');
 505                      }
 506                  }
 507  
 508                  // dispatch the event only if instant schedule and status ok
 509                  if ($handler->status or (!$handler->internal and $DB->is_transaction_started())) {
 510                      // increment the error status counter
 511                      $handler->status++;
 512                      $DB->set_field('events_handlers', 'status', $handler->status, array('id'=>$handler->id));
 513                      // reset static handler cache
 514                      events_get_handlers('reset');
 515  
 516                  } else {
 517                      $errormessage = 'Unknown error';
 518                      $result = events_dispatch($handler, $eventdata, $errormessage);
 519                      if ($result === true) {
 520                          // everything is fine - event dispatched
 521                          continue;
 522                      } else if ($result === false) {
 523                          // retry later - set error count to 1 == send next instant into cron queue
 524                          $DB->set_field('events_handlers', 'status', 1, array('id'=>$handler->id));
 525                          // reset static handler cache
 526                          events_get_handlers('reset');
 527                      } else {
 528                          // internal problem - ignore the event completely
 529                          $failedcount ++;
 530                          continue;
 531                      }
 532                  }
 533  
 534                  // update the failed counter
 535                  $failedcount ++;
 536  
 537              } else if ($handler->schedule === 'cron') {
 538                  //ok - use queueing of events only
 539  
 540              } else {
 541                  // unknown schedule - ignore event completely
 542                  debugging("Unknown handler schedule type: $handler->schedule");
 543                  $failedcount ++;
 544                  continue;
 545              }
 546  
 547              // if even type is not instant, or dispatch asked for retry, queue it
 548              $event = new stdClass();
 549              $event->userid      = $USER->id;
 550              $event->eventdata   = base64_encode(serialize($eventdata));
 551              $event->timecreated = time();
 552              if (debugging()) {
 553                  $dump = '';
 554                  $callers = debug_backtrace();
 555                  foreach ($callers as $caller) {
 556                      if (!isset($caller['line'])) {
 557                          $caller['line'] = '?';
 558                      }
 559                      if (!isset($caller['file'])) {
 560                          $caller['file'] = '?';
 561                      }
 562                      $dump .= 'line ' . $caller['line'] . ' of ' . substr($caller['file'], strlen($CFG->dirroot) + 1);
 563                      if (isset($caller['function'])) {
 564                          $dump .= ': call to ';
 565                          if (isset($caller['class'])) {
 566                              $dump .= $caller['class'] . $caller['type'];
 567                          }
 568                          $dump .= $caller['function'] . '()';
 569                      }
 570                      $dump .= "\n";
 571                  }
 572                  $event->stackdump = $dump;
 573              } else {
 574                  $event->stackdump = '';
 575              }
 576              $event->id = $DB->insert_record('events_queue', $event);
 577              events_queue_handler($handler, $event, $errormessage);
 578          }
 579      } else {
 580          // No handler found for this event name - this is ok!
 581      }
 582  
 583      return $failedcount;
 584  }
 585  
 586  /**
 587   * checks if an event is registered for this component
 588   *
 589   * @access public Part of the public API
 590   *
 591   * @param string $eventname name of the event
 592   * @param string $component component name, can be mod/data or moodle
 593   * @return bool
 594   */
 595  function events_is_registered($eventname, $component) {
 596      global $DB;
 597      return $DB->record_exists('events_handlers', array('component'=>$component, 'eventname'=>$eventname));
 598  }
 599  
 600  /**
 601   * checks if an event is queued for processing - either cron handlers attached or failed instant handlers
 602   *
 603   * @access public Part of the public API
 604   *
 605   * @param string $eventname name of the event
 606   * @return int number of queued events
 607   */
 608  function events_pending_count($eventname) {
 609      global $DB;
 610  
 611      $sql = "SELECT COUNT('x')
 612                FROM {events_queue_handlers} qh
 613                JOIN {events_handlers} h ON h.id = qh.handlerid
 614               WHERE h.eventname = ?";
 615  
 616      return $DB->count_records_sql($sql, array($eventname));
 617  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1