[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/ -> eventslib.php (source)

   1  <?php
   2  /**
   3   * Library of functions for events manipulation.
   4   * 
   5   * The public API is all at the end of this file.
   6   *
   7   * @author Martin Dougiamas and many others
   8   * @version $Id: eventslib.php,v 1.16.2.3 2008/10/06 22:07:10 skodak Exp $
   9   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  10   * @package moodlecore
  11   */
  12  
  13  
  14  /**
  15   * Loads the events definitions for the component (from file). If no
  16   * events are defined for the component, we simply return an empty array.
  17   * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
  18   * @return array of capabilities or empty array if not exists
  19   *
  20   * INTERNAL - to be used from eventslib only
  21   */
  22  function events_load_def($component) {
  23      global $CFG;
  24  
  25      if ($component == 'moodle') {
  26          $defpath = $CFG->libdir.'/db/events.php';
  27  
  28      } else if ($component == 'unittest') {
  29          $defpath = $CFG->libdir.'/simpletest/fixtures/events.php';
  30  
  31      } else {
  32          $compparts = explode('/', $component);
  33  
  34          if ($compparts[0] == 'block') {
  35              // Blocks are an exception. Blocks directory is 'blocks', and not
  36              // 'block'. So we need to jump through hoops.
  37              $defpath = $CFG->dirroot.'/blocks/'.$compparts[1].'/db/events.php';
  38  
  39          } else if ($compparts[0] == 'format') {
  40              // Similar to the above, course formats are 'format' while they
  41              // are stored in 'course/format'.
  42              $defpath = $CFG->dirroot.'/course/format/'.$compparts[1].'/db/events.php';
  43  
  44          } else if ($compparts[0] == 'gradeimport') {
  45              $defpath = $CFG->dirroot.'/grade/import/'.$compparts[1].'/db/events.php';  
  46          
  47          } else if ($compparts[0] == 'gradeexport') {
  48              $defpath = $CFG->dirroot.'/grade/export/'.$compparts[1].'/db/events.php'; 
  49          
  50          } else if ($compparts[0] == 'gradereport') {
  51              $defpath = $CFG->dirroot.'/grade/report/'.$compparts[1].'/db/events.php'; 
  52          
  53          } else {
  54              $defpath = $CFG->dirroot.'/'.$component.'/db/events.php';
  55          }
  56      }
  57  
  58      $handlers = array();
  59  
  60      if (file_exists($defpath)) {
  61          require($defpath);
  62      }
  63  
  64      return $handlers;
  65  }
  66  
  67  /**
  68   * Gets the capabilities that have been cached in the database for this
  69   * component.
  70   * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
  71   * @return array of events
  72   *
  73   * INTERNAL - to be used from eventslib only
  74   */
  75  function events_get_cached($component) {
  76      $cachedhandlers = array();
  77  
  78      if ($storedhandlers = get_records('events_handlers', 'handlermodule', $component)) {
  79          foreach ($storedhandlers as $handler) {
  80              $cachedhandlers[$handler->eventname] = array (
  81                  'id'              => $handler->id,
  82                  'handlerfile'     => $handler->handlerfile,
  83                  'handlerfunction' => $handler->handlerfunction,
  84                  'schedule'        => $handler->schedule);
  85          }
  86      }
  87  
  88      return $cachedhandlers;
  89  }
  90  
  91  /**
  92   * We can not removed all event handlers in table, then add them again
  93   * because event handlers could be referenced by queued items
  94   *
  95   * Note that the absence of the db/events.php event definition file
  96   * will cause any queued events for the component to be removed from
  97   * the database.
  98   *
  99   * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
 100   * @return boolean
 101   */
 102  function events_update_definition($component='moodle') {
 103  
 104      // load event definition from events.php
 105      $filehandlers = events_load_def($component);
 106  
 107      // load event definitions from db tables
 108      // if we detect an event being already stored, we discard from this array later
 109      // the remaining needs to be removed
 110      $cachedhandlers = events_get_cached($component);
 111  
 112      foreach ($filehandlers as $eventname => $filehandler) {
 113          if (!empty($cachedhandlers[$eventname])) {
 114              if ($cachedhandlers[$eventname]['handlerfile'] == $filehandler['handlerfile'] &&
 115                  $cachedhandlers[$eventname]['handlerfunction'] == serialize($filehandler['handlerfunction']) &&
 116                  $cachedhandlers[$eventname]['schedule'] == $filehandler['schedule']) {
 117                  // exact same event handler already present in db, ignore this entry
 118  
 119                  unset($cachedhandlers[$eventname]);
 120                  continue;
 121  
 122              } else {
 123                  // same event name matches, this event has been updated, update the datebase
 124                  $handler = new object();
 125                  $handler->id              = $cachedhandlers[$eventname]['id'];
 126                  $handler->handlerfile     = $filehandler['handlerfile'];
 127                  $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
 128                  $handler->schedule        = $filehandler['schedule'];
 129  
 130                  update_record('events_handlers', $handler);
 131  
 132                  unset($cachedhandlers[$eventname]);
 133                  continue;
 134              }
 135  
 136          } else {
 137              // if we are here, this event handler is not present in db (new)
 138              // add it
 139              $handler = new object();
 140              $handler->eventname       = $eventname;
 141              $handler->handlermodule   = $component;
 142              $handler->handlerfile     = $filehandler['handlerfile'];
 143              $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
 144              $handler->schedule        = $filehandler['schedule'];
 145  
 146              insert_record('events_handlers', $handler);
 147          }
 148      }
 149  
 150      // clean up the left overs, the entries in cachedevents array at this points are deprecated event handlers
 151      // and should be removed, delete from db
 152      events_cleanup($component, $cachedhandlers);
 153  
 154      return true;
 155  }
 156  
 157  /**
 158   * Remove all event handlers and queued events
 159   * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
 160   */
 161  function events_uninstall($component) {
 162      $cachedhandlers = events_get_cached($component);
 163      events_cleanup($component, $cachedhandlers);
 164  }
 165  
 166  /**
 167   * Deletes cached events that are no longer needed by the component.
 168   * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
 169   * @param $chachedevents - array of the cached events definitions that will be
 170   * @return int - number of deprecated capabilities that have been removed
 171   *
 172   * INTERNAL - to be used from eventslib only
 173   */
 174  function events_cleanup($component, $cachedhandlers) {
 175      $deletecount = 0;
 176      foreach ($cachedhandlers as $eventname => $cachedhandler) {
 177          if ($qhandlers = get_records('events_queue_handlers', 'handlerid', $cachedhandler['id'])) {
 178              debugging("Removing pending events from queue before deleting of event handler: $component - $eventname");
 179              foreach ($qhandlers as $qhandler) {
 180                  events_dequeue($qhandler);
 181              }
 182          }
 183          if (delete_records('events_handlers', 'eventname', $eventname, 'handlermodule', $component)) {
 184              $deletecount++;
 185          }
 186      }
 187  
 188      // reset static handler cache
 189      events_get_handlers('reset');
 190  
 191      return $deletecount;
 192  }
 193  
 194  /****************** End of Events handler Definition code *******************/
 195  
 196  /**
 197   * puts a handler on queue
 198   * @param object handler - event handler object from db
 199   * @param object eventdata - event data object
 200   * @return id number of new queue handler
 201   *
 202   * INTERNAL - to be used from eventslib only
 203   */
 204  function events_queue_handler($handler, $event, $errormessage) {
 205  
 206      if ($qhandler = get_record('events_queue_handlers', 'queuedeventid', $event->id, 'handlerid', $handler->id)) {
 207          debugging("Please check code: Event id $event->id is already queued in handler id $qhandler->id");
 208          return $qhandler->id;
 209      }
 210  
 211      // make a new queue handler
 212      $qhandler = new object();
 213      $qhandler->queuedeventid  = $event->id;
 214      $qhandler->handlerid      = $handler->id;
 215      $qhandler->errormessage   = addslashes($errormessage);
 216      $qhandler->timemodified   = time();
 217      if ($handler->schedule == 'instant' and $handler->status == 1) {
 218          $qhandler->status     = 1; //already one failed attempt to dispatch this event
 219      } else {
 220          $qhandler->status     = 0;
 221      }
 222  
 223      return insert_record('events_queue_handlers', $qhandler);
 224  }
 225  
 226  /**
 227   * trigger a single event with a specified handler
 228   * @param handler - hander object from db
 229   * @param eventdata - event dataobject
 230   * @param errormessage - error message indicating problem
 231   * @return bool - success or fail
 232   *
 233   * INTERNAL - to be used from eventslib only
 234   */
 235  function events_dispatch($handler, $eventdata, &$errormessage) {
 236      global $CFG;
 237  
 238      $function = unserialize($handler->handlerfunction);
 239  
 240      if (is_callable($function)) {
 241          // oki, no need for includes
 242  
 243      } else if (file_exists($CFG->dirroot.$handler->handlerfile)) {
 244          include_once($CFG->dirroot.$handler->handlerfile);
 245  
 246      } else {
 247          $errormessage = "Handler file of component $handler->handlermodule: $handler->handlerfile can not be found!";
 248          return false;
 249      }
 250  
 251      // checks for handler validity
 252      if (is_callable($function)) {
 253          return call_user_func($function, $eventdata);
 254  
 255      } else {
 256          $errormessage = "Handler function of component $handler->handlermodule: $handler->handlerfunction not callable function or class method!";
 257          return false;
 258      }
 259  }
 260  
 261  /**
 262   * given a queued handler, call the respective event handler to process the event
 263   * @param object qhandler - events_queued_handler object from db
 264   * @return boolean meaning success, or NULL on fatal failure
 265   *
 266   * INTERNAL - to be used from eventslib only
 267   */
 268  function events_process_queued_handler($qhandler) {
 269      global $CFG;
 270  
 271      // get handler
 272      if (!$handler = get_record('events_handlers', 'id', $qhandler->handlerid)) {
 273          debugging("Error processing queue handler $qhandler->id, missing handler id: $qhandler->handlerid");
 274          //irrecoverable error, remove broken queue handler
 275          events_dequeue($qhandler);
 276          return NULL;
 277      }
 278  
 279      // get event object
 280      if (!$event = get_record('events_queue', 'id', $qhandler->queuedeventid)) {
 281          // can't proceed with no event object - might happen when two crons running at the same time
 282          debugging("Error processing queue handler $qhandler->id, missing event id: $qhandler->queuedeventid");
 283          //irrecoverable error, remove broken queue handler
 284          events_dequeue($qhandler);
 285          return NULL;
 286      }
 287  
 288      // call the function specified by the handler
 289      $errormessage = 'Unknown error';
 290      if (events_dispatch($handler, unserialize($event->eventdata), $errormessage)) {
 291          //everything ok
 292          events_dequeue($qhandler);
 293          return true;
 294  
 295      } else {
 296          //dispatching failed
 297          $qh = new object();
 298          $qh->id           = $qhandler->id;
 299          $qh->errormessage = addslashes($errormessage);
 300          $qh->timemodified = time();
 301          $qh->status       = $qhandler->status + 1;
 302          update_record('events_queue_handlers', $qh);
 303          return false;
 304      }
 305  }
 306  
 307  /**
 308   * removes this queued handler from the events_queued_handler table
 309   * removes events_queue record from events_queue if no more references to this event object exists
 310   * @param object qhandler - events_queued_handler object from db
 311   *
 312   * INTERNAL - to be used from eventslib only
 313   */
 314  function events_dequeue($qhandler) {
 315      // first delete the queue handler
 316      delete_records('events_queue_handlers', 'id', $qhandler->id);
 317  
 318      // if no more queued handler is pointing to the same event - delete the event too
 319      if (!record_exists('events_queue_handlers', 'queuedeventid', $qhandler->queuedeventid)) {
 320          delete_records('events_queue', 'id', $qhandler->queuedeventid);
 321      }
 322  }
 323  
 324  /**
 325   * Returns hanflers for given event. Uses caching for better perf.
 326   * @param string $eventanme name of even or 'reset'
 327   * @return mixed array of handlers or false otherwise
 328   *
 329   * INTERNAL - to be used from eventslib only
 330   */
 331  function events_get_handlers($eventname) {
 332      static $handlers = array();
 333  
 334      if ($eventname == 'reset') {
 335          $handlers = array();
 336          return false;
 337      }
 338  
 339      if (!array_key_exists($eventname, $handlers)) {
 340          $handlers[$eventname] = get_records('events_handlers', 'eventname', $eventname);
 341      }
 342  
 343      return $handlers[$eventname];
 344  }
 345  
 346  /****** Public events API starts here, do not use functions above in 3rd party code ******/
 347  
 348  
 349  /**
 350   * Events cron will try to empty the events queue by processing all the queued events handlers
 351   * @param string eventname - empty means all
 352   * @return number of dispatched+removed broken events
 353   *
 354   * PUBLIC
 355   */
 356  function events_cron($eventname='') {
 357      global $CFG;
 358  
 359      $failed = array();
 360      $processed = 0;
 361  
 362      if ($eventname) {
 363          $sql = "SELECT qh.* FROM {$CFG->prefix}events_queue_handlers qh, {$CFG->prefix}events_handlers h
 364                  WHERE qh.handlerid = h.id AND h.eventname='$eventname'
 365                  ORDER BY qh.id";
 366      } else {
 367          $sql = "SELECT * FROM {$CFG->prefix}events_queue_handlers
 368                  ORDER BY id";
 369      }
 370  
 371      if ($rs = get_recordset_sql($sql)) {
 372          while ($qhandler = rs_fetch_next_record($rs)) {
 373              if (in_array($qhandler->handlerid, $failed)) {
 374                  // do not try to dispatch any later events when one already failed
 375                  continue;
 376              }
 377              $status = events_process_queued_handler($qhandler);
 378              if ($status === false) {
 379                  $failed[] = $qhandler->handlerid;
 380              } else {
 381                  $processed++;
 382              }
 383          }
 384          rs_close($rs);
 385      }
 386      return $processed;
 387  }
 388  
 389  
 390  /**
 391   * Function to call all eventhandlers when triggering an event
 392   * @param eventname - name of the event
 393   * @param eventdata - event data object (without magic quotes)
 394   * @return number of failed events
 395   *
 396   * PUBLIC
 397   */
 398  function events_trigger($eventname, $eventdata) {
 399      global $CFG, $USER;
 400  
 401      $failedcount = 0; // number of failed events.
 402      $event = false;
 403  
 404      // pull out all registered event handlers
 405      if ($handlers = events_get_handlers($eventname)) {
 406          foreach ($handlers as $handler) {
 407  
 408             $errormessage = '';
 409  
 410             if ($handler->schedule == 'instant') {
 411                  if ($handler->status) {
 412                      //check if previous pending events processed
 413                      if (!record_exists('events_queue_handlers', 'handlerid', $handler->id)) {
 414                          // ok, queue is empty, lets reset the status back to 0 == ok
 415                          $handler->status = 0;
 416                          set_field('events_handlers', 'status', 0, 'id', $handler->id);
 417                          // reset static handler cache
 418                          events_get_handlers('reset');
 419                      }
 420                  }
 421  
 422                  // dispatch the event only if instant schedule and status ok
 423                  if (!$handler->status) {
 424                      $errormessage = 'Unknown error';;
 425                      if (events_dispatch($handler, $eventdata, $errormessage)) {
 426                          continue;
 427                      }
 428                      // set error count to 1 == send next instant into cron queue
 429                      set_field('events_handlers', 'status', 1, 'id', $handler->id);
 430                      // reset static handler cache
 431                      events_get_handlers('reset');
 432  
 433                  } else {
 434                      // increment the error status counter
 435                      $handler->status++;
 436                      set_field('events_handlers', 'status', $handler->status, 'id', $handler->id);
 437                      // reset static handler cache
 438                      events_get_handlers('reset');
 439                  }
 440  
 441                  // update the failed counter
 442                  $failedcount ++;
 443  
 444              } else if ($handler->schedule == 'cron') {
 445                  //ok - use queuing of events only
 446  
 447              } else {
 448                  // unknown schedule - fallback to cron type
 449                  debugging("Unknown handler schedule type: $handler->schedule");
 450              }
 451  
 452              // if even type is not instant, or dispatch failed, queue it
 453              if ($event === false) {
 454                  $event = new object();
 455                  $event->userid      = $USER->id;
 456                  $event->eventdata   = addslashes(serialize($eventdata));
 457                  $event->timecreated = time();
 458                  if (debugging()) {
 459                      $dump = '';
 460                      $callers = debug_backtrace();
 461                      foreach ($callers as $caller) {
 462                          $dump .= 'line ' . $caller['line'] . ' of ' . substr($caller['file'], strlen($CFG->dirroot) + 1);
 463                          if (isset($caller['function'])) {
 464                              $dump .= ': call to ';
 465                              if (isset($caller['class'])) {
 466                                  $dump .= $caller['class'] . $caller['type'];
 467                              }
 468                              $dump .= $caller['function'] . '()';
 469                          }
 470                          $dump .= "\n";
 471                      }
 472                      $event->stackdump = addslashes($dump);
 473                 } else {
 474                      $event->stackdump = '';
 475                  }
 476                  $event->id = insert_record('events_queue', $event);
 477              }
 478              events_queue_handler($handler, $event, $errormessage);
 479          }
 480      } else {
 481          //debugging("No handler found for event: $eventname");
 482      }
 483  
 484      return $failedcount;
 485  }
 486  
 487  /**
 488   * checks if an event is registered for this component
 489   * @param string eventname - name of the event
 490   * @param string component - component name, can be mod/data or moodle
 491   * @return bool
 492   *
 493   * PUBLIC
 494   */
 495  function events_is_registered($eventname, $component) {
 496      return record_exists('events_handlers', 'handlermodule', $component, 'eventname', $eventname);
 497  }
 498  
 499  /**
 500   * checks if an event is queued for processing - either cron handlers attached or failed instant handlers
 501   * @param string eventname - name of the event
 502   * @return int number of queued events
 503   *
 504   * PUBLIC
 505   */
 506  function events_pending_count($eventname) {
 507      global $CFG;
 508  
 509      $sql = "SELECT COUNT(*) FROM {$CFG->prefix}events_queue_handlers qh, {$CFG->prefix}events_handlers h
 510              WHERE qh.handlerid = h.id AND h.eventname='$eventname'";
 511      return count_records_sql($sql);
 512  }
 513  ?>


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7