| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
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 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |