]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-9848 eventslib improvements and cleanup
authorskodak <skodak>
Tue, 22 May 2007 10:26:29 +0000 (10:26 +0000)
committerskodak <skodak>
Tue, 22 May 2007 10:26:29 +0000 (10:26 +0000)
admin/blocks.php
admin/modules.php
grade/import/csv/index.php
lib/db/install.xml
lib/db/upgrade.php
lib/eventslib.php
lib/gradelib.php
lib/simpletest/fixtures/events.php [new file with mode: 0644]
lib/simpletest/testeventslib.php
version.php

index 2ceaff3739c3e663aee43fcad48438e461e3eb23..3b296beeb9f47cf8e9662d00aae4a4fe7d1a0d1d 100644 (file)
             // Delete the capabilities that were defined by this block
             capabilities_cleanup('block/'.$block->name);
 
+            // remove entent handlers and dequeue pending events
+            events_uninstall('block/'.$block->name);
+
             $a->block = $strblockname;
             $a->directory = $CFG->dirroot.'/blocks/'.$block->name;
             notice(get_string('blockdeletefiles', '', $a), 'blocks.php');
index 1f395374edf7dce5e9efa7640398fb8afe517696..08120caf2738ab8e8c68489725785b158f4f7e9c 100644 (file)
             // Delete the capabilities that were defined by this module
             capabilities_cleanup('mod/'.$module->name);
 
+            // remove entent handlers and dequeue pending events
+            events_uninstall('mod/'.$module->name);
+
             // rebuild_course_cache();  // Because things have changed
             $coursesaffected = true;
 
index 8850a4319449391a1fb6539060d6bf62ded8c59c..c6d5dcc85369581a2bfd3c38ce6a4f699cca4f04 100755 (executable)
@@ -123,7 +123,7 @@ if (($formdata = data_submitted()) && !empty($formdata->map)) {
                     $eventdata->idnumber = $idnumber;
                     $eventdata->userid = $studentid;
                     $eventdata->gradevalue = $studentgrade;
-                    trigger_event('grade_added', $eventdata);               
+                    events_trigger('grade_added', $eventdata);               
                 
                     echo "<br/>triggering event for $idnumber... student id is $studentid and grade is $studentgrade";
             
index 43a03719bbd9112a61ae714f41ddf4ca5da8039e..4c138a8c28077dfa7ee16e43149c03b00d7cad68 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20070511" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20070517" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <INDEX NAME="eventname-handlermodule" UNIQUE="true" FIELDS="eventname, handlermodule"/>
       </INDEXES>
     </TABLE>
-    <TABLE NAME="events_queue" COMMENT="This table is for storing queued events. It stores only one copy of the eventdata here, and entries from this table are being references by the event_queue_handlers_todo table." PREVIOUS="events_handlers" NEXT="events_queue_handlers">
+    <TABLE NAME="events_queue" COMMENT="This table is for storing queued events. It stores only one copy of the eventdata here, and entries from this table are being references by the event_queue_handlers table." PREVIOUS="events_handlers" NEXT="events_queue_handlers">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="true" ENUM="false" COMMENT="id of the table, please edit me" NEXT="eventdata"/>
-        <FIELD NAME="eventdata" TYPE="text" LENGTH="big" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="serialized version of the data object passed to the event handler." PREVIOUS="id" NEXT="schedule"/>
-        <FIELD NAME="schedule" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="'cron' or 'instant'." PREVIOUS="eventdata" NEXT="stackdump"/>
-        <FIELD NAME="stackdump" TYPE="text" LENGTH="medium" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="serialized debug_backtrace showing where the event was fired from" PREVIOUS="schedule" NEXT="userid"/>
+        <FIELD NAME="eventdata" TYPE="text" LENGTH="big" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="serialized version of the data object passed to the event handler." PREVIOUS="id" NEXT="stackdump"/>
+        <FIELD NAME="stackdump" TYPE="text" LENGTH="medium" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="serialized debug_backtrace showing where the event was fired from" PREVIOUS="eventdata" NEXT="userid"/>
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="$USER-&amp;gt;id when the event was fired" PREVIOUS="stackdump" NEXT="timecreated"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="time stamp of the first time this was added" PREVIOUS="userid"/>
       </FIELDS>
index adeeef7a89ac435e4d5c98ffd263f9e4883a583d..412e03e0641ef4b35474836ef86fe8e9b7557f08 100644 (file)
@@ -1292,6 +1292,16 @@ function xmldb_main_upgrade($oldversion=0) {
         }
     }
 
+    if ($result && $oldversion < 2007052200) {
+
+    /// Define field schedule to be dropped from events_queue
+        $table = new XMLDBTable('events_queue');
+        $field = new XMLDBField('schedule');
+
+    /// Launch drop field stackdump
+        $result = $result && drop_field($table, $field);
+    }
+
     return $result; 
 }
 
index e241804bfe65284c1bff1566b5a2753b675bb6f9..1620b6c184545d063ddcd5e090d83dabf0271c2f 100755 (executable)
@@ -23,6 +23,9 @@ function events_load_def($component) {
     if ($component == 'moodle') {
         $defpath = $CFG->libdir.'/db/events.php';
 
+    } else if ($component == 'unittest') {
+        $defpath = $CFG->libdir.'/simpletest/fixtures/events.php';
+
     } else {
         $compparts = explode('/', $component);
 
@@ -41,7 +44,7 @@ function events_load_def($component) {
         }
     }
 
-    $events = array();
+    $events = array(); // TODO: $handlers might be better here ;-)
 
     if (file_exists($defpath)) {
         require($defpath);
@@ -58,93 +61,97 @@ function events_load_def($component) {
  *
  * INTERNAL - to be used from eventslib only
  */
-function get_cached_events($component) {
-    $cachedevents = array();
-
-    if ($storedevents = get_records('events_handlers', 'handlermodule', $component)) {
-        foreach ($storedevents as $event) {
-            $cachedevents[$event->eventname] = array (
-                'id'              => $event->id,
-                'handlerfile'     => $event->handlerfile,
-                'handlerfunction' => $event->handlerfunction, 
-                'schedule'        => $event->schedule);
+function events_get_cached($component) {
+    $cachedhandlers = array();
+
+    if ($storedhandlers = get_records('events_handlers', 'handlermodule', $component)) {
+        foreach ($storedhandlers as $handler) {
+            $cachedhandlers[$handler->eventname] = array (
+                'id'              => $handler->id,
+                'handlerfile'     => $handler->handlerfile,
+                'handlerfunction' => $handler->handlerfunction,
+                'schedule'        => $handler->schedule);
         }
     }
 
-    return $cachedevents;
+    return $cachedhandlers;
 }
 
 /**
  * We can not removed all event handlers in table, then add them again
  * because event handlers could be referenced by queued items
  *
- * Updates the capabilities table with the component capability definitions.
- * If no parameters are given, the function updates the core moodle
- * capabilities.
- *
  * Note that the absence of the db/events.php event definition file
- * will cause any stored events for the component to be removed from
+ * will cause any queued events for the component to be removed from
  * the database.
  *
  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
  * @return boolean
  */
-
 function events_update_definition($component='moodle') {
 
     // load event definition from events.php
-    $fileevents = events_load_def($component);
+    $filehandlers = events_load_def($component);
 
     // load event definitions from db tables
     // if we detect an event being already stored, we discard from this array later
     // the remaining needs to be removed
-    $cachedevents = get_cached_events($component);
+    $cachedhandlers = events_get_cached($component);
 
-    foreach ($fileevents as $eventname => $fileevent) {
-        if (!empty($cachedevents[$eventname])) {
-            if ($cachedevents[$eventname]['handlerfile'] == $fileevent['handlerfile'] &&
-                $cachedevents[$eventname]['handlerfunction'] == $fileevent['handlerfunction'] &&
-                $cachedevents[$eventname]['schedule'] == $fileevent['schedule']) {
+    foreach ($filehandlers as $eventname => $filehandler) {
+        if (!empty($cachedhandlers[$eventname])) {
+            if ($cachedhandlers[$eventname]['handlerfile'] == $filehandler['handlerfile'] &&
+                $cachedhandlers[$eventname]['handlerfunction'] == serialize($filehandler['handlerfunction']) &&
+                $cachedhandlers[$eventname]['schedule'] == $filehandler['schedule']) {
                 // exact same event handler already present in db, ignore this entry
 
-                unset($cachedevents[$eventname]);
+                unset($cachedhandlers[$eventname]);
                 continue;
 
             } else {
                 // same event name matches, this event has been updated, update the datebase
-                $event = new object();
-                $event->id              = $cachedevents[$eventname]['id'];
-                $event->handlerfile     = $fileevent['handlerfile'];
-                $event->handlerfunction = $fileevent['handlerfunction'];
-                $event->schedule        = $fileevent['schedule'];
+                $handler = new object();
+                $handler->id              = $cachedhandlers[$eventname]['id'];
+                $handler->handlerfile     = $filehandler['handlerfile'];
+                $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
+                $handler->schedule        = $filehandler['schedule'];
 
-                update_record('events_handlers', $event);
+                update_record('events_handlers', $handler);
 
-                unset($cachedevents[$eventname]);
+                unset($cachedhandlers[$eventname]);
                 continue;
             }
 
         } else {
             // if we are here, this event handler is not present in db (new)
             // add it
-            $event = new object();
-            $event->eventname       = $eventname;
-            $event->handlermodule   = $component;
-            $event->handlerfile     = $fileevent['handlerfile'];
-            $event->handlerfunction = $fileevent['handlerfunction'];
-            $event->schedule        = $fileevent['schedule'];
-
-            insert_record('events_handlers', $event);
+            $handler = new object();
+            $handler->eventname       = $eventname;
+            $handler->handlermodule   = $component;
+            $handler->handlerfile     = $filehandler['handlerfile'];
+            $handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
+            $handler->schedule        = $filehandler['schedule'];
+
+            insert_record('events_handlers', $handler);
         }
     }
 
     // clean up the left overs, the entries in cachedevents array at this points are deprecated event handlers
     // and should be removed, delete from db
-    events_cleanup($component, $cachedevents);
+    events_cleanup($component, $cachedhandlers);
 
     return true;
 }
 
+/**
+ * Remove all event handlers and queued events
+ * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
+ */
+function events_uninstall($component) {
+    $cachedhandlers = events_get_cached($component);
+    events_cleanup($component, $cachedhandlers);
+}
+
 /**
  * Deletes cached events that are no longer needed by the component.
  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
@@ -153,9 +160,15 @@ function events_update_definition($component='moodle') {
  *
  * INTERNAL - to be used from eventslib only
  */
-function events_cleanup($component, $cachedevents) {
+function events_cleanup($component, $cachedhandlers) {
     $deletecount = 0;
-    foreach ($cachedevents as $eventname => $cachedevent) {
+    foreach ($cachedhandlers as $eventname => $cachedhandler) {
+        if ($qhandlers = get_records('events_queue_handlers', 'handlerid', $cachedhandler['id'])) {
+            debugging("Removing pending events from queue before deleting of event handler: $component - $eventname");
+            foreach ($qhandlers as $qhandler) {
+                events_dequeue($qhandler);
+            }
+        }
         if (delete_records('events_handlers', 'eventname', $eventname, 'handlermodule', $component)) {
             $deletecount++;
         }
@@ -169,162 +182,293 @@ function events_cleanup($component, $cachedevents) {
  * puts a handler on queue
  * @param object handler - event handler object from db
  * @param object eventdata - event data object
- * @param bool failed -  whether this handler is queued because of a failed event trigger
- * @return
- */
-function queue_handler($handler, $eventid) {
-    global $USER;
-
-    // check if this event handler is already queued
-    if (!$qh = get_record('events_queue_handlers', 'queuedeventid', $eventid, 'handlerid', $handler->id)) {
-        // make a new one
-        $qh = new object;
-        $qh->queuedeventid = $eventid;
-        $qh->handlerid = $handler->id;
-        $qh->status = 0;
-        $qh->errormessage = '';
-        $qh->timemodified = time();
-        return insert_record('events_queue_handlers', $qh);
-    } else {
-        // update existing one, failed again
-        $qh->states++;
-        $qh->timemodified = time();
-        update_record('events_queue_handlers', $qh);
-        return -1; // failed
-    }
-}
-
-/**
- * function to call all eventhandlers when triggering an event
- * @param eventname - name of the event
- * @param eventdata - event data object
- * @return number of failed events
+ * @return id number of new queue handler
+ *
+ * INTERNAL - to be used from eventslib only
  */
-function trigger_event($eventname, $eventdata) {
-    $failedevent = 0; // number of failed events.
-    $eventid = 0;
+function events_queue_handler($handler, $event, $errormessage) {
 
-    // pull out all registered event handlers
-    if ($handlers = get_records('events_handlers', 'eventname', $eventname)) {
-        foreach ($handlers as $handler) {
-            // either excute it now
+    if ($qhandler = get_record('events_queue_handlers', 'queuedeventid', $event->id, 'handlerid', $handler->id)) {
+        debugging("Please check code: Event id $event->id is already queued in handler id $qhandler->id");
+        return $qhandler->id;
+    }
 
-            // if event type is
-            if ($handler->schedule == 'instant') {
-                if (dispatch_event($handler, $eventdata)) {
-                    continue;
-                } else {
-                    // update the failed flag
-                    $failedevent ++;
-                }
-            }
-            // if even type is not instant, or trigger failed, queue it
-            $queuedevent++;
-            // make and queue the event object here
-
-            if (!$eventid) {
-                $eq = new object;
-                $eq->userid = $USER->id;
-                $eq->schedule = $eventdata->schedule;
-                $eq->eventdata = serialize($eventdata);
-                $eq->stackdump = '';
-                $eq->timecreated = time();
-                $eventid = insert_record('events_queue', $eq);
-            }
-            queue_handler($handler, $eventid);
-        }
+    // make a new queue handler
+    $qhandler = new object();
+    $qhandler->queuedeventid  = $event->id;
+    $qhandler->handlerid      = $handler->id;
+    $qhandler->errormessage   = addslashes($errormessage);
+    $qhandler->timemodified   = time();
+    if ($handler->schedule == 'instant' and $handler->status == 1) {
+        $qhandler->status     = 1; //already one failed attempt to dispatch this event
+    } else {
+        $qhandler->status     = 0;
     }
-    return $failedevent;
+
+    return insert_record('events_queue_handlers', $qhandler);
 }
 
 /**
  * trigger a single event with a specified handler
  * @param handler - hander object from db
  * @param eventdata - event dataobject
+ * @param errormessage - error message indicating problem
  * @return bool - success or fail
+ *
+ * INTERNAL - to be used from eventslib only
  */
-function dispatch_event($handler, $eventdata) {
-
+function events_dispatch($handler, $eventdata, &$errormessage) {
     global $CFG;
-    // checks for handler validity
 
-    // check if the same handler is queued already, if so, return false so we can queue it
-    // TODO
+    $function = unserialize($handler->handlerfunction);
+
+    if (is_callable($function)) {
+        // oki, no need for includes
+
+    } else if (file_exists($CFG->dirroot.$handler->handlerfile)) {
+        include_once($CFG->dirroot.$handler->handlerfile);
+
+    } else {
+        $errormessage = "Handler file of component $handler->handlermodule: $handler->handlerfile can not be found!";
+        return false;
+    }
+
+    // checks for handler validity
+    if (is_callable($function)) {
+        return call_user_func($function, $eventdata);
 
-    include_once($CFG->dirroot.$handler->handlerfile);
-    return call_user_func($handler->handlerfunction, $eventdata);
+    } else {
+        $errormessage = "Handler function of component $handler->handlermodule: $handler->handlerfunction not callable function or class method!";
+        return false;
+    }
 }
 
 /**
  * given a queued handler, call the respective event handler to process the event
- * @param object handler- events_queued_handler object from db
- * @return fail or custom function value
+ * @param object qhandler - events_queued_handler object from db
+ * @return boolean meaning success, or NULL on fatal failure
+ *
+ * INTERNAL - to be used from eventslib only
  */
-function events_process_queued_handler($handler) {
-    // checks for handler validity
+function events_process_queued_handler($qhandler) {
     global $CFG;
 
     // get handler
-    if (!$eventhandler = get_record('events_handlers', 'id', $handler->handlerid)) {
-        // can't proceed with no handler
-        return false;
+    if (!$handler = get_record('events_handlers', 'id', $qhandler->handlerid)) {
+        debugging("Error processing queue handler $qhandler->id, missing handler id: $qhandler->handlerid");
+        //irrecoverable error, remove broken queue handler
+        events_dequeue($qhandler);
+        return NULL;
     }
+
     // get event object
-    if (!$eventobject = get_record('events_queue', 'id', $handler->queuedeventid)) {
-        // can't proceed with no event object
+    if (!$event = get_record('events_queue', 'id', $qhandler->queuedeventid)) {
+        // can't proceed with no event object - might happen when two crons running at the same time
+        debugging("Error processing queue handler $qhandler->id, missing event id: $qhandler->queuedeventid");
+        //irrecoverable error, remove broken queue handler
+        events_dequeue($qhandler);
+        return NULL;
+    }
+
+    // call the function specified by the handler
+    $errormessage = 'Unknown error';
+    if (events_dispatch($handler, unserialize($event->eventdata), $errormessage)) {
+        //everything ok
+        events_dequeue($qhandler);
+        return true;
+
+    } else {
+        //dispatching failed
+        $qh = new object();
+        $qh->id           = $qhandler->id;
+        $qh->errormessage = addslashes($errormessage);
+        $qh->timemodified = time();
+        $qh->status       = $qhandler->status + 1;
+        update_record('events_queue_handlers', $qh);
         return false;
     }
-    // call the function sepcified by the handler
+}
+
+/**
+ * removes this queued handler from the events_queued_handler table
+ * removes events_queue record from events_queue if no more references to this event object exists
+ * @param object qhandler - events_queued_handler object from db
+ *
+ * INTERNAL - to be used from eventslib only
+ */
+function events_dequeue($qhandler) {
+    // first delete the queue handler
+    delete_records('events_queue_handlers', 'id', $qhandler->id);
 
-    return dispatch_event($eventhandler, unserialize($eventobject->eventdata));
+    // if no more queued handler is pointing to the same event - delete the event too
+    if (!record_exists('events_queue_handlers', 'queuedeventid', $qhandler->queuedeventid)) {
+        delete_records('events_queue', 'id', $qhandler->queuedeventid);
+    }
 }
 
+
+
+/****** Public events API starts here, do not use functions above in 3rd party code ******/
+
+
 /**
  * Events cron will try to empty the events queue by processing all the queued events handlers
+ * @param string eventname - empty means all
+ * @return number of dispatched+removed broken events
+ *
+ * PUBLIC
  */
-function events_cron() {
-
+function events_cron($eventname='') {
     global $CFG;
 
-    if ($handlers = get_records_select('events_queue_handlers', '', 'timemodified')) {
-        foreach ($handlers as $handler) {
-            if (events_process_queued_handler($handler)) {
-                // dequeue();
-                events_dequeue($handler);
-            } else {
-                // failed again, put back on queue
-                $handler->timemodified = time();
-                $handler->status++;
-                update_record('events_queue_handlers', $handler);
+    $failed = array();
+    $processed = 0;
+
+    if ($eventname) {
+        $sql = "SELECT qh.* FROM {$CFG->prefix}events_queue_handlers qh, {$CFG->prefix}events_handlers h
+                WHERE qh.handlerid = h.id AND h.eventname='$eventname'
+                ORDER BY qh.id";
+    } else {
+        $sql = "SELECT * FROM {$CFG->prefix}events_queue_handlers
+                ORDER BY id";
+    }
+
+    if ($rs = get_recordset_sql($sql)) {
+        if ($rs->RecordCount() > 0) {
+            while ($qhandler = rs_fetch_next_record($rs)) {
+                if (in_array($qhandler->handlerid, $failed)) {
+                    // do not try to dispatch any later events when one already failed
+                    continue;
+                }
+                $status = events_process_queued_handler($qhandler);
+                if ($status === false) {
+                    $failed[] = $qhandler->handlerid;
+                } else {
+                    $processed++;
+                }
             }
         }
+        rs_close($rs);
     }
+    return $processed;
 }
 
+
 /**
- * removes this queued handler from the events_queued_handler table
- * removes events_queue record from events_queue if no more references to this event object exists
- * @input object handler - events_queued_handler object from db
+ * Function to call all eventhandlers when triggering an event
+ * @param eventname - name of the event
+ * @param eventdata - event data object
+ * @return number of failed events
+ *
+ * PUBLIC
  */
-function events_dequeue($handler) {
+function events_trigger($eventname, $eventdata) {
+    global $CFG, $USER;
 
-    if (delete_records('events_queue_handlers', 'id', $handler->id)) {
-        // if no more queued handler is pointing to the same event
-        if (!record_exists('events_queue_handlers', 'queuedeventid', $handler->queuedeventid)) {
-            delete_records('events_queue', 'id', $handler->queuedeventid);
+    $failedcount = 0; // number of failed events.
+    $event = false;
+
+    // pull out all registered event handlers
+    if ($handlers = get_records('events_handlers', 'eventname', $eventname)) {
+        foreach ($handlers as $handler) {
+
+           $errormessage = '';
+
+           if ($handler->schedule == 'instant') {
+                if ($handler->status) {
+                    //check if previous pending events processed
+                    if (!record_exists('events_queue_handlers', 'handlerid', $handler->id)) {
+                        // ok, queue is empty, lets reset the status back to 0 == ok
+                        $handler->status = 0;
+                        set_field('events_handlers', 'status', 0, 'id', $handler->id);
+                    }
+                }
+
+                // dispatch the event only if instant schedule and status ok
+                if (!$handler->status) {
+                    $errormessage = 'Unknown error';;
+                    if (events_dispatch($handler, $eventdata, $errormessage)) {
+                        continue;
+                    }
+                    // set error count to 1 == send next instant into cron queue
+                    set_field('events_handlers', 'status', 1, 'id', $handler->id);
+
+                } else {
+                    // increment the error status counter
+                    $handler->status++;
+                    set_field('events_handlers', 'status', $handler->status, 'id', $handler->id);
+                }
+
+                // update the failed counter
+                $failedcount ++;
+
+            } else if ($handler->schedule == 'cron') {
+                //ok - use queuing of events only
+
+            } else {
+                // unknown schedule - fallback to cron type
+                debugging("Unknown handler schedule type: $handler->schedule");
+            }
+
+            // if even type is not instant, or dispatch failed, queue it
+            if ($event === false) {
+                $event = new object();
+                $event->userid      = $USER->id;
+                $event->eventdata   = serialize($eventdata);
+                $event->timecreated = time();
+                if (debugging()) {
+                    $dump = '';
+                    $callers = debug_backtrace();
+                    foreach ($callers as $caller) {
+                        $dump .= 'line ' . $caller['line'] . ' of ' . substr($caller['file'], strlen($CFG->dirroot) + 1);
+                        if (isset($caller['function'])) {
+                            $dump .= ': call to ';
+                            if (isset($caller['class'])) {
+                                $dump .= $caller['class'] . $caller['type'];
+                            }
+                            $dump .= $caller['function'] . '()';
+                        }
+                        $dump .= "\n";
+                    }
+                    $event->stackdump = addslashes($dump);
+               } else {
+                    $event->stackdump = '';
+                }
+                $event->id = insert_record('events_queue', $event);
+            }
+            events_queue_handler($handler, $event, $errormessage);
         }
-        return true;
     } else {
-        return false;
+        debugging("No handler found for event: $eventname");
     }
+
+    return $failedcount;
 }
 
 /**
  * checks if an event is registered for this component
+ * @param string eventname - name of the event
  * @param string component - component name, can be mod/data or moodle
  * @return bool
+ *
+ * PUBLIC
  */
-function event_is_registered($component, $eventname) {
+function events_is_registered($eventname, $component) {
     return record_exists('events_handlers', 'handlermodule', $component, 'eventname', $eventname);
 }
+
+/**
+ * checks if an event is queued for processing - either cron handlers attached or failed instant handlers
+ * @param string eventname - name of the event
+ * @return int number of queued events
+ *
+ * PUBLIC
+ */
+function events_pending_count($eventname) {
+    global $CFG;
+
+    $sql = "SELECT COUNT(*) FROM {$CFG->prefix}events_queue_handlers qh, {$CFG->prefix}events_handlers h
+            WHERE qh.handlerid = h.id AND h.eventname='$eventname'";
+    return count_records_sql($sql);
+}
 ?>
index a1df02f103a15400c7c595d9b1d6a94e64f2c9ca..5ed09be37d0ca3ddbcd73dc4581a102d80bc0327 100644 (file)
@@ -165,7 +165,7 @@ function grade_update_final_grades($courseid=NULL, $gradeitemid=NULL) {
  * For backward compatibility with old third-party modules, this function is called
  * via to admin/cron.php to search all mod/xxx/lib.php files for functions named xxx_grades(),
  * if the current modules does not have grade events registered with the grade book.
- * Once the data is extracted, the event_trigger() function can be called to initiate 
+ * Once the data is extracted, the events_trigger() function can be called to initiate 
  * an event as usual and copy/ *upgrade the data in the gradebook tables. 
  */
 function grades_grab_grades() {
@@ -192,7 +192,7 @@ function grades_grab_grades() {
             $gradefunc = $mod.'_grades';
             // if this mod has grades, but grade_added event is not registered
             // then we need to pull grades into the new gradebook
-            if (function_exists($gradefunc) && !event_is_registered($mod, $gradefunc)) {
+            if (function_exists($gradefunc) && !events_is_registered($gradefunc, $mod)) {//TODO: the use of $gradefunct as eventname here does not seem to be correct
                 // get all instance of the mod
                 $module = get_record('modules', 'name', $mod);
                 if ($module && $modinstances = get_records_select('course_modules cm, '.$CFG->prefix.$mod.' m', 'cm.module = '.$module->id.' AND m.id = cm.instance')) {
@@ -202,8 +202,8 @@ function grades_grab_grades() {
                             foreach ($grades->grades as $userid=>$usergrade) {                              
                                 // make the grade_added eventdata
                                 // missing grade event trigger
-                                // trigger_event('grade_added', $eventdata);
-                                unset($eventdata);
+                                // events_trigger('grade_added', $eventdata);
+                                $eventdata = new object();
                                 $eventdata->courseid =  $modinstance->course;
                                 $eventdata->itemmodule = $mod;
                                 $eventdata->iteminstance = $modinstance->instance;
@@ -211,7 +211,7 @@ function grades_grab_grades() {
                                 $eventdata->userid = $userid;
                                 $eventdata->gradevalue = $usergrade;
                                 $eventdata->itemname = $modinstance->name;
-                                trigger_event('grade_added', $eventdata);                             
+                                events_trigger('grade_added', $eventdata);                             
                                                        
                             }
                         }
diff --git a/lib/simpletest/fixtures/events.php b/lib/simpletest/fixtures/events.php
new file mode 100644 (file)
index 0000000..609eac1
--- /dev/null
@@ -0,0 +1,41 @@
+<?php // $Id$
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.org                                            //
+//                                                                       //
+// Copyright (C) 1999-2007  Martin Dougiamas  http://dougiamas.com       //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+
+$events = array (
+   'test_instant' => array (
+        'handlerfile'      => '/lib/simpletest/testeventslib.php',
+        'handlerfunction'  => 'sample_function_handler',
+        'schedule'         => 'instant'
+    ),
+
+   'test_cron' => array (
+        'handlerfile'      => '/lib/simpletest/testeventslib.php',
+        'handlerfunction'  => array('sample_handler_class', 'static_method'),
+        'schedule'         => 'cron'
+    )
+);
+
+?>
index f6ce9250ec080883baccda4bf2e055ae40f3912a..1e3bd5c3e5bab69ca7e576cb20efca206f25cef0 100755 (executable)
@@ -1,24 +1,84 @@
 <?php
 
-/** $Id */
-require_once(dirname(__FILE__) . '/../../config.php');
+/* $Id$ */
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
 
 global $CFG;
 require_once($CFG->libdir . '/simpletestlib.php');
-require_once($CFG->libdir . '/eventslib.php');
-require_once($CFG->libdir . '/dmllib.php');
 
-// dummy test function
-function plusone($eventdata) {
-  
-    return $eventdata+1;  
+// test handler function
+function sample_function_handler($eventdata) {
+    static $called = 0;
+    static $ignorefail = false;
+
+    if ($eventdata == 'status') {
+        return $called;
+
+    } else if ($eventdata == 'reset') {
+        $called = 0;
+        $ignorefail = false;
+        return;
+
+    } else if ($eventdata == 'fail') {
+        if ($ignorefail) {
+            $called++;
+            return true;
+        } else {
+            return false;
+        }
+
+    } else if ($eventdata == 'ignorefail') {
+        $ignorefail = true;
+        return;
+
+    } else if ($eventdata == 'ok') {
+        $called++;
+        return true;
+    }
+
+    error('Incorrect eventadata submitted: '.$eventdata);
+}
+
+// test handler class with static method
+class sample_handler_class {
+    function static_method($eventdata) {
+        static $called = 0;
+        static $ignorefail = false;
+
+        if ($eventdata == 'status') {
+            return $called;
+
+        } else if ($eventdata == 'reset') {
+            $called = 0;
+            $ignorefail = false;
+            return;
+
+        } else if ($eventdata == 'fail') {
+            if ($ignorefail) {
+                $called++;
+                return true;
+            } else {
+                return false;
+            }
+
+        } else if ($eventdata == 'ignorefail') {
+            $ignorefail = true;
+            return;
+
+        } else if ($eventdata == 'ok') {
+            $called++;
+            return true;
+        }
+
+        error('Incorrect eventadata submitted: '.$eventdata);
+    }
 }
 
 class eventslib_test extends UnitTestCase {
 
-    var $handlerid;
-    var $handler;
-    var $storedhandler;
     /**
      * Create temporary entries in the database for these tests.
      * These tests have to work no matter the data currently in the database
@@ -26,71 +86,118 @@ class eventslib_test extends UnitTestCase {
      * data have to be artificially inseminated (:-) in the DB.
      */
     function setUp() {
-        
-        global $CFG;
-        
-        // make a dummy event
-        $eventhandler -> eventname = 'testevent';
-        $eventhandler -> handlermodule = 'unittest';
-        $eventhandler -> handlerfile = '/lib/simpletest/testeventslib.php';
-        $eventhandler -> handlerfunction = 'plusone';
-        $eventhandler -> schedule = 'instant';
-        
-        $this -> handler = $eventhandler;
-        $this -> handlerid = insert_record('events_handlers', $eventhandler);
-        $this -> handler->id = $this->handlerid;
-
+        events_uninstall('unittest');
+        sample_function_handler('reset');
+        sample_handler_class::static_method('reset');
+        events_update_definition('unittest');
     }
 
     /**
      * Delete temporary entries from the database
      */
-    function tearDown() 
-    {
-        delete_records('events_handlers', 'id', $this->handlerid);
+    function tearDown() {
+       events_uninstall('unittest');
     }
-    
+
     /**
-     * tests queue_handler() and events_process_queued_handler() and trigger_event()
+     * Tests the installation of event handlers from file
      */
-    function test_events_process_queued_handler_handler() {
-        
-        $eventdata = new object;
-        $eventdata->eventdata = serialize(1);
-        $eventdata->schedule = 'instant';
-        
-        $eventid = insert_record('events_queue', $eventdata);
-
-        $id = queue_handler($this->handler, $eventid);
-        $storedhandler = get_record('events_queue_handlers', 'id', $id);
-
-        $retval = events_process_queued_handler($storedhandler);
-        $this->assertEqual(2, $retval);
-        $this->storedhandler = $storedhandler;
+    function test__events_update_definition__install() {
+        global $CFG;
+
+        $dbcount = count_records('events_handlers', 'handlermodule', 'unittest');
+        $events = array();
+        require($CFG->libdir.'/simpletest/fixtures/events.php');
+        $filecount = count($events);
+        $this->assertEqual($dbcount, $filecount, 'Equal number of handlers in file and db: %s');
     }
-    
+
     /**
-     * tests events_dequeue()
+     * Tests the uninstallation of event handlers from file
      */
-    function test_events_dequeue() {
-        $this->assertTrue(events_dequeue($this->storedhandler));        
+    function test__events_update_definition__uninstall() {
+        events_uninstall('unittest');
+        $this->assertEqual(0, count_records('events_handlers', 'handlermodule', 'unittest'), 'All handlers should be uninstalled: %s');
     }
-    
-    /** 
-     * tests trigger_event funtion()
+
+    /**
+     * Tests the update of event handlers from file
      */
-    function test_trigger_event() {
-        $eventdata = 2;
-        $this->assertEqual(0, trigger_event('testevent', $eventdata));
+    function test__events_update_definition__update() {
+        // first modify directly existing handler
+        $handler = get_record('events_handlers', 'handlermodule', 'unittest', 'eventname', 'test_instant');
+
+        $original = $handler->handlerfunction;
+
+        // change handler in db
+        set_field('events_handlers', 'handlerfunction', serialize('some_other_function_handler'), 'id', $handler->id);
+
+        // update the definition, it should revert the handler back
+        events_update_definition('unittest');
+        $handler = get_record('events_handlers', 'handlermodule', 'unittest', 'eventname', 'test_instant');
+        $this->assertEqual($handler->handlerfunction, $original, 'update should sync db with file definition: %s');
     }
-    
-    /** 
-    * tests trigger_event_is_registered funtion()
+
+    /**
+    * tests events_trigger_is_registered funtion()
     */
-    function test_event_is_registered() {
-        $this->assertTrue(event_is_registered('unittest', 'testevent'));
+    function test__events_is_registered() {
+        $this->assertTrue(events_is_registered('test_instant', 'unittest'));
     }
-    
+
+    /**
+     * tests events_trigger funtion()
+     */
+    function test__events_trigger__instant() {
+        $this->assertEqual(0, events_trigger('test_instant', 'ok'));
+        $this->assertEqual(0, events_trigger('test_instant', 'ok'));
+        $this->assertEqual(2, sample_function_handler('status'));
+    }
+
+    /**
+     * tests events_trigger funtion()
+     */
+    function test__events_trigger__cron() {
+        $this->assertEqual(0, events_trigger('test_cron', 'ok'));
+        $this->assertEqual(0, sample_handler_class::static_method('status'));
+        events_cron();
+        $this->assertEqual(1, sample_handler_class::static_method('status'));
+    }
+
+    /**
+     * tests events_pending_count()
+     */
+    function test__events_pending_count() {
+        events_trigger('test_cron', 'ok');
+        events_trigger('test_cron', 'ok');
+        $this->assertEqual(2, events_pending_count('test_cron'), 'two events should in queue: %s');
+        events_cron('test_cron');
+        $this->assertEqual(0, events_pending_count('test_cron'), 'all messages should be already dequeued: %s');
+    }
+
+    /**
+     * tests events_trigger funtion() when instant handler fails
+     */
+    function test__events_trigger__failed_instant() {
+        $this->assertEqual(1, events_trigger('test_instant', 'fail'), 'fail first event: %s');
+        $this->assertEqual(1, events_trigger('test_instant', 'ok'), 'this one should fail too: %s');
+        $this->assertEqual(0, events_cron('test_instant'), 'all events should stay in queue: %s');
+        $this->assertEqual(2, events_pending_count('test_instant'), 'two events should in queue: %s');
+        $this->assertEqual(0, sample_function_handler('status'), 'verify no event dispatched yet: %s');
+        sample_function_handler('ignorefail'); //ignore "fail" eventdata from now on
+        $this->assertEqual(1, events_trigger('test_instant', 'ok'), 'this one should go to queue directly: %s');
+        $this->assertEqual(3, events_pending_count('test_instant'), 'three events should in queue: %s');
+        $this->assertEqual(0, sample_function_handler('status'), 'verify previous event was not dispatched: %s');
+        $this->assertEqual(3, events_cron('test_instant'), 'all events should be dispatched: %s');
+        $this->assertEqual(3, sample_function_handler('status'), 'verify three events were dispatched: %s');
+        $this->assertEqual(0, events_pending_count('test_instant'), 'no events should in queue: %s');
+        $this->assertEqual(0, events_trigger('test_instant', 'ok'), 'this event should be dispatched immediately: %s');
+        $this->assertEqual(4, sample_function_handler('status'), 'verify event was dispatched: %s');
+        $this->assertEqual(0, events_pending_count('test_instant'), 'no events should in queue: %s');
+    }
+
+
+
 }
 
 ?>
\ No newline at end of file
index 3bb8451a5b5ce51d841542549bdfb8ab0838ee15..478c13f22b1d7aa6ad4f8f0280784371e93e41f8 100644 (file)
@@ -6,7 +6,7 @@
 // This is compared against the values stored in the database to determine
 // whether upgrades should be performed (see lib/db/*.php)
 
-   $version = 2007051801;  // YYYYMMDD = date
+   $version = 2007052200;  // YYYYMMDD = date
                            //       XY = increments within a single day
 
    $release = '1.9 dev';    // Human-friendly version name