[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |