|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00029 defined('MOODLE_INTERNAL') || die(); 00030 00040 function events_load_def($component) { 00041 global $CFG; 00042 if ($component === 'unittest') { 00043 $defpath = $CFG->dirroot.'/lib/simpletest/fixtures/events.php'; 00044 } else { 00045 $defpath = get_component_directory($component).'/db/events.php'; 00046 } 00047 00048 $handlers = array(); 00049 00050 if (file_exists($defpath)) { 00051 require($defpath); 00052 } 00053 00054 // make sure the definitions are valid and complete; tell devs what is wrong 00055 foreach ($handlers as $eventname => $handler) { 00056 if ($eventname === 'reset') { 00057 debugging("'reset' can not be used as event name."); 00058 unset($handlers['reset']); 00059 continue; 00060 } 00061 if (!is_array($handler)) { 00062 debugging("Handler of '$eventname' must be specified as array'"); 00063 unset($handlers[$eventname]); 00064 continue; 00065 } 00066 if (!isset($handler['handlerfile'])) { 00067 debugging("Handler of '$eventname' must include 'handlerfile' key'"); 00068 unset($handlers[$eventname]); 00069 continue; 00070 } 00071 if (!isset($handler['handlerfunction'])) { 00072 debugging("Handler of '$eventname' must include 'handlerfunction' key'"); 00073 unset($handlers[$eventname]); 00074 continue; 00075 } 00076 if (!isset($handler['schedule'])) { 00077 $handler['schedule'] = 'instant'; 00078 } 00079 if ($handler['schedule'] !== 'instant' and $handler['schedule'] !== 'cron') { 00080 debugging("Handler of '$eventname' must include valid 'schedule' type (instant or cron)'"); 00081 unset($handlers[$eventname]); 00082 continue; 00083 } 00084 if (!isset($handler['internal'])) { 00085 $handler['internal'] = 1; 00086 } 00087 $handlers[$eventname] = $handler; 00088 } 00089 00090 return $handlers; 00091 } 00092 00102 function events_get_cached($component) { 00103 global $DB; 00104 00105 $cachedhandlers = array(); 00106 00107 if ($storedhandlers = $DB->get_records('events_handlers', array('component'=>$component))) { 00108 foreach ($storedhandlers as $handler) { 00109 $cachedhandlers[$handler->eventname] = array ( 00110 'id' => $handler->id, 00111 'handlerfile' => $handler->handlerfile, 00112 'handlerfunction' => $handler->handlerfunction, 00113 'schedule' => $handler->schedule, 00114 'internal' => $handler->internal); 00115 } 00116 } 00117 00118 return $cachedhandlers; 00119 } 00120 00132 function events_update_definition($component='moodle') { 00133 global $DB; 00134 00135 // load event definition from events.php 00136 $filehandlers = events_load_def($component); 00137 00138 // load event definitions from db tables 00139 // if we detect an event being already stored, we discard from this array later 00140 // the remaining needs to be removed 00141 $cachedhandlers = events_get_cached($component); 00142 00143 foreach ($filehandlers as $eventname => $filehandler) { 00144 if (!empty($cachedhandlers[$eventname])) { 00145 if ($cachedhandlers[$eventname]['handlerfile'] === $filehandler['handlerfile'] && 00146 $cachedhandlers[$eventname]['handlerfunction'] === serialize($filehandler['handlerfunction']) && 00147 $cachedhandlers[$eventname]['schedule'] === $filehandler['schedule'] && 00148 $cachedhandlers[$eventname]['internal'] == $filehandler['internal']) { 00149 // exact same event handler already present in db, ignore this entry 00150 00151 unset($cachedhandlers[$eventname]); 00152 continue; 00153 00154 } else { 00155 // same event name matches, this event has been updated, update the datebase 00156 $handler = new stdClass(); 00157 $handler->id = $cachedhandlers[$eventname]['id']; 00158 $handler->handlerfile = $filehandler['handlerfile']; 00159 $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array 00160 $handler->schedule = $filehandler['schedule']; 00161 $handler->internal = $filehandler['internal']; 00162 00163 $DB->update_record('events_handlers', $handler); 00164 00165 unset($cachedhandlers[$eventname]); 00166 continue; 00167 } 00168 00169 } else { 00170 // if we are here, this event handler is not present in db (new) 00171 // add it 00172 $handler = new stdClass(); 00173 $handler->eventname = $eventname; 00174 $handler->component = $component; 00175 $handler->handlerfile = $filehandler['handlerfile']; 00176 $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array 00177 $handler->schedule = $filehandler['schedule']; 00178 $handler->status = 0; 00179 $handler->internal = $filehandler['internal']; 00180 00181 $DB->insert_record('events_handlers', $handler); 00182 } 00183 } 00184 00185 // clean up the left overs, the entries in cached events array at this points are deprecated event handlers 00186 // and should be removed, delete from db 00187 events_cleanup($component, $cachedhandlers); 00188 00189 events_get_handlers('reset'); 00190 00191 return true; 00192 } 00193 00199 function events_uninstall($component) { 00200 $cachedhandlers = events_get_cached($component); 00201 events_cleanup($component, $cachedhandlers); 00202 00203 events_get_handlers('reset'); 00204 } 00205 00215 function events_cleanup($component, $cachedhandlers) { 00216 global $DB; 00217 00218 $deletecount = 0; 00219 foreach ($cachedhandlers as $eventname => $cachedhandler) { 00220 if ($qhandlers = $DB->get_records('events_queue_handlers', array('handlerid'=>$cachedhandler['id']))) { 00221 //debugging("Removing pending events from queue before deleting of event handler: $component - $eventname"); 00222 foreach ($qhandlers as $qhandler) { 00223 events_dequeue($qhandler); 00224 } 00225 } 00226 $DB->delete_records('events_handlers', array('eventname'=>$eventname, 'component'=>$component)); 00227 $deletecount++; 00228 } 00229 00230 return $deletecount; 00231 } 00232 00233 /****************** End of Events handler Definition code *******************/ 00234 00245 function events_queue_handler($handler, $event, $errormessage) { 00246 global $DB; 00247 00248 if ($qhandler = $DB->get_record('events_queue_handlers', array('queuedeventid'=>$event->id, 'handlerid'=>$handler->id))) { 00249 debugging("Please check code: Event id $event->id is already queued in handler id $qhandler->id"); 00250 return $qhandler->id; 00251 } 00252 00253 // make a new queue handler 00254 $qhandler = new stdClass(); 00255 $qhandler->queuedeventid = $event->id; 00256 $qhandler->handlerid = $handler->id; 00257 $qhandler->errormessage = $errormessage; 00258 $qhandler->timemodified = time(); 00259 if ($handler->schedule === 'instant' and $handler->status == 1) { 00260 $qhandler->status = 1; //already one failed attempt to dispatch this event 00261 } else { 00262 $qhandler->status = 0; 00263 } 00264 00265 return $DB->insert_record('events_queue_handlers', $qhandler); 00266 } 00267 00278 function events_dispatch($handler, $eventdata, &$errormessage) { 00279 global $CFG; 00280 00281 $function = unserialize($handler->handlerfunction); 00282 00283 if (is_callable($function)) { 00284 // oki, no need for includes 00285 00286 } else if (file_exists($CFG->dirroot.$handler->handlerfile)) { 00287 include_once($CFG->dirroot.$handler->handlerfile); 00288 00289 } else { 00290 $errormessage = "Handler file of component $handler->component: $handler->handlerfile can not be found!"; 00291 return null; 00292 } 00293 00294 // checks for handler validity 00295 if (is_callable($function)) { 00296 $result = call_user_func($function, $eventdata); 00297 if ($result === false) { 00298 $errormessage = "Handler function of component $handler->component: $handler->handlerfunction requested resending of event!"; 00299 return false; 00300 } 00301 return true; 00302 00303 } else { 00304 $errormessage = "Handler function of component $handler->component: $handler->handlerfunction not callable function or class method!"; 00305 return null; 00306 } 00307 } 00308 00317 function events_process_queued_handler($qhandler) { 00318 global $DB; 00319 00320 // get handler 00321 if (!$handler = $DB->get_record('events_handlers', array('id'=>$qhandler->handlerid))) { 00322 debugging("Error processing queue handler $qhandler->id, missing handler id: $qhandler->handlerid"); 00323 //irrecoverable error, remove broken queue handler 00324 events_dequeue($qhandler); 00325 return NULL; 00326 } 00327 00328 // get event object 00329 if (!$event = $DB->get_record('events_queue', array('id'=>$qhandler->queuedeventid))) { 00330 // can't proceed with no event object - might happen when two crons running at the same time 00331 debugging("Error processing queue handler $qhandler->id, missing event id: $qhandler->queuedeventid"); 00332 //irrecoverable error, remove broken queue handler 00333 events_dequeue($qhandler); 00334 return NULL; 00335 } 00336 00337 // call the function specified by the handler 00338 try { 00339 $errormessage = 'Unknown error'; 00340 if (events_dispatch($handler, unserialize(base64_decode($event->eventdata)), $errormessage)) { 00341 //everything ok 00342 events_dequeue($qhandler); 00343 return true; 00344 } 00345 } catch (Exception $e) { 00346 // the problem here is that we do not want one broken handler to stop all others, 00347 // cron handlers are very tricky because the needed data might have been deleted before the cron execution 00348 $errormessage = "Handler function of component $handler->component: $handler->handlerfunction threw exception :" . 00349 $e->getMessage() . "\n" . format_backtrace($e->getTrace(), true); 00350 if (!empty($e->debuginfo)) { 00351 $errormessage .= $e->debuginfo; 00352 } 00353 } 00354 00355 //dispatching failed 00356 $qh = new stdClass(); 00357 $qh->id = $qhandler->id; 00358 $qh->errormessage = $errormessage; 00359 $qh->timemodified = time(); 00360 $qh->status = $qhandler->status + 1; 00361 $DB->update_record('events_queue_handlers', $qh); 00362 00363 return false; 00364 } 00365 00375 function events_dequeue($qhandler) { 00376 global $DB; 00377 00378 // first delete the queue handler 00379 $DB->delete_records('events_queue_handlers', array('id'=>$qhandler->id)); 00380 00381 // if no more queued handler is pointing to the same event - delete the event too 00382 if (!$DB->record_exists('events_queue_handlers', array('queuedeventid'=>$qhandler->queuedeventid))) { 00383 $DB->delete_records('events_queue', array('id'=>$qhandler->queuedeventid)); 00384 } 00385 } 00386 00396 function events_get_handlers($eventname) { 00397 global $DB; 00398 static $handlers = array(); 00399 00400 if ($eventname === 'reset') { 00401 $handlers = array(); 00402 return false; 00403 } 00404 00405 if (!array_key_exists($eventname, $handlers)) { 00406 $handlers[$eventname] = $DB->get_records('events_handlers', array('eventname'=>$eventname)); 00407 } 00408 00409 return $handlers[$eventname]; 00410 } 00411 00412 /****** Public events API starts here, do not use functions above in 3rd party code ******/ 00413 00414 00423 function events_cron($eventname='') { 00424 global $DB; 00425 00426 $failed = array(); 00427 $processed = 0; 00428 00429 if ($eventname) { 00430 $sql = "SELECT qh.* 00431 FROM {events_queue_handlers} qh, {events_handlers} h 00432 WHERE qh.handlerid = h.id AND h.eventname=? 00433 ORDER BY qh.id"; 00434 $params = array($eventname); 00435 } else { 00436 $sql = "SELECT * 00437 FROM {events_queue_handlers} 00438 ORDER BY id"; 00439 $params = array(); 00440 } 00441 00442 $rs = $DB->get_recordset_sql($sql, $params); 00443 foreach ($rs as $qhandler) { 00444 if (isset($failed[$qhandler->handlerid])) { 00445 // do not try to dispatch any later events when one already asked for retry or ended with exception 00446 continue; 00447 } 00448 $status = events_process_queued_handler($qhandler); 00449 if ($status === false) { 00450 // handler is asking for retry, do not send other events to this handler now 00451 $failed[$qhandler->handlerid] = $qhandler->handlerid; 00452 } else if ($status === NULL) { 00453 // means completely broken handler, event data was purged 00454 $failed[$qhandler->handlerid] = $qhandler->handlerid; 00455 } else { 00456 $processed++; 00457 } 00458 } 00459 $rs->close(); 00460 00461 // remove events that do not have any handlers waiting 00462 $sql = "SELECT eq.id 00463 FROM {events_queue} eq 00464 LEFT JOIN {events_queue_handlers} qh ON qh.queuedeventid = eq.id 00465 WHERE qh.id IS NULL"; 00466 $rs = $DB->get_recordset_sql($sql); 00467 foreach ($rs as $event) { 00468 //debugging('Purging stale event '.$event->id); 00469 $DB->delete_records('events_queue', array('id'=>$event->id)); 00470 } 00471 $rs->close(); 00472 00473 return $processed; 00474 } 00475 00476 00486 function events_trigger($eventname, $eventdata) { 00487 global $CFG, $USER, $DB; 00488 00489 $failedcount = 0; // number of failed events. 00490 00491 // pull out all registered event handlers 00492 if ($handlers = events_get_handlers($eventname)) { 00493 foreach ($handlers as $handler) { 00494 $errormessage = ''; 00495 00496 if ($handler->schedule === 'instant') { 00497 if ($handler->status) { 00498 //check if previous pending events processed 00499 if (!$DB->record_exists('events_queue_handlers', array('handlerid'=>$handler->id))) { 00500 // ok, queue is empty, lets reset the status back to 0 == ok 00501 $handler->status = 0; 00502 $DB->set_field('events_handlers', 'status', 0, array('id'=>$handler->id)); 00503 // reset static handler cache 00504 events_get_handlers('reset'); 00505 } 00506 } 00507 00508 // dispatch the event only if instant schedule and status ok 00509 if ($handler->status or (!$handler->internal and $DB->is_transaction_started())) { 00510 // increment the error status counter 00511 $handler->status++; 00512 $DB->set_field('events_handlers', 'status', $handler->status, array('id'=>$handler->id)); 00513 // reset static handler cache 00514 events_get_handlers('reset'); 00515 00516 } else { 00517 $errormessage = 'Unknown error';; 00518 $result = events_dispatch($handler, $eventdata, $errormessage); 00519 if ($result === true) { 00520 // everything is fine - event dispatched 00521 continue; 00522 } else if ($result === false) { 00523 // retry later - set error count to 1 == send next instant into cron queue 00524 $DB->set_field('events_handlers', 'status', 1, array('id'=>$handler->id)); 00525 // reset static handler cache 00526 events_get_handlers('reset'); 00527 } else { 00528 // internal problem - ignore the event completely 00529 $failedcount ++; 00530 continue; 00531 } 00532 } 00533 00534 // update the failed counter 00535 $failedcount ++; 00536 00537 } else if ($handler->schedule === 'cron') { 00538 //ok - use queueing of events only 00539 00540 } else { 00541 // unknown schedule - ignore event completely 00542 debugging("Unknown handler schedule type: $handler->schedule"); 00543 $failedcount ++; 00544 continue; 00545 } 00546 00547 // if even type is not instant, or dispatch asked for retry, queue it 00548 $event = new stdClass(); 00549 $event->userid = $USER->id; 00550 $event->eventdata = base64_encode(serialize($eventdata)); 00551 $event->timecreated = time(); 00552 if (debugging()) { 00553 $dump = ''; 00554 $callers = debug_backtrace(); 00555 foreach ($callers as $caller) { 00556 if (!isset($caller['line'])) { 00557 $caller['line'] = '?'; 00558 } 00559 if (!isset($caller['file'])) { 00560 $caller['file'] = '?'; 00561 } 00562 $dump .= 'line ' . $caller['line'] . ' of ' . substr($caller['file'], strlen($CFG->dirroot) + 1); 00563 if (isset($caller['function'])) { 00564 $dump .= ': call to '; 00565 if (isset($caller['class'])) { 00566 $dump .= $caller['class'] . $caller['type']; 00567 } 00568 $dump .= $caller['function'] . '()'; 00569 } 00570 $dump .= "\n"; 00571 } 00572 $event->stackdump = $dump; 00573 } else { 00574 $event->stackdump = ''; 00575 } 00576 $event->id = $DB->insert_record('events_queue', $event); 00577 events_queue_handler($handler, $event, $errormessage); 00578 } 00579 } else { 00580 // No handler found for this event name - this is ok! 00581 } 00582 00583 return $failedcount; 00584 } 00585 00593 function events_is_registered($eventname, $component) { 00594 global $DB; 00595 return $DB->record_exists('events_handlers', array('component'=>$component, 'eventname'=>$eventname)); 00596 } 00597 00606 function events_pending_count($eventname) { 00607 global $DB; 00608 00609 $sql = "SELECT COUNT('x') 00610 FROM {events_queue_handlers} qh 00611 JOIN {events_handlers} h ON h.id = qh.handlerid 00612 WHERE h.eventname = ?"; 00613 00614 return $DB->count_records_sql($sql, array($eventname)); 00615 }