Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/eventslib.php
Go to the documentation of this file.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations