Note: You are currently viewing documentation for Moodle 2.1. Up-to-date documentation for the latest stable version is available here: Events API.

Development:Events API: Difference between revisions

From MoodleDocs
 
(52 intermediate revisions by 19 users not shown)
Line 1: Line 1:
The Events API is a new core system in Moodle to allow better communication between modules.  It's based on modules triggering new events with attached data, and the other modules handling those events with custom functions.
==Overview==


==Overview==
The Events API is a core system in Moodle to allow communication between modules. 
 
An '''event''' is when something "interesting" happens in Moodle that is worth alerting the system about.
 
Any Moodle modules can '''trigger''' new events (with attached data), and other modules can elect to '''handle''' those events with custom functions that operate on the given data.
 
 
==Example==


We'll be using the example of a grade being posted from a module into the [[Development:Grades|new gradebook in Moodle 1.9]], but there are obviously all kinds of events possible.
Let's look at an example of how events are used to implement Messaging in Moodle 2.0. In the messaging system, textual messages are generated for users by different modules, and the user can decide how certain types of messages are displayed.


===Triggering an event===
===Triggering an event===


Whenever a grade is created or changed by a module, it should “tell” the system about it (in addition to any local working storage it uses).   So, using the quiz as an example, we first define an object as follows:
When a messaging event occurs, the module should trigger a "message_send" event.  In this example let's pretend someone just posted to a forum.
 
The forum module needs to create an object with the data that this event needs.  This may vary completely for different types of events, it's just a data object.


  $eventdata = new object;
  $eventdata = new object();
  $eventdata->courseid = $course->id;
  $eventdata->component        = 'mod/forum';   // path in Moodle
  $eventdata->itemname = $quiz->name;
  $eventdata->name             = 'posts';       // type of message from that module (as module defines it)
$eventdata->itemtype = 'mod';
  $eventdata->userfrom          = $userfrom;     // user object
  $eventdata->itemmodule = 'quiz';
  $eventdata->userto            = $userto;       // user object
  $eventdata->iteminstance = $quiz->id;
  $eventdata->subject          = "Hi there";     // short one-line subject
  $eventdata->itemnumber = 1;
  $eventdata->fullmessage      = "Here is the full message";     // raw text
  $eventdata->iteminfo = $quiz->info;
  $eventdata->fullmessageformat = FORMAT_PLAIN;  // text format
  $eventdata->idnumber = $cm->idnumber;  // new field in 1.9
  $eventdata->fullmessagehtml  = "Here is the <b>full</b> message";   // html rendered version  (optional)
  $eventdata->grademax = $quiz->grade;
  $eventdata->smallmessage      = "Here is the truncated message";     // useful for plugins like sms or twitter (optional)
$eventdata->grademin = 0;
  $eventdata->userid = $USER->id;
  $eventdata->gradevalue = $currentvalue;


Then we post the object as an event and forget about it:
Then we post the object as an event and forget about it:


  trigger_event('grade_added', $eventdata);
  events_trigger('message_send', $eventdata);
 


===Handling an event===
===Handling an event===


Modules can define an events.php in their db directory which defines events they want to be notified about, and describes which of their functions or class methods should be notified.   For example, an export  plugin could register something like:
Modules or core code can define an events.php in the db directory which defines events they want to be notified about, and describes which of their functions or class methods should be notified. For this case, there is this definition of a handler in lib/db/events.php


  $events = array (
  $handlers = array (
    'grade_added' => array (
    'message_send' => array (
        'file'       => '/grade/export/banner/lib.php',
          'handlerfile'     => '/lib/messagelib.php',
        'function'  => 'banner_handle_grade',   // argument to call_user_func(), could be an array
          'handlerfunction'  => 'message_send_handler',
        'timing'   => 'cron'
          'schedule'         => 'instant'
    );
      )
  );
  );


