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