Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Migrating logging calls in plugins: Difference between revisions

From MoodleDocs
No edit summary
(Adjusting more grammar.)
Line 1: Line 1:
This document is aimed to assist developers in replacing existing '''add_to_log()''' and '''events_trigger()''' calls with the events. This '''can''' be implemented in Moodle 2.6 and will be '''required''' in 2.7.
This document is aimed to assist developers in replacing existing '''add_to_log()''' and '''events_trigger()''' calls with events. This can be implemented in Moodle 2.6 and will be required in 2.7.


As a quick reminder: [[Event 2|new events]] were introduced in Moodle 2.6, a new [[Logging 2|logging system]] is being introduced in Moodle 2.7. Function '''add_to_log()''' will be deprecated, but the existing log table will still be present with existing data intact. This original logging is now called '''legacy logging'''. The new and legacy logging may coexist in the legacy logging system for purposes of transition, but this is not recommended for performance reasons. When replacing calls to add_to_log() with the triggering of an event, developers must ensure that they also generates an entry to the legacy log. It will only be used if the legacy log is enabled, since it may be enabled on systems that continue to use custom reports relying on presence of legacy log table and it may take time to migrate the reports.
As a quick reminder: [[Event 2|new events]] were introduced in Moodle 2.6, a new [[Logging 2|logging system]] is being introduced in Moodle 2.7. The '''add_to_log()''' function will be deprecated, but the existing log table will still be present with existing data intact. This original logging is now called ''legacy logging''. The new and legacy logging may coexist in the legacy logging system for purposes of transition, but this is not recommended for performance reasons. When replacing calls to add_to_log() with the triggering of an event, developers must ensure that they also generate an entry for the legacy log. It will only be used if the legacy log is enabled, since it may be enabled on systems that continue to use custom reports relying on presence of the legacy log table and it may take time to migrate such reports.


== Quick guide ==
== Quick guide ==


If you are replacing common add_to_log() calls such as "view" and "view all" in mod/XXX/view.php and mod/XXX/index.php, see below. Otherwise do the following:
If you are replacing common add_to_log() calls such as "view" and "view all" in mod/XXX/view.php and mod/XXX/index.php, see below. Otherwise do the following.


=== Step 1. Choose a name for event ===
=== Step 1. Choose a name for the event ===


It should have syntax OBJECT_VERB, for example "entry_added", "work_submitted", etc. It does not need to have plugin name in it because it is obvious from the PHP class namespace. See [[Event 2]] for more details on events names.
Names should follow the syntax OBJECT_VERB, for example "entry_added", "work_submitted", etc. It does not need to include a plugin name because this can be obtained from the PHP class namespace. See [[Event 2|the events documentation]] for more details about events names.


Define language string for the event name in '''YOURPLUGINDIR/lang/en/FULLPLUGINNAME.php''' :
Define a language string for the event name in '''YOURPLUGINDIR/lang/en/FULLPLUGINNAME.php'''.
<pre style="background: #DFF">
<pre style="background: #DFF">
$string['eventSOMETHINGHAPPENED] = 'SOMETHING has HAPPENED';
$string['eventEVENTNAME] = 'Something has happened';
</pre>
</pre>


=== Step 2. Create event class ===
=== Step 2. Create event class ===


It must be located in '''YOURPLUGINDIR/classes/event/SOMETHING_HAPPENED.php''' , typical example:
For each event you must create an event class in '''YOURPLUGINDIR/classes/event/EVENTNAME.php''', with the following format.
<pre style="background: #FFA">
<pre style="background: #FFA">
namespace FULLPLUGINNAME\event;
namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
defined('MOODLE_INTERNAL') || die();
class SOMETHING_HAPPENED extends \core\event\base {
class EVENTNAME extends \core\event\base {
     protected function init() {
     protected function init() {
         $this->data['crud'] = 'c'; // c(reate), r(ead), u(pdate), d(elete)
         $this->data['crud'] = 'c'; // c(reate), r(ead), u(pdate), d(elete)
Line 30: Line 30:


     public static function get_name() {
     public static function get_name() {
         return get_string('eventSOMETHINGHAPPENED', 'FULLPLUGINNAME');
         return get_string('eventEVENTNAME', 'FULLPLUGINNAME');
     }
     }


Line 38: Line 38:


     public function get_url() {
     public function get_url() {
         return new \moodle_url('....', array());
         return new \moodle_url('....', array('parameter' => 'value', ...));
     }
     }


     public function get_legacy_log_data() {
     public function get_legacy_log_data() {
         // Overwrite if you are migrating add_to_log() call.
         // Override if you are migrating an add_to_log() call.
         return array($this->courseid, 'PLUGINNAME', 'LOGACTION',
         return array($this->courseid, 'PLUGINNAME', 'LOGACTION',
             '...........',
             '...........',
Line 49: Line 49:


     public static function get_legacy_eventname() {
     public static function get_legacy_eventname() {
         // Overwrite ONLY if you are migrating events_trigger() call.
         // Override ONLY if you are migrating events_trigger() call.
         return 'MYPLUGIN_OLD_EVENT_NAME';
         return 'MYPLUGIN_OLD_EVENT_NAME';
     }
     }


     protected function get_legacy_eventdata() {
     protected function get_legacy_eventdata() {
         // Overwrite if you migrating events_trigger() call.
         // Override if you migrating events_trigger() call.
         $data = new \stdClass();
         $data = new \stdClass();
         $data->id = $this->objectid;
         $data->id = $this->objectid;
Line 63: Line 63:
</pre>
</pre>


=== Step 3. Trigger event instead of add_to_log() ===
=== Step 3. Trigger the event instead of add_to_log() ===


Replace add_to_log() with triggering of event. This is a common example for event inside an activity module:
Replace the add_to_log() with an event trigger. The following is a common example of an event trigger inside an activity module.
<pre>
<pre>
add_to_log($course->id, 'PLUGINNAME', 'LOGACTION', '...........', $objid, $cmid);
add_to_log($course->id, 'PLUGINNAME', 'LOGACTION', '...........', $objid, $cmid);
</pre>
</pre>


becomes:
...becomes...


<pre style="background: #FDD">
<pre style="background: #FDD">
$event = \FULLPLUGINNAME\event\SOMETHING_HAPPENED::create(array(
$event = \FULLPLUGINNAME\event\EVENTNAME::create(array(
     'objectid' => $objid,
     'objectid' => $objid,
     'context' => context_module::instance($cmid)
     'context' => context_module::instance($cmid)
Line 80: Line 80:
</pre>
</pre>


== Replacing 'view' event in the modules ==
== Replacing 'view' events in modules ==


This is usually found in mod/PLUGINNAME/view.php (or in lib function included from this file) and indicates that user viewed the module.  
Calls to add_to_log() to report a 'view' event are usually found in mod/PLUGINNAME/view.php (or in a lib function included by this file) and indicate that a user viewed the module.  


=== Step 1. Choosing the name ===
=== Step 1. Choosing the name ===


The name is already choosen - course_module_viewed - and the language string is defined in core.
Because this is a common event, the name is already chosen: '''course_module_viewed''' and the language string is defined in core.


=== Step 2. Defining class ===
=== Step 2. Defining class ===


'''YOURPLUGINDIR/classes/event/course_module_viewed.php''' :
You must create a class for this event in '''YOURPLUGINDIR/classes/event/course_module_viewed.php''' with the following format.
<pre style="background: #FFA">
<pre style="background: #FFA">
namespace FULLPLUGINNAME\event;
namespace FULLPLUGINNAME\event;
Line 99: Line 99:
         parent::init();
         parent::init();
     }
     }
     // You might need to overwrite get_url() and get_legacy_log_data() if view mode needs to be stored as well.
     // You might need to override get_url() and get_legacy_log_data() if view mode needs to be stored as well.
}
}
</pre>
</pre>


=== Step 3. Triggering event ===
=== Step 3. Triggering the event ===


This example takes data from $PAGE object but you may as well substite with ids and objects that you fetched:
This example takes data from $PAGE object but you may substitute this with ids and objects that you have fetched.
<pre style="background: #FDD">
<pre style="background: #FDD">
$event = \FULLPLUGINNAME\event\course_module_viewed::create(array(
$event = \FULLPLUGINNAME\event\course_module_viewed::create(array(
Line 116: Line 116:
</pre>
</pre>


== Replacing 'view all' event in the modules ==
== Replacing 'view all' events in modules ==


This is usually found in mod/PLUGINNAME/index.php (or in lib function included from this file) and indicates that user viewed list of modules of this type in the course.  
Calls to add_to_log using 'view_all' are usually found in mod/PLUGINNAME/index.php (or in a lib function included by this file). These invents indicate that a user viewed the list of all instances of this module within the course.  


=== Step 1. Choosing the name ===
=== Step 1. Choosing the name ===


The name is already choosen - course_module_instance_list_viewed - and the language string is defined in core.
Because this is a common event, the name is already chosen: '''course_module_instance_list_viewed''' and the language string is defined in core.


=== Step 2. Defining class ===
=== Step 2. Defining class ===


'''YOURPLUGINDIR/classes/event/course_module_instance_list_viewed.php''' :
You must create a class for this event in '''YOURPLUGINDIR/classes/event/course_module_instance_list_viewed.php''' with the following structure.
<pre style="background: #FFA">
<pre style="background: #FFA">
namespace FULLPLUGINNAME\event;
namespace FULLPLUGINNAME\event;
Line 134: Line 134:
</pre>
</pre>


=== Step 3. Triggering event ===
=== Step 3. Triggering the event ===


<pre style="background: #FDD">
<pre style="background: #FDD">
Line 147: Line 147:
=== init() and create() ===
=== init() and create() ===


As you noticed in the examples above you can specify additional fields either by overwriting init() method of event or when calling create(). The first way is for properties that are always the same for this event, the second is for dynamic properties.
Ideally all information needed when initialising and triggering events should already be available without having to run additional queries. Queries run to fill event objects with data will cause additional performance load, which should be avoided. Information that needs to be gathered from the database should be provided by other event methods, which can be called selectively when needed.


Usually you need to include fields:
As you noticed in the examples above you can specify additional properties either by the overriding the init() method of the event or when calling create(). The first way is used for properties that are always the same for this event, the second is for dynamic properties that may differ when the event is triggered.
* '''context''' or '''contextid''': required. If you want to hardcode system context, do so in init();
* '''crud''', '''edulevel''': required, most often are specified in init();
* '''objecttable''' and '''objectid''': used by events that show the change in one record of one table, which will be the case for the most of the events. Since 'objecttable' is always the same it is usually specified in init();
* '''relateduserid''': only if it is easy to select a single user who is affected by this operation. For example, user who is being graded, user who receives the message, user who is being enrolled, etc. This is NOT a user who performs the action;
* '''other''': everything else that you may think is important about this event. This field will be serialised and stored by logger. Developers should put here only necessary information for functions get_description(), get_url() and get_legacy_logdata(). This should only contain array or scalar value, '''can not use objects'''


Usually you need to include the following properties.
{| class="nicetable"
|-
| '''context''' or '''contextid'''
| required
| Describes the context where the event took place.
| If you want to hardcode the system context, do so in init().
|-
| '''crud'''
| required
| Describes whether the event reflects creation (c), reading (r), updating (u) or deleting (d). This should be a single character string.
| Most often are specified in init().
|-
| '''edulevel'''
| required
| The level of educational value of the event. Can be LEVEL_TEACHING, LEVEL_PARTICIPATING or LEVEL_OTHER.
| Most often are specified in init().
|-
| '''objecttable''' and '''objectid'''
|
| Used by events that show the change in one record of one table, which will be the case for the most events.
| Since 'objecttable' is always the same it is usually specified in init().
|-
| '''relateduserid'''
|
| The id of the user affected by the event.
| Only used if it is easy to identify a single user who is affected by this operation. For example a user who is being graded, a user who receives the message, a user being enrolled, etc. This is NOT the user who performs the action (who is identified in the userid field).
|-
| '''other'''
|
| Everything else that you may think is important about this event. This property will be serialised and stored by loggers.
| Include only necessary information, keeping in mind that there are also functions get_description(), get_url() and get_legacy_logdata(). This property should only contain an array or scalar value, it '''can not use objects'''.
Example of information stored in 'other' can be found in event course_module_deleted:
Example of information stored in 'other' can be found in event course_module_deleted:
* Definition: https://github.com/moodle/moodle/blob/master/lib/classes/event/course_module_deleted.php#L69
* Definition: https://github.com/moodle/moodle/blob/master/lib/classes/event/course_module_deleted.php#L69
* Triggering: https://github.com/moodle/moodle/blob/master/course/lib.php#L1716..L1727
* Triggering: https://github.com/moodle/moodle/blob/master/course/lib.php#L1716..L1727
|}


Usually you don't need to include fields:
Usually you don't need to include the following properties as they are deduced by the base class.
* 'userid': user who performs the action, taken from $USER
* 'userid': user who performs the action, taken from $USER
* 'courseid': course affected in the operation, it will be taken from context. It may not be specified at all for the events that are not related to a particular course.
* 'courseid': course affected in the operation, which will be taken from context. It need not be specified at all for events that are not related to a particular course.


See the full list of fields in [[Event_2#Information_contained_in_events]]
See the [[Event_2#Information_contained_in_events|full list of properties]] for more information.


=== get_legacy_logdata() ===
=== get_legacy_logdata() ===


Since this document is a transition guide from add_to_log() to the events, you will most likely need to overwrite this method. Return here a 5-element array which imitates the 5 arguments you used to pass to add_to_log() function. Since 2.7 the get_legacy_logdata() method will only be called if the legacy logging is enabled in legacy logging plugin.
This method is used to add log data to the legacy log. You need only override this method when replacing an add_to_log() call. Since this document is a transition guide from add_to_log() to events, you will most likely need to override this method. This function needs to return a five-element array that imitates the five arguments that used to be passed to the add_to_log() function. From Moodle 2.7, the get_legacy_logdata() method will only be called if legacy logging is enabled through the legacy logging plugin.


=== get_description() and get_url() ===
=== get_description() and get_url() ===


Most reporting tools will display aggregated logged information so those methods are not likely to be called often. They will only be used by detailed reports such as loglive. At the moment treat get_description() as internal very brief description of the performed action that is used for error recovery like any other system log. It is hardcoded in English but it is possible that in future versions of Moodle (2.8 or even later) it can start using language strings. Those methods can not make DB queries, access global variables, etc. They should return exactly the same result whenever they are called regardless of the environment. For example: course may be renamed, user may be deleted - '''do not retrieve course or user name but instead use their ids'''.
Most reporting tools will display aggregated event information (for example the count of student logins) so those methods, which describe individual events, are not likely to be called often; they will only be used by detailed reports such as loglive. At the moment, use get_description() to provide a very brief internal description of the action performed, so that it can be used for error recovery like any other system log. The description is hard-coded in English but it may be possible that future versions of Moodle (2.8 or later) will allow the use of translatable language strings. These methods should not make DB queries, access global variables, etc. For example, when a course is renamed or when a user is deleted, do not retrieve the course name or user name, instead simply use their ids. These functions should return exactly the same result whenever they are called, regardless of the environment or state, even after they have been restored from logs.  


=== get_legacy_eventname() and get_legacy_eventdata() ===
=== get_legacy_eventname() and get_legacy_eventdata() ===


Two functions that you need to overwrite if you upgrade events_trigger() calls and want other plugins to still be able to listen to your new events without upgrading their listeners.
You will need to override these two functions if you are upgrading events_trigger() calls. These will allow legacy plugins to continue to listen to your new events without upgrading their listeners.


If you need to provide more detailed information to observers, choose from:
=== add_record_snapshot() ===
* add fields to 'other' - but remember that they will be logged and it's better to keep it as small as possible;
* use record snapshots, especially useful for delete actions. You can call get_record_snapshot() inside get_legacy_eventdata() and observers are encouraged to get data from snapshots as well;
* add new field to your event class and define getter/setter functions, for example set_custom_data() and get_custom_data()  


=== can_view() ===
A record snapshot can be added for any DB table related to the event. If it is added it must be exact record from the DB, with correct property types and without additional properties. You must add a record snapshot when you delete something from database. '''Record snapshots cannot be used from reports, it is intended for event observers only.'''


Future of this function is not yet decided. It was intended as a callback allowing each event to determine whether the current user can see the logged event. But in reality checking various capabilities on the big amount of records is a very expensive process. At the moment '''events do not overwrite this function and it is not used'''. If you are interested in method future, watch/vote/comment on MDL-44107
Record snapshots should be added only when you already have an object and do not need to perform any additional DB queries to retrieve it. Otherwise omit it, as the record will be retrieved by get_record_snapshot() automatically, relying on objecttable and objectid, and only if needed. For performance reasons the snapshots are not guaranteed to contain an exact state at the time of event triggering, it may be actually fetched at any time between the triggering of event and its observation.


=== add_record_snapshot() ===
=== can_view() ===


Record snapshot can be added for any DB table related to the event. If it is added it must be exact record from the DB, with correct field types and without additional fields. Also developer must add a record snapshot when he deletes something from database. '''Record snapshots cannot be used from reports, it is intended for event observers only.'''
The future of this function is not yet decided. It was intended as a callback allowing each event to determine whether the current user can see the logged event. But in reality checking various capabilities on a large number of records is a very expensive process. At the moment '''events do not override this function and it is not used'''. If you are interested in this method's future, watch/vote/comment on MDL-44107.


Record snapshots should be added only when you already have an object do not need to perform any additional DB queries to retrieve it. Otherwise omit it, as the record will be retrieved by get_record_snapshot() automatically and only if needed. For performance reasons the snapshots are not guaranteed to contain the exact state at the time of event triggering, it may be actually fetched at any time between the triggering of event and its observation.


== Events DONTs ==
== Events DON'Ts ==


Do not put more information in 'other' than it is needed, for example full DB record for delete/create operation or list of all changed fields in edit operation. If observer is interested in those fields, it can request them by calling get_record_snapshot(). Never include large text fields in event data. '''Keep the log size reasonable.'''
Do not put more information in 'other' than is needed. For example, do not include a full DB record for delete/create operations or a list of all changed properties in edit operations. If observers are interested in this information, it can requested by calling get_record_snapshot(). Never include large text fields in event data. '''Please help to keep the log size reasonable.'''


Do NOT use $USER, $COURSE, $PAGE, and other global variables when overwriting get_* methods.
Do NOT use $USER, $COURSE, $PAGE or other global variables when overriding get_* methods.


Do NOT call $this->get_record_snapshot() inside event class, if you need additional information for internal functions, add it to 'other'.
Do NOT call $this->get_record_snapshot() inside the event class. If you need additional information for internal functions that cannot be added to existing properties, add it to the 'other' property.


Do NOT use $this->context inside an event class. '''Remember that methods get_description() and get_url() may be called on events restored from log.''' It is possible that the original context does not exist any more. Instead use $this->contextid, $this->contextlevel, $this->contextinstanceid.
Do NOT use $this->context inside an event class. '''Remember that methods get_description() and get_url() may be called on events after they have been restored from logs.''' It is possible that the original context no longer exists when these functions are called. Instead use $this->contextid, $this->contextlevel, $this->contextinstanceid.


== Validation and testing ==
== Validation and testing ==


You may notice that the most of existing events in Moodle also have function validate_data() . You can also add it for your own safety to ensure that you don't forget to define all required data when triggering event. Also we highly recommend to cover your events with unittests. Search for files with the names events_test.php in Moodle for examples.
You may notice that the most of events in Moodle also have function validate_data() . You can add this function for your own safety to ensure that you don't forget to define all required data when triggering event.
 
We highly recommend to cover your events with unit tests. Search in standard plugins for files with the names events_test.php to see examples.

Revision as of 06:40, 27 February 2014

This document is aimed to assist developers in replacing existing add_to_log() and events_trigger() calls with events. This can be implemented in Moodle 2.6 and will be required in 2.7.

As a quick reminder: new events were introduced in Moodle 2.6, a new logging system is being introduced in Moodle 2.7. The add_to_log() function will be deprecated, but the existing log table will still be present with existing data intact. This original logging is now called legacy logging. The new and legacy logging may coexist in the legacy logging system for purposes of transition, but this is not recommended for performance reasons. When replacing calls to add_to_log() with the triggering of an event, developers must ensure that they also generate an entry for the legacy log. It will only be used if the legacy log is enabled, since it may be enabled on systems that continue to use custom reports relying on presence of the legacy log table and it may take time to migrate such reports.

Quick guide

If you are replacing common add_to_log() calls such as "view" and "view all" in mod/XXX/view.php and mod/XXX/index.php, see below. Otherwise do the following.

Step 1. Choose a name for the event

Names should follow the syntax OBJECT_VERB, for example "entry_added", "work_submitted", etc. It does not need to include a plugin name because this can be obtained from the PHP class namespace. See the events documentation for more details about events names.

Define a language string for the event name in YOURPLUGINDIR/lang/en/FULLPLUGINNAME.php.

$string['eventEVENTNAME] = 'Something has happened';

Step 2. Create event class

For each event you must create an event class in YOURPLUGINDIR/classes/event/EVENTNAME.php, with the following format.

namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
class EVENTNAME extends \core\event\base {
    protected function init() {
        $this->data['crud'] = 'c'; // c(reate), r(ead), u(pdate), d(elete)
        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
        $this->data['objecttable'] = '...';
    }

    public static function get_name() {
        return get_string('eventEVENTNAME', 'FULLPLUGINNAME');
    }

    public function get_description() {
        return "User {$this->userid} has ... ... ... with id {$this->objectid}.";
    }

    public function get_url() {
        return new \moodle_url('....', array('parameter' => 'value', ...));
    }

    public function get_legacy_log_data() {
        // Override if you are migrating an add_to_log() call.
        return array($this->courseid, 'PLUGINNAME', 'LOGACTION',
            '...........',
            $this->objectid, $this->contextinstanceid);
    }

    public static function get_legacy_eventname() {
        // Override ONLY if you are migrating events_trigger() call.
        return 'MYPLUGIN_OLD_EVENT_NAME';
    }

    protected function get_legacy_eventdata() {
        // Override if you migrating events_trigger() call.
        $data = new \stdClass();
        $data->id = $this->objectid;
        $data->userid = $this->relateduserid;
        return $data;
    }
}

Step 3. Trigger the event instead of add_to_log()

Replace the add_to_log() with an event trigger. The following is a common example of an event trigger inside an activity module.

add_to_log($course->id, 'PLUGINNAME', 'LOGACTION', '...........', $objid, $cmid);

...becomes...

$event = \FULLPLUGINNAME\event\EVENTNAME::create(array(
    'objectid' => $objid,
    'context' => context_module::instance($cmid)
));
$event->trigger();

Replacing 'view' events in modules

Calls to add_to_log() to report a 'view' event are usually found in mod/PLUGINNAME/view.php (or in a lib function included by this file) and indicate that a user viewed the module.

Step 1. Choosing the name

Because this is a common event, the name is already chosen: course_module_viewed and the language string is defined in core.

Step 2. Defining class

You must create a class for this event in YOURPLUGINDIR/classes/event/course_module_viewed.php with the following format.

namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
class course_module_viewed extends \core\event\course_module_viewed {
    protected function init() {
        $this->data['objecttable'] = 'PLUGINNAME';
        parent::init();
    }
    // You might need to override get_url() and get_legacy_log_data() if view mode needs to be stored as well.
}

Step 3. Triggering the event

This example takes data from $PAGE object but you may substitute this with ids and objects that you have fetched.

$event = \FULLPLUGINNAME\event\course_module_viewed::create(array(
    'objectid' => $PAGE->cm->instance,
    'context' => $PAGE->context,
));
$event->add_record_snapshot('course', $PAGE->course);
$event->add_record_snapshot($PAGE->cm->modname, $activityrecord); // You can use $PAGE->activityrecord if you have set it or skip this line if you don't have a record.
$event->trigger();

Replacing 'view all' events in modules

Calls to add_to_log using 'view_all' are usually found in mod/PLUGINNAME/index.php (or in a lib function included by this file). These invents indicate that a user viewed the list of all instances of this module within the course.

Step 1. Choosing the name

Because this is a common event, the name is already chosen: course_module_instance_list_viewed and the language string is defined in core.

Step 2. Defining class

You must create a class for this event in YOURPLUGINDIR/classes/event/course_module_instance_list_viewed.php with the following structure.

namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
}

Step 3. Triggering the event

$event = \FULLPLUGINNAME\event\course_module_instance_list_viewed::create(array(
    'context' => context_course::instance($course->id)
));
$event->trigger();

What to include in the event

init() and create()

Ideally all information needed when initialising and triggering events should already be available without having to run additional queries. Queries run to fill event objects with data will cause additional performance load, which should be avoided. Information that needs to be gathered from the database should be provided by other event methods, which can be called selectively when needed.

As you noticed in the examples above you can specify additional properties either by the overriding the init() method of the event or when calling create(). The first way is used for properties that are always the same for this event, the second is for dynamic properties that may differ when the event is triggered.

Usually you need to include the following properties.

context or contextid required Describes the context where the event took place. If you want to hardcode the system context, do so in init().
crud required Describes whether the event reflects creation (c), reading (r), updating (u) or deleting (d). This should be a single character string. Most often are specified in init().
edulevel required The level of educational value of the event. Can be LEVEL_TEACHING, LEVEL_PARTICIPATING or LEVEL_OTHER. Most often are specified in init().
objecttable and objectid Used by events that show the change in one record of one table, which will be the case for the most events. Since 'objecttable' is always the same it is usually specified in init().
relateduserid The id of the user affected by the event. Only used if it is easy to identify a single user who is affected by this operation. For example a user who is being graded, a user who receives the message, a user being enrolled, etc. This is NOT the user who performs the action (who is identified in the userid field).
other Everything else that you may think is important about this event. This property will be serialised and stored by loggers. Include only necessary information, keeping in mind that there are also functions get_description(), get_url() and get_legacy_logdata(). This property should only contain an array or scalar value, it can not use objects.

Example of information stored in 'other' can be found in event course_module_deleted:

Usually you don't need to include the following properties as they are deduced by the base class.

  • 'userid': user who performs the action, taken from $USER
  • 'courseid': course affected in the operation, which will be taken from context. It need not be specified at all for events that are not related to a particular course.

See the full list of properties for more information.

get_legacy_logdata()

This method is used to add log data to the legacy log. You need only override this method when replacing an add_to_log() call. Since this document is a transition guide from add_to_log() to events, you will most likely need to override this method. This function needs to return a five-element array that imitates the five arguments that used to be passed to the add_to_log() function. From Moodle 2.7, the get_legacy_logdata() method will only be called if legacy logging is enabled through the legacy logging plugin.

get_description() and get_url()

Most reporting tools will display aggregated event information (for example the count of student logins) so those methods, which describe individual events, are not likely to be called often; they will only be used by detailed reports such as loglive. At the moment, use get_description() to provide a very brief internal description of the action performed, so that it can be used for error recovery like any other system log. The description is hard-coded in English but it may be possible that future versions of Moodle (2.8 or later) will allow the use of translatable language strings. These methods should not make DB queries, access global variables, etc. For example, when a course is renamed or when a user is deleted, do not retrieve the course name or user name, instead simply use their ids. These functions should return exactly the same result whenever they are called, regardless of the environment or state, even after they have been restored from logs.

get_legacy_eventname() and get_legacy_eventdata()

You will need to override these two functions if you are upgrading events_trigger() calls. These will allow legacy plugins to continue to listen to your new events without upgrading their listeners.

add_record_snapshot()

A record snapshot can be added for any DB table related to the event. If it is added it must be exact record from the DB, with correct property types and without additional properties. You must add a record snapshot when you delete something from database. Record snapshots cannot be used from reports, it is intended for event observers only.

Record snapshots should be added only when you already have an object and do not need to perform any additional DB queries to retrieve it. Otherwise omit it, as the record will be retrieved by get_record_snapshot() automatically, relying on objecttable and objectid, and only if needed. For performance reasons the snapshots are not guaranteed to contain an exact state at the time of event triggering, it may be actually fetched at any time between the triggering of event and its observation.

can_view()

The future of this function is not yet decided. It was intended as a callback allowing each event to determine whether the current user can see the logged event. But in reality checking various capabilities on a large number of records is a very expensive process. At the moment events do not override this function and it is not used. If you are interested in this method's future, watch/vote/comment on MDL-44107.


Events DON'Ts

Do not put more information in 'other' than is needed. For example, do not include a full DB record for delete/create operations or a list of all changed properties in edit operations. If observers are interested in this information, it can requested by calling get_record_snapshot(). Never include large text fields in event data. Please help to keep the log size reasonable.

Do NOT use $USER, $COURSE, $PAGE or other global variables when overriding get_* methods.

Do NOT call $this->get_record_snapshot() inside the event class. If you need additional information for internal functions that cannot be added to existing properties, add it to the 'other' property.

Do NOT use $this->context inside an event class. Remember that methods get_description() and get_url() may be called on events after they have been restored from logs. It is possible that the original context no longer exists when these functions are called. Instead use $this->contextid, $this->contextlevel, $this->contextinstanceid.

Validation and testing

You may notice that the most of events in Moodle also have function validate_data() . You can add this function for your own safety to ensure that you don't forget to define all required data when triggering event.

We highly recommend to cover your events with unit tests. Search in standard plugins for files with the names events_test.php to see examples.