These are parsed during install / upgrade and stored in a simple database table.
These events.php files are parsed during install / upgrade and stored in a simple database table.
 
Now, when a '''message_send''' event happens, all the registered handlers functions for that event will be called something like this (but with more error handling):


Then, when a grade_added event happens, all the registered functions for that event will be called something like this (but with more error handling):
          include_once($CFG->dirroot.$handlers['message_send']['handlerfile']);
          call_user_func($handlers['message_send']['handlerfunction'], $eventdata);


          include_once($CFG->dirroot.$events['grade_added']['file'];
Any code can hook into any events this way.
          call_user_func($events['grade_added']['function'], $eventdata);


All plugins in Moodle have access to this and can this easily “hook in” to 'grade_added' events (and of course any other events).
The handler function accepts one parameter (the event data object) and should return a boolean.  Returning false indicates that there was an error and the event will be left in the event queue.


    function message_send_handler($eventdata) {
        // handle event
        // ...
        return true;
    }


==Database structure==
==Database structure==
Line 56: Line 68:
===events_handlers===
===events_handlers===


This table is for storing which components requests what type of event, and the location of the responsible handlers. For example, the grade book can register 'grade_added' event with a function add_grade() that should be called event time an 'grade_added' event is triggered by a module.
This table is for storing which components requests what type of event, and the location of the responsible handler functions.


These entries are created by parsing events.php files in all the modules, and can be rebuilt any time (during an upgrade, say).
These entries are created by parsing events.php files in all the modules, and can be rebuilt any time (during an upgrade, say).
Line 71: Line 83:
|eventname
|eventname
|varchar(255)
|varchar(255)
|name of the event, e.g. 'grade_added'
|name of the event, e.g. 'message_send'
|-
|-
|handlermodule
|handlermodule
Line 79: Line 91:
|handlerfile
|handlerfile
|varchar(255)
|varchar(255)
|path to the file of the function, eg /grade/export/lib.php
|path to the file of the function, eg /lib/messagelib.php
|-
|-
|handlerfunction
|handlerfunction
|text
|text
|serialized string or array describing function, suitable to be passed to '''call_user_func()'''
|serialized string or array describing function, suitable to be passed to '''call_user_func()'''
|-
|schedule
|varchar(255)
|'cron' or 'instant'.
|-
|status
|int(10)
|number of failed attempts to process this handler
|}
|}


===events_queues===
===events_queue===


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.
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 events_queue_handlers table.


{| border="1" cellpadding="2" cellspacing="0"
{| border="1" cellpadding="2" cellspacing="0"
Line 102: Line 122:
|longtext
|longtext
|serialized version of the data object passed to the event handler.
|serialized version of the data object passed to the event handler.
|-
|schedule
|varchar(255)
|'cron' or 'instant'.
|-
|-
|stackdump
|stackdump
Line 122: Line 138:
===events_queue_handlers===
===events_queue_handlers===


This is the list of queued handlers for processing. The event object is retrieved from the event_queued_events table. When no further reference is made to the event_queued_events table, the corresponding entry in the event_queued_events table should be deleted. Entry should get deleted (?) after a successful event processing by the specified handler.
This is the list of queued handlers for processing. The event object is retrieved from the events_queue table. When no further reference is made to the events_queue table, the corresponding entry in the events_queue table should be deleted. Entry should get deleted (?) after a successful event processing by the specified handler.  The status field keeps track of failures, after it gets to a certain number (eg 10?) it should trigger an "event failed" event (that could result in admin being emailed etc, or perhaps even the originating module taking care of it or rolling something back etc).


{| border="1" cellpadding="2" cellspacing="0"
{| border="1" cellpadding="2" cellspacing="0"
Line 135: Line 151:
|queuedeventid
|queuedeventid
|int(10)
|int(10)
|foreign key id corresponding to the id of the event_queued_events table
|foreign key id corresponding to the id of the event_queues table
|-
|-
|handlerid
|handlerid
Line 158: Line 174:
All event names should follow a consistent naming pattern, such as modulename_noun_verb
All event names should follow a consistent naming pattern, such as modulename_noun_verb


If the event is being fired after the action has taken place (as in most cases) then use the past tense for the verb (created / deleted / updated / sent).


If the event '''is''' the action, then use the present tense (create / delete / update / send).
==Events which exist==
When we add new events to core we should always add them here too.
Under each event, list the data sent as part of the event.
===Users===
* user_created
** full new record from 'user' table
* user_deleted
** record from 'user' table before marked as deleted
* user_updated
** full new record from 'user' table
===Roles===
* role_assigned (full new record from 'role_assignments' table)
* role_unassigned (record from 'role_assignments', course context only)
===Courses===
* course_created
** full course record
* course_updated
** full course record
* course_deleted
** full course record
* course_category_deleted
** full category record
===Groups===
* groups_member_added
** groupid
** userid
* groups_member_removed
** groupid
** userid
* groups_group_created
** id
** courseid
** name
** description
** timecreated
** timemodified
** picture
* groups_group_updated
** id
** courseid
** name
** description
** timecreated
** timemodified
** picture
* groups_group_deleted
** id
** courseid
** name
** description
** timecreated
** timemodified
** picture
* groups_grouping_created
** id
** courseid
** name
** timecreated
** timemodified
* groups_grouping_updated
** id
** courseid
** name
** timecreated
** timemodified
* groups_grouping_deleted
** id
** courseid
** name
** timecreated
** timemodified
* groups_members_removed (user deleted from all groups in a course)
** courseid
** userid
* groups_groupings_groups_removed (remove all groups from all groupings in a course)
** courseid (as plain integer, not object)
* groups_groups_deleted (delete all groups in a course)
** courseid (as plain integer, not object)
* groups_groupings_deleted (delete all groupings in a course)
** courseid (as plain integer, not object)
===Messaging===
* message_send
** component = 'mod/forum': path in Moodle
** name = 'posts': type of message from that module (as module defines it)
** userfrom = $userfrom: a user object to send from
** userto = $userto: a user object to send to
** subject = 'subject line': a short text line
** fullmessage = 'full plain text': raw text as entered by user
** fullmessageformat = FORMAT_PLAIN|FORMAT_HTML|FORMAT_MOODLE|FORMAT_MARKDOWN: the format of this text
** fullmessagehtml = 'long html text'; html rendered version (optional)
** smallmessage = 'short text': useful for plugins like sms or twitter (optional)
===Portfolio===
* portfolio_send
** id : recordid in portfolio_tempdata table, used for itemid in file storage
==Events wishlist==
List of events which it would be nice to have.  Please add to this list if what you want is not shown here.
* mform_print_form -- this for all types of form e.g. admin settings, user profile, module updating, + some sort of standard way of discriminiating between them e.g. if ($form->name == 'user_profile') {}. This would be better triggered at the end of the form generation process so that new bits can be inserted at any point, or existing bits could be removed.
* module_installed
* module_removed
* grade_update
* assignment_submitted
* course completed
* course module completion state changed (completed / not completed)
Provide event trigger hooks for the modules, similar to what is done for the cron service which checks the LOCAL directory for a cron file. It is already possible to define an event trigger but the core/module code must be modified to actually make use of it.


== See also ==
== See also ==


* [http://moodle.org/mod/forum/discuss.php?d=69103 General Developer Forum thread for discussing this proposal].  
* [http://moodle.org/mod/forum/discuss.php?d=69103 General Developer Forum thread for discussing this proposal].  
* [[Development:Grades]]
* [[Development:Messaging_2.0]]
 


[[Category:Developer]]
[[Category:Coding guidelines|Events]]
[[Category:Grades]]
[[Category:Grades]]

Latest revision as of 14:46, 10 February 2011

Overview

The Events API is a core system in Moodle to allow communication between modules.

An event is when something "interesting" happens in Moodle that is worth alerting the system about.

Any Moodle modules can trigger new events (with attached data), and other modules can elect to handle those events with custom functions that operate on the given data.


Example

Let's look at an example of how events are used to implement Messaging in Moodle 2.0. In the messaging system, textual messages are generated for users by different modules, and the user can decide how certain types of messages are displayed.

Triggering an event

When a messaging event occurs, the module should trigger a "message_send" event. In this example let's pretend someone just posted to a forum.

The forum module needs to create an object with the data that this event needs. This may vary completely for different types of events, it's just a data object.

$eventdata = new object();
$eventdata->component         = 'mod/forum';    // path in Moodle
$eventdata->name              = 'posts';        // type of message from that module (as module defines it)
$eventdata->userfrom          = $userfrom;      // user object
$eventdata->userto            = $userto;        // user object
$eventdata->subject           = "Hi there";     // short one-line subject
$eventdata->fullmessage       = "Here is the full message";      // raw text
$eventdata->fullmessageformat = FORMAT_PLAIN;   // text format
$eventdata->fullmessagehtml   = "Here is the <b>full</b> message";    // html rendered version   (optional)
$eventdata->smallmessage      = "Here is the truncated message";      // useful for plugins like sms or twitter  (optional)

Then we post the object as an event and forget about it:

events_trigger('message_send', $eventdata);

Handling an event

Modules or core code can define an events.php in the db directory which defines events they want to be notified about, and describes which of their functions or class methods should be notified. For this case, there is this definition of a handler in lib/db/events.php

$handlers = array (
    'message_send' => array (
         'handlerfile'      => '/lib/messagelib.php',
         'handlerfunction'  => 'message_send_handler',
         'schedule'         => 'instant'
     )
);

These events.php files are parsed during install / upgrade and stored in a simple database table.

Now, when a message_send event happens, all the registered handlers functions for that event will be called something like this (but with more error handling):

         include_once($CFG->dirroot.$handlers['message_send']['handlerfile']);
         call_user_func($handlers['message_send']['handlerfunction'], $eventdata);

Any code can hook into any events this way.

The handler function accepts one parameter (the event data object) and should return a boolean. Returning false indicates that there was an error and the event will be left in the event queue.

   function message_send_handler($eventdata) {
       // handle event 
       // ...
       return true;
   }

Database structure

There are 3 core tables for events. Note that if a handler is queued, and yet to be processed or processing failed, then all subsequent calls on that handler must be queued.

events_handlers

This table is for storing which components requests what type of event, and the location of the responsible handler functions.

These entries are created by parsing events.php files in all the modules, and can be rebuilt any time (during an upgrade, say).

Field Type Info
id int(10) auto increment identifier
eventname varchar(255) name of the event, e.g. 'message_send'
handlermodule varchar(255) e.g. moodle, mod/forum, block/rss_client
handlerfile varchar(255) path to the file of the function, eg /lib/messagelib.php
handlerfunction text serialized string or array describing function, suitable to be passed to call_user_func()
schedule varchar(255) 'cron' or 'instant'.
status int(10) number of failed attempts to process this handler

events_queue

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 events_queue_handlers table.

Field Type Info
id int(10) auto increment identifier
eventdata longtext serialized version of the data object passed to the event handler.
stackdump text serialized debug_backtrace showing where the event was fired from
userid int(10) $USER->id when the event was fired
timecreated int(10) time stamp of the first time this was added

events_queue_handlers

This is the list of queued handlers for processing. The event object is retrieved from the events_queue table. When no further reference is made to the events_queue table, the corresponding entry in the events_queue table should be deleted. Entry should get deleted (?) after a successful event processing by the specified handler. The status field keeps track of failures, after it gets to a certain number (eg 10?) it should trigger an "event failed" event (that could result in admin being emailed etc, or perhaps even the originating module taking care of it or rolling something back etc).

Field Type Info
id int(10) auto increment identifier
queuedeventid int(10) foreign key id corresponding to the id of the event_queues table
handlerid int(10) foreign key id corresponding to the id of the event_handlers table
status int(10) number of failed attempts to process this handler
errormessage text if an error happened last time we tried to process this event, record it here.
timemodified int(10) time stamp of the last attempt to run this from the queue

Standards for naming events

All event names should follow a consistent naming pattern, such as modulename_noun_verb

If the event is being fired after the action has taken place (as in most cases) then use the past tense for the verb (created / deleted / updated / sent).

If the event is the action, then use the present tense (create / delete / update / send).

Events which exist

When we add new events to core we should always add them here too.

Under each event, list the data sent as part of the event.

Users

  • user_created
    • full new record from 'user' table
  • user_deleted
    • record from 'user' table before marked as deleted
  • user_updated
    • full new record from 'user' table

Roles

  • role_assigned (full new record from 'role_assignments' table)
  • role_unassigned (record from 'role_assignments', course context only)

Courses

  • course_created
    • full course record
  • course_updated
    • full course record
  • course_deleted
    • full course record
  • course_category_deleted
    • full category record

Groups

  • groups_member_added
    • groupid
    • userid
  • groups_member_removed
    • groupid
    • userid
  • groups_group_created
    • id
    • courseid
    • name
    • description
    • timecreated
    • timemodified
    • picture
  • groups_group_updated
    • id
    • courseid
    • name
    • description
    • timecreated
    • timemodified
    • picture
  • groups_group_deleted
    • id
    • courseid
    • name
    • description
    • timecreated
    • timemodified
    • picture
  • groups_grouping_created
    • id
    • courseid
    • name
    • timecreated
    • timemodified
  • groups_grouping_updated
    • id
    • courseid
    • name
    • timecreated
    • timemodified
  • groups_grouping_deleted
    • id
    • courseid
    • name
    • timecreated
    • timemodified
  • groups_members_removed (user deleted from all groups in a course)
    • courseid
    • userid
  • groups_groupings_groups_removed (remove all groups from all groupings in a course)
    • courseid (as plain integer, not object)
  • groups_groups_deleted (delete all groups in a course)
    • courseid (as plain integer, not object)
  • groups_groupings_deleted (delete all groupings in a course)
    • courseid (as plain integer, not object)

Messaging

  • message_send
    • component = 'mod/forum': path in Moodle
    • name = 'posts': type of message from that module (as module defines it)
    • userfrom = $userfrom: a user object to send from
    • userto = $userto: a user object to send to
    • subject = 'subject line': a short text line
    • fullmessage = 'full plain text': raw text as entered by user
    • fullmessageformat = FORMAT_PLAIN|FORMAT_HTML|FORMAT_MOODLE|FORMAT_MARKDOWN: the format of this text
    • fullmessagehtml = 'long html text'; html rendered version (optional)
    • smallmessage = 'short text': useful for plugins like sms or twitter (optional)

Portfolio

  • portfolio_send
    • id : recordid in portfolio_tempdata table, used for itemid in file storage

Events wishlist

List of events which it would be nice to have. Please add to this list if what you want is not shown here.

  • mform_print_form -- this for all types of form e.g. admin settings, user profile, module updating, + some sort of standard way of discriminiating between them e.g. if ($form->name == 'user_profile') {}. This would be better triggered at the end of the form generation process so that new bits can be inserted at any point, or existing bits could be removed.
  • module_installed
  • module_removed
  • grade_update
  • assignment_submitted
  • course completed
  • course module completion state changed (completed / not completed)

Provide event trigger hooks for the modules, similar to what is done for the cron service which checks the LOCAL directory for a cron file. It is already possible to define an event trigger but the core/module code must be modified to actually make use of it.

See also