Events 2 | |
---|---|
Project state | Completed |
Tracker issue | MDL-39797 , MDL-39952, MDL-39846 |
Discussion | https://moodle.org/mod/forum/discuss.php?d=229425 |
Assignee | Backend Team |
What are events?
Events are atomic pieces of information describing something that happened in Moodle. Events are primarily the result of user actions, but could also be the result of the cron process or administration actions undertaken via the command line.
When an action takes place, an event is created by a core API or plugin. The Events system then disseminates this event information to observers registered for this event. In this way, the events system acts as a communication backbone throughout the Moodle system. Event observers can not modify event data or interrupt the dispatching of events, it is a one way communication channel.
Why was a new events system needed?
The need to improve the Events system was prompted by a need for a richer and more efficient logging system, however the benefits of this improvement are useful to other parts of Moodle that observe event information.
- The events need to be more strictly defined for logging and other advanced use cases. They need to contain more information, organised in a standardised way (in addition to the fields from the legacy log table and log_actions table).
- Complex data types were allowed in old events, which was causing major problems when serialising/storing/unserialising the data.
- The logging tables and events contain similar information and were triggered at the same places; utilising events for logging would remove this code duplication. All events should be loggable and all current log entries should be triggered as events.
- The logging system is an event observer, listening to all events and directing them to logging storage plugins in a controllable way.
- It is possible to subscribe to '*' event, which would allow a system to potentially observe, and selectively deal with, all events. Previously, handlers did not receive event names which made this problematic.
- Previously, event handlers could trigger exceptions during site upgrades, which would lead to fatal upgrade problems. The new design eliminates this.
- Failure in previous handlers blocked dispatching of subsequent events. Problems in new observers would only be logged and execution would continue normally.
- Previously, execution of external handlers during DB transactions blocked other handlers. This would be eliminated by in-memory buffer for external events.
- Previously there was no observer priority.
- Previously, nested events were not dispatched sequentially, which would change the order of events were received by lower priority handlers.
Performance
Some basic profiling was conducted.
This next two paragraphs need to be replaced with results.
There is a general plan to complete pre- and post-implementation testing as development happens. The new system will be implemented in parallel with the old one, which should help with comparison of new and old logging and event triggering performance on each page.
Our aim is to capture more information about actions in Moodle by triggering more events and logging more information about each event. This is going to impact negatively on performance. We hope to offset that impact by improving log storage, simplifying event dispatching and adding other core performance improvements. The proposed class structure of the base event should allow some new advanced techniques, which may also improve performance in some scenarios.
More details will be added to this section soon.
Events API
Each plugin defines the events that it can report (trigger) by extending an abstract base class, once for each possible event. This approach has several benefits.
- Events are active objects
- When they are triggered and possibly after they are reinstantiated (say, when they are retrieved from a log), an event object is able to provide callback functions for various purposes (such as event description).
- Automatic inclusion
- Event class definitions are automatically included when needed, without having to maintain lists of known event types. During development new event definitions and observers can be added without the need to upgrade, only purging of the MUC cache is required.
- Maintainability
- It is relatively simple to add new events and migrate existing events. Code review is simplified because there is less duplication of code when triggering same events and all event related information/code is concentrated in one file in fixed locations.
- Self documenting
- The behaviour of events is combined with the definition of events in one place (file). It is easy for event observer writers to know what events a plugin can trigger. This includes support for autocompletion and code inspection in modern IDEs. A list of all events is now available as a report to administrators and researchers.
- Quick, self-validating data structure
- As events are instantiated objects, the PHP processor will validate the structure and type of event classes. This does not ensure data value validity, but does give some assurance of consistency and it also detects unintentional typos.
Backwards compatibility and migration
Events:
- All legacy events in the standard distribution have been replaced with the new events API and events_trigger() has been deprecated.
- For events that already exist in Moodle 2.5, the additional legacy information has been added to the event data (in properties 'legacyeventname' and 'legacyeventdata')
- The legacy events handling code is maintained separately and will continue being supported in Moodle 2.7.
- All legacy events-handlers have been migrated to new observers in standard distribution.
- In future, more subsystems may be migrated to events-observers, ex.: gradebook history, completion.
Logging:
- The function add_to_log() has been deprecated with a notice as of Moodle 2.7.
- Existing add_to_log() parameters have been migrated to method get_legacy_log_data() in new events. Calls to core_event_base::trigger() will lead to entries being added to the legacy log table if the legacy log plugin is enabled.
See https://docs.moodle.org/dev/Migrating_logging_calls_in_plugins
Event dispatching and observers
The new event dispatching system is completely separate from the old events code. Original event handlers are now called observers with the description stored in the same db/events.php file, but as a new array with a different format.
Event observers
Observers are described in db/events.php in the array $observers, the array is not indexed and contains a list of observers defined as an array with the following properties;
- eventname – fully qualified event class name or "*" indicating all events, ex.: \plugintype_pluginname\event\something_happened.
- callback - PHP callable type.
- includefile - optional. File to be included before calling the observer. Path relative to dirroot.
- priority - optional. Defaults to 0. Observers with higher priority are notified first.
- internal - optional. Defaults to true. Non-internal observers are not called during database transactions, but instead after a successful commit of the transaction.
$observers = array(
array(
'eventname' => '\core\event\sample_executed',
'callback' => 'core_event_sample_observer::observe_one',
),
array(
'eventname' => '\core\event\sample_executed',
'callback' => 'core_event_sample_observer::external_observer',
'priority' => 200,
'internal' => false,
),
array(
'eventname' => '*',
'callback' => 'core_event_sample_observer::observe_all',
'includefile' => null,
'internal' => true,
'priority' => 9999,
),
);
NOTE: Event observers are cached. If you add or change observers you need to purge the caches or they will not be recognised. Plugin developers need to bump up the version number to guarantee that the list of observers is reloaded during upgrade.
Event dispatching
A list of available observers is constructed on the fly directly from all available 'events.php' files from core and all plugins. Previously handlers were installed only during installation and upgrade. This has little risk of performance regression because the list is already cached in MUC. Observers get events before installation or any upgrade, however observers are not notified during the initial installation of Moodle core tables.
Developers of observers must make sure that execution does not end with a fatal error under any condition (before install, before upgrade or normal operation). Exceptions are automatically captured, logged in the PHP error log, and notification of other observers continues. Original handlers could not throw any exceptions at any time.
Observers are notified sequentially in the same order in which events were triggered. This means that events triggered in observers are queued in FIFO buffer and are processed after all observers of the current event are notified.
Differences from old event handling
- New events contain a lot more structured information.
- There is a separate record snapshot cache that may be used when deleting data or for observer performance improvements.
- There is no database access in new event dispatching code.
- There is no support for delayed cron execution. This eliminates performance problems, simplifies events dispatching and prevents abuse of cron events.
- Events triggered in observers are processed in a different order.
- External events are buffered when a transaction is in progress, instead of being sent to the cron queue.
- It is possible to define multiple observers for one event in one events.php file.
- It is possible to subscribe an observer to all events.
- The new event manager is using frankenstyle autoloading, which leads to a smaller memory footprint when events are not used on the current page.
Triggering events
- All event definitions are classes extending the \core\event\base class.
- Events are triggered by creating a new instance of the class event and executing $event->trigger().
- Event class name is a unique identifier of each event.
- Class names and namespace follow the identifier scheme \frankenstyle_component\event\some_object_action. Core events are in namespace '\core\event\'.
- Each event class is defined in a separate file. File name and location must match the class name for the use of auto loading, for example: plugindir/classes/event/something_happened.php
- The event identifier suffix has the form some_object_action (something_happened in the example above) and should follow the standard naming convention. The last word after the underscore is automatically parsed as its action, the rest of word is its object.
Decision: Recommended verb list
Examples: \core\event\course_completed, \mod_assign\event\submission_commented, \mod_forum\event\post_shared, \mod_forum\event\post_responded, etc.
- Ideally, it should be possible to trigger an event without gathering additional information for the event. To reduce the cost of data gathering, specifically the cost of database reads, at least the minimal values needed to trigger an event should be already available in variables.
An example of triggering an event:
$event = \mod_myplugin\event\something_happened::create(array('context' => $context, 'objectid' => YYY, 'other' => ZZZ));
// ... code that may add some record snapshots
$event->trigger();
Use of PHP autoloading
All new OOP APIs in Moodle 2.6 onwards are going to use class auto loading - see Automatic class loading. New events use PHP within strictly defined namespaces, which concentrate all event classes in the classes/event/ subdirectory.
Why separate classes?
Pros
- Maintainability - It is much easier to review, debug and integrate.
- Self documenting, behaviour is combined with definition.
- It is extremely flexible for plugin developers and core devs too.
- Automatic lists events can be generated without being installed - PHPDocs as events documentation.
- It is included only when needed using autoloading.
- Self-validating data structure (by PHP).
- Some developers will find it easier to copy whole class files as templates.
Cons
- Big learning curve for developers without OOP skills (all other new subsystems in Moodle already use OOP, you can not code without these skills any more).
- Some developers may find it harder to copy-and-paste examples because they will need to create new class first and use it afterwards (this can be viewed as a benefit because it forces developers to think more about events).
- This has increased the number lines of code in Moodle for events and logging.
Information contained in events
Events have to contain as much information as they can, but this should not affect the performance. That's why part of the information is available in properties, and the rest via methods. This allows for delayed computation of data until the time it is really needed, if it ever is.
Properties
The following is a list of properties that the developer has to pass to the event upon creation, or ones that can be automatically generated where possible and cost free. Some of these properties are not mandatory.
Property name | Title | Type | Required | Comment |
---|---|---|---|---|
eventname | Event name | static, automatic from class name | - | Automatically computed by copying class name |
component | Component | static, automatic from top namespace | - | Component declaring the event, automatically computed from class name. |
action | Action | static, automatic from last word in class name | - | Can be automatically computed from class name. |
target | target of action | static, automatic from class name | - | Target on which the action is taken, can be automatically computed from class name. |
objecttable | Database table name | static | optional (Must be set if objectid present) | Database table name which represents the event object to the best. Never use a relationship table here. |
objectid | Object ID | variable | Optional (Must be set if objettable present) | Id of the object record from objecttable. |
crud | Transaction type | static | optional | One of [crud] letters - indicating 'c'reate, 'r'ead, 'u'pdate or 'd'elete operation. Statically declared in the event class method init(). |
level / edulevel | Level | static | optional | Level of educational value of the event. Statically declared in the event class method init(). Changed from level to edulevel in Moodle 2.7 (See below for more details) |
contextid | Context ID | mandatory | - | |
contextlevel | Context level | automatic from context | - | This tells you if it was a course, activity, course category, etc. |
contextinstanceid | Context instanceid | automatic from context | - | Based on context level this may be course id , course module id, course category, etc. (event->contextinstanceid) |
userid | User ID | defaults to current user | - | User ID, or 0 when not logged in, or -1 when other (System, CLI, Cron, ...) |
courseid | Affected course | defaults to course context from context | - | This is used only for contexts at and bellow course level - this can be used to filter events by course (includes all course activities) |
relateduserid | Affected user | variable | optional | Is this action related to some user? This could be used for some personal timeline view. |
anonymous | Anonymous action | variable | optional (Defaults to 0) | Is this action anonymous? Reports should ignore events with anonymous set to 1. |
other | All other data | variable array | optional | Any other fields needed for event description - scalars or arrays, must be serialisable using json_encode(). Floating point numbers cannot be used (see MDL-46701). |
timecreated | Time of the event | automatic | - | Time when the event was triggered. |
- static: same for all event instances of this class
- variable: might not be same for all instances of this event class
- mandatory: required in order to trigger the event
- optional: not necessarily required to trigger the event
Level property
The edulevel property helps define the educational value of the event. It is intentional that the list is limited to only 3 different constants as having too many options would make it harder for developers to pick the right one(s). We also have to keep in mind that this is not supposed to answer all the questions about a particular event, other event properties like the courseid, the context, the component name can be used with the level to get more granular reports.
Remember that this is event based. If the user that has triggered the event is not really "participating" because he is an admin, or a manager, then it is the job of the report to filter those. In a few cases, the educational level of an event depends on the context (site, course...) and/or the role (admin, teacher...) in these cases the selected educational level should be the most usual use case of that event. The event itself has a static educational level.
Teaching (LEVEL_TEACHING: 1)
Any event/action that is performed by someone (typically a teacher) and has a teaching value (anything that is effecting the learning experience/environment of the students). This should not be combined with "Participating" events.
Valid events:
- A teacher grading a student
- A teacher modifying the course settings
- A teacher adding a new section to the course page
- A teacher modifying a module settings
- A teacher adding a page to course
- A teacher leaving a feedback
INVALID events:
- A teacher posting in a forum (it might affect the learning experience, but not necessarily, so the teacher is just participating)
Participating (LEVEL_PARTICIPATING: 2)
Any event/action that is performed by a user, that is related (or could be related) to his learning experience.
Valid events:
- A user posting to a forum
- A user submitting an assignment
- A user blogging
- A user reading someone's blog
- A user posting a comment
- A user chatting on a chat activity
- A user viewing the course page
- A user deletes a blog post
INVALID events:
- A user updating his profile
- A user visiting someone's profile
- A user viewing his /my/ page
- A user sending a message to another one
Other (LEVEL_OTHER: 0)
Any other action, whether they are related to the site administration, or are specific to user. They do not have any educational value.
Methods
The computation of this data is not required by default, but can be accessed by any event observer if need be.
Method | Comment |
---|---|
get_name() | Returns localised name of the event, it is the same for all instances. |
get_description() | Returns non-localised description of one particular event. There are plans to make this localised in future.
It is recommended that the string begins with identifying the user who triggered the event by providing the user id in quotations. This applies to other variables used in the description as well. If an activity is also mentioned then the course module id should be used, not the id from the activity table. For example "The user with the id '$this->userid' updated the activity 'youractivity' with the course module id '$this->contextinstanceid'.". |
can_view($user) | This method is deprecated, please do not use this. |
get_url() | Returns Moodle URL where the event can be observed afterwards. Can be null, if no valid location is present. |
get_data() | Returns standardised event data as an array (a good starting point for the information you need to handle an event!) |
get_context() | Returns event context |
get_legacy_eventname() | Information necessary for event backward compatibility. |
get_legacy_eventdata() | Information necessary for event backward compatibility. |
get_legacy_logdata() | Information necessary for logging backward compatibility. |
Record caching
The standard event data may not contain all the information observers need. The built-in record snapshot support in events allows developers to attach more auxiliary information when triggering events, it may be for example course record, some record that was just deleted, etc.
- Snapshots are expected to be an exact snapshot of the database table at the instance when the event was triggered.
- Snapshot can be set and requested for any table. If not cached, it defaults to direct $DB->get_record calls.
- Please be aware that the snapshots are not stored in the logging subsystem, and cannot be used later when logged event is displayed.
- Snapshot are supposed to be used only by observers. They should never be used by reports or by the event itself.
- When using a snapshot in an observer, please make sure you are taking proper care of handling exceptions as the record you are requesting could have been deleted in the time, in between your observer is notified about the event and the trigger.
- We recommend all delete event must add snapshot of the deleted record.
$event = \core\event\role_assigned::create(
array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
$event->add_record_snapshot('role_assignments', $ra);
$event->trigger();
$event = \core\event\role_unassigned::create(
array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
$event->add_record_snapshot('role_assignments', $ra);
$event->trigger();
The related methods are:
- public function add_record_snapshot($tablename, $record)
- public function get_record_snapshot($tablename, $id)
Backup/restore
When we perform a restore of an event we may need to map the 'objectid' and 'other' information in order to restore the event accurately. For example - take the event mod_assign\event\feedback_viewed which sets the 'objectid' to the value in the 'assign_grades' table and also stores the 'assignid' in 'other' which represents the id in the 'assign' table. When restoring this we need to map the data to the newly created 'assign_grades' and 'assign' entries as the event will be related to a new assignment, like when restoring to a new course. In order for the mapping to work it is necessary for the event to specify the relationship between these values by defining the static functions get_objectid_mapping() and get_other_mapping(). See the code example below -
public static function get_objectid_mapping() {
return array('db' => 'assign_grades', 'restore' => 'grade');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign');
return $othermapped;
}
The 'db' value is the name of the database table the id is linked to, and the 'restore' value is what the item is named during restore. The mapping can be found in the restore code. In this case it is located in mod/assign/backup/moodle2/restore_assign_stepslib.php where we can see the grades are mapped by the following in the function process_assign_grade() -
$this->set_mapping('grade', $oldid, $newitemid, false, null, $this->task->get_old_contextid());
The $othermapped variable is an array of all the values in 'other' that require mapping. It is not necessary to list every single value in this array as they may not be related to a database table - only the ones that require mapping are needed.
If your event does not specify an 'objectid', then the get_objectid_mapping() is not necessary to declare. The same applies if there is no 'other' value. It is also common for 'other' to store data that doesn't need mapping at all, in this case simply declare the function but return false. For example, in mod_assign\event\submission_status_updated it is -
public static function get_other_mapping() {
// Nothing to map.
return false;
}
There are some events such as assignsubmission_onlinetext\event\submission_created where mapping is not possible. In this case we assign the value to \core\event\base::NOT_MAPPED.
public static function get_objectid_mapping() {
// No mapping available for 'assignsubmission_onlinetext'.
return array('db' => 'assignsubmission_onlinetext', 'restore' => \core\event\base::NOT_MAPPED);
}
This way we can distinguish in the logs which values were not mapped.
Events naming convention
Clear event names help developers when reading what events are triggered, and defining the events properties when defining the event class.
Decision: \<component>\event\<some_object>_<verb>
Technically, the verb should be in past participle form (e.g. created).
Existing events
List of existing events in Moodle code base, along with their 2.5 couterparts.
This list is out of date. For a full list of events check out the Event list report which is located in "Site administration > Reports > Event list" of your Moodle installation (2.7+).
Fully qualified event name | 2.5 name | Component | Object | Action (Verb) | Comment |
---|---|---|---|---|---|
assignsubmission_comments\event\comment_created | - | assignsubmission_comments | comment | created | |
assignsubmission_comments\event\comment_deleted | - | assignsubmission_comments | comment | deleted | |
assignsubmission_file\event\assessable_uploaded | assessable_file_uploaded | assignsubmission_file | assessable | uploaded | To be deprecated MDL-35197 |
assignsubmission_file\event\submission_created | - | assignsubmission_file | submission | created | |
assignsubmission_file\event\submission_updated | - | assignsubmission_file | submission | updated | |
assignsubmission_onlinetext\event\assessable_uploaded | assessable_content_uploaded | assignsubmission_onlinetext | assessable | uploaded | |
assignsubmission_onlinetext\event\submission_created | - | assignsubmission_onlinetext | submission | created | |
assignsubmission_onlinetext\event\submission_updated | - | assignsubmission_onlinetext | submission | updated | |
block_comments\event\comment_created | - | block_comments | comment | created | |
block_comments\event\comment_deleted | - | block_comments | comment | deleted | |
booktool_exportimscp\event\book_exported | - | booktool_exportimscp | book | exported | |
booktool_print\event\book_printed | - | booktool_print | book | printed | |
booktool_print\event\chapter_printed | - | booktool_print | chapter | printed | |
core\event\assessable_submitted | - | core | assessable | submitted | Abstract class |
core\event\assessable_uploaded | - | core | assessable | uploaded | Abstract class |
core\event\base | - | core | base | - | Abstract class |
core\event\blog_association_created | - | core | blog_association | created | |
core\event\blog_comment_created | - | core | blog_comment | created | |
core\event\blog_comment_deleted | - | core | blog_comment | deleted | |
core\event\blog_entries_viewed | - | core | blog_entries | viewed | |
core\event\blog_entry_created | blog_entry_added | core | blog_entry | created | |
core\event\blog_entry_deleted | blog_entry_deleted | core | blog_entry | deleted | |
core\event\blog_entry_updated | blog_entry_edited | core | blog_entry | updated | |
core\event\cohort_created | cohort_added | core | cohort | created | |
core\event\cohort_deleted | cohort_deleted | core | cohort | deleted | |
core\event\cohort_member_added | cohort_member_added | core | cohort_member | added | |
core\event\cohort_member_removed | cohort_member_removed | core | cohort_member | removed | |
core\event\cohort_updated | cohort_updated | core | cohort | updated | |
core\event\comment_created | - | core | comment | created | Abstract class |
core\event\comment_deleted | - | core | comment | deleted | Abstract class |
core\event\comments_viewed | - | core | comments | viewed | Abstract class |
core\event\content_viewed | - | core | content | viewed | Abstract class |
core\event\course_category_created | - | core | course_category | created | |
core\event\course_category_deleted | course_category_deleted | core | course_category | deleted | |
core\event\course_category_updated | - | core | course_category | updated | |
core\event\course_completed | course_completed | core | course | completed | |
core\event\course_completion_updated | - | core | course_completion | updated | |
core\event\course_content_deleted | course_content_removed | core | course_content | deleted | |
core\event\course_created | course_created | core | course | created | |
core\event\course_deleted | course_deleted | core | course | deleted | |
core\event\course_module_completion_updated | activity_completion_changed | core | course_module_completion | updated | |
core\event\course_module_created | mod_created | core | course_module | created | |
core\event\course_module_deleted | mod_deleted | core | course_module | deleted | |
core\event\course_module_instance_list_viewed | - | core | course_module_instance_list | viewed | Abstract class |
core\event\course_module_instances_list_viewed | - | core | course_module_instances_list | viewed | Abstract class |
core\event\course_module_updated | mod_updated | core | course_module | updated | |
core\event\course_module_viewed | - | core | course_module | viewed | Abstract class |
core\event\course_reset_ended | - | core | course_reset | ended | |
core\event\course_reset_started | - | core | course_reset | started | |
core\event\course_restored | course_restored | core | course | restored | |
core\event\course_section_updated | - | core | course_section | updated | |
core\event\course_updated | course_updated | core | course | updated | |
core\event\email_failed | - | core | failed | ||
core\event\group_created | groups_group_created | core | group | created | |
core\event\group_deleted | groups_group_deleted | core | group | deleted | |
core\event\group_member_added | groups_member_added | core | group_member | added | |
core\event\group_member_removed | groups_member_removed | core | group_member | removed | |
core\event\group_updated | groups_group_updated | core | group | updated | |
core\event\grouping_created | groups_grouping_created | core | grouping | created | |
core\event\grouping_deleted | groups_grouping_deleted | core | grouping | deleted | |
core\event\grouping_updated | groups_grouping_updated | core | grouping | updated | |
core\event\manager | - | core | manager | anager | |
core\event\mnet_access_control_created | - | core | mnet_access_control | created | |
core\event\mnet_access_control_updated | - | core | mnet_access_control | updated | |
core\event\note_created | - | core | note | created | |
core\event\note_deleted | - | core | note | deleted | |
core\event\note_updated | - | core | note | updated | |
core\event\notes_viewed | - | core | notes | viewed | |
core\event\role_allow_assign_updated | - | core | role_allow_assign | updated | |
core\event\role_allow_override_updated | - | core | role_allow_override | updated | |
core\event\role_allow_switch_updated | - | core | role_allow_switch | updated | |
core\event\role_assigned | role_assigned | core | role | assigned | |
core\event\role_capabilities_updated | - | core | role_capabilities | updated | |
core\event\role_deleted | - | core | role | deleted | |
core\event\role_unassigned | role_unassigned | core | role | unassigned | |
core\event\user_created | user_created | core | user | created | |
core\event\user_deleted | user_deleted | core | user | deleted | |
core\event\user_password_updated | user_password_updated | core | user | updated | |
core\event\user_enrolment_created | user_enrolled | core | user_enrolment | created | |
core\event\user_enrolment_deleted | user_unenrolled | core | user_enrolment | deleted | |
core\event\user_enrolment_updated | user_enrol_modified | core | user_enrolment | updated | |
core\event\user_list_viewed | - | core | user_list | viewed | |
core\event\user_loggedin | - | core | user | loggedin | |
core\event\user_loggedinas | - | core | user | loggedinas | |
core\event\user_loggedout | user_logout | core | user | loggedout | |
core\event\user_login_failed | - | core | user_login | failed | |
core\event\user_profile_viewed | - | core | user_profile | viewed | |
core\event\user_updated | user_updated | core | user | updated | |
core\event\webservice_function_called | - | core | webservice_function | called | |
core\event\webservice_login_failed | - | core | webservice_login | failed | |
core\event\webservice_service_created | - | core | webservice_service | created | |
core\event\webservice_service_deleted | - | core | webservice_service | deleted | |
core\event\webservice_service_updated | - | core | webservice_service | updated | |
core\event\webservice_service_user_added | - | core | webservice_service_user | added | |
core\event\webservice_service_user_removed | - | core | webservice_service_user | removed | |
core\event\webservice_token_created | - | core | webservice_token | created | |
core\event\webservice_token_sent | - | core | webservice_token | sent | |
logstore_legacy\event\legacy_logged | - | logstore_legacy | legacy | logged | |
mod_assign\event\all_submissions_downloaded | - | mod_assign | all_submissions | downloaded | |
mod_assign\event\assessable_submitted | assessable_submitted | mod_assign | assessable | submitted | |
mod_assign\event\extension_granted | - | mod_assign | extension | granted | |
mod_assign\event\identities_revealed | - | mod_assign | identities | revealed | |
mod_assign\event\marker_updated | - | mod_assign | marker | updated | |
mod_assign\event\statement_accepted | - | mod_assign | statement | accepted | |
mod_assign\event\submission_created | - | mod_assign | submission | created | Abstract class |
mod_assign\event\submission_duplicated | - | mod_assign | submission | duplicated | |
mod_assign\event\submission_graded | - | mod_assign | submission | graded | |
mod_assign\event\submission_locked | - | mod_assign | submission | locked | |
mod_assign\event\submission_status_updated | - | mod_assign | submission_status | updated | |
mod_assign\event\submission_unlocked | - | mod_assign | submission | unlocked | |
mod_assign\event\submission_updated | - | mod_assign | submission | updated | Abstract class |
mod_assign\event\workflow_state_updated | - | mod_assign | workflow_state | updated | |
mod_book\event\chapter_created | - | mod_book | chapter | created | |
mod_book\event\chapter_deleted | - | mod_book | chapter | deleted | |
mod_book\event\chapter_updated | - | mod_book | chapter | updated | |
mod_book\event\chapter_viewed | - | mod_book | chapter | viewed | |
mod_book\event\course_module_instance_list_viewed | - | mod_book | course_module_instance_list | viewed | |
mod_book\event\course_module_viewed | - | mod_book | course_module | viewed | |
mod_chat\event\course_module_instance_list_viewed | - | mod_chat | course_module_instance_list | viewed | |
mod_chat\event\message_sent | - | mod_chat | message | sent | |
mod_chat\event\sessions_viewed | - | mod_chat | sessions | viewed | |
mod_choice\event\answer_submitted | - | mod_choice | answer | submitted | |
mod_choice\event\answer_updated | - | mod_choice | answer | updated | |
mod_choice\event\course_module_instance_list_viewed | - | mod_choice | course_module_instance_list | viewed | |
mod_choice\event\course_module_viewed | - | mod_choice | course_module | viewed | |
mod_choice\event\report_viewed | - | mod_choice | report | viewed | |
mod_data\event\comment_created | - | mod_data | comment | created | |
mod_data\event\comment_deleted | - | mod_data | comment | deleted | |
mod_data\event\course_module_instance_list_viewed | - | mod_data | course_module_instance_list | viewed | |
mod_data\event\course_module_viewed | - | mod_data | course_module | viewed | |
mod_data\event\field_created | - | mod_data | field | created | |
mod_data\event\field_deleted | - | mod_data | field | deleted | |
mod_data\event\field_updated | - | mod_data | field | updated | |
mod_data\event\record_created | - | mod_data | record | created | |
mod_data\event\record_deleted | - | mod_data | record | deleted | |
mod_data\event\record_updated | - | mod_data | record | updated | |
mod_data\event\template_updated | - | mod_data | template | updated | |
mod_data\event\template_viewed | - | mod_data | template | viewed | |
mod_feedback\event\course_module_instance_list_viewed | - | mod_feedback | course_module_instance_list | viewed | |
mod_feedback\event\course_module_viewed | - | mod_feedback | course_module | viewed | |
mod_feedback\event\response_deleted | - | mod_feedback | response | deleted | |
mod_feedback\event\response_submitted | - | mod_feedback | response | submitted | |
mod_folder\event\course_module_instance_list_viewed | - | mod_folder | course_module_instance_list | viewed | |
mod_folder\event\course_module_viewed | - | mod_folder | course_module | viewed | |
mod_folder\event\folder_updated | - | mod_folder | folder | updated | |
mod_forum\event\assessable_uploaded | assessable_content_uploaded | mod_forum | assessable | uploaded | |
mod_forum\event\course_module_instance_list_viewed | - | mod_forum | course_module_instance_list | viewed | |
mod_forum\event\course_searched | - | mod_forum | course | searched | |
mod_forum\event\discussion_created | - | mod_forum | discussion | created | |
mod_forum\event\discussion_deleted | - | mod_forum | discussion | deleted | |
mod_forum\event\discussion_moved | - | mod_forum | discussion | moved | |
mod_forum\event\discussion_updated | - | mod_forum | discussion | updated | |
mod_forum\event\discussion_viewed | - | mod_forum | discussion | viewed | |
mod_forum\event\forum_viewed | - | mod_forum | forum | viewed | |
mod_forum\event\post_created | - | mod_forum | post | created | |
mod_forum\event\post_deleted | - | mod_forum | post | deleted | |
mod_forum\event\post_updated | - | mod_forum | post | updated | |
mod_forum\event\readtracking_disabled | - | mod_forum | readtracking | disabled | |
mod_forum\event\readtracking_enabled | - | mod_forum | readtracking | enabled | |
mod_forum\event\subscribers_viewed | - | mod_forum | subscribers | viewed | |
mod_forum\event\subscription_created | - | mod_forum | subscription | created | |
mod_forum\event\subscription_deleted | - | mod_forum | subscription | deleted | |
mod_forum\event\userreport_viewed | - | mod_forum | userreport | viewed | |
mod_glossary\event\comment_created | - | mod_glossary | comment | created | |
mod_glossary\event\comment_deleted | - | mod_glossary | comment | deleted | |
mod_lesson\event\course_module_instance_list_viewed | - | mod_lesson | course_module_instance_list | viewed | |
mod_lesson\event\course_module_viewed | - | mod_lesson | course_module | viewed | |
mod_lesson\event\essay_assessed | - | mod_lesson | essay | assessed | |
mod_lesson\event\essay_attempt_viewed | - | mod_lesson | essay_attempt | viewed | |
mod_lesson\event\highscore_added | - | mod_lesson | highscore | added | |
mod_lesson\event\highscores_viewed | - | mod_lesson | highscores | viewed | |
mod_lesson\event\lesson_ended | - | mod_lesson | lesson | ended | |
mod_lesson\event\lesson_started | - | mod_lesson | lesson | started | |
mod_lti\event\course_module_instance_list_viewed | - | mod_lti | course_module_instance_list | viewed | |
mod_lti\event\course_module_viewed | - | mod_lti | course_module | viewed | |
mod_lti\event\unknown_service_api_called | lti_unknown_service_api_call | mod_lti | unknown_service_api | called | |
mod_page\event\course_module_instance_list_viewed | - | mod_page | course_module_instance_list | viewed | |
mod_page\event\course_module_viewed | - | mod_page | course_module | viewed | |
mod_quiz\event\attempt_abandoned | quiz_attempt_abandoned | mod_quiz | attempt | abandoned | |
mod_quiz\event\attempt_becameoverdue | quiz_attempt_overdue | mod_quiz | attempt | becameoverdue | |
mod_quiz\event\attempt_started | quiz_attempt_started | mod_quiz | attempt | started | |
mod_quiz\event\attempt_submitted | quiz_attempt_submitted | mod_quiz | attempt | submitted | |
mod_resource\event\course_module_instance_list_viewed | - | mod_resource | course_module_instance_list | viewed | |
mod_resource\event\course_module_viewed | - | mod_resource | course_module | viewed | |
mod_scorm\event\attempt_deleted | - | mod_scorm | attempt | deleted | |
mod_scorm\event\course_module_instance_list_viewed | - | mod_scorm | course_module_instance_list | viewed | |
mod_scorm\event\course_module_viewed | - | mod_scorm | course_module | viewed | |
mod_scorm\event\interactions_viewed | - | mod_scorm | interactions | viewed | |
mod_scorm\event\report_viewed | - | mod_scorm | report | viewed | |
mod_scorm\event\sco_launched | - | mod_scorm | sco | launched | |
mod_scorm\event\tracks_viewed | - | mod_scorm | tracks | viewed | |
mod_scorm\event\user_report_viewed | - | mod_scorm | user_report | viewed | |
mod_url\event\course_module_instance_list_viewed | - | mod_url | course_module_instance_list | viewed | |
mod_url\event\course_module_viewed | - | mod_url | course_module | viewed | |
mod_wiki\event\comment_created | - | mod_wiki | comment | created | |
mod_wiki\event\comment_deleted | - | mod_wiki | comment | deleted | |
mod_wiki\event\comments_viewed | - | mod_wiki | comments | viewed | |
mod_wiki\event\course_module_instance_list_viewed | - | mod_wiki | course_module_instance_list | viewed | |
mod_wiki\event\course_module_viewed | - | mod_wiki | course_module | viewed | |
mod_wiki\event\page_created | - | mod_wiki | page | created | |
mod_wiki\event\page_deleted | - | mod_wiki | page | deleted | |
mod_wiki\event\page_diff_viewed | - | mod_wiki | page_diff | viewed | |
mod_wiki\event\page_history_viewed | - | mod_wiki | page_history | viewed | |
mod_wiki\event\page_locks_deleted | - | mod_wiki | page_locks | deleted | |
mod_wiki\event\page_map_viewed | - | mod_wiki | page_map | viewed | |
mod_wiki\event\page_updated | - | mod_wiki | page | updated | |
mod_wiki\event\page_version_deleted | - | mod_wiki | page_version | deleted | |
mod_wiki\event\page_version_restored | - | mod_wiki | page_version | restored | |
mod_wiki\event\page_version_viewed | - | mod_wiki | page_version | viewed | |
mod_wiki\event\page_viewed | - | mod_wiki | page | viewed | |
mod_workshop\event\assessable_uploaded | assessable_content_uploaded | mod_workshop | assessable | uploaded | |
mod_workshop\event\assessment_evaluated | - | mod_workshop | assessment | evaluated | |
mod_workshop\event\assessment_evaluations_reset | - | mod_workshop | assessment_evaluations | reset | |
mod_workshop\event\assessment_reevaluated | - | mod_workshop | assessment | reevaluated | |
mod_workshop\event\course_module_viewed | workshop_viewed | mod_workshop | course_module | viewed | |
mod_workshop\event\instances_list_viewed | - | mod_workshop | instances_list | viewed | |
mod_workshop\event\phase_switched | - | mod_workshop | phase | switched | |
mod_workshop\event\submission_assessed | - | mod_workshop | submission | assessed | |
mod_workshop\event\submission_created | - | mod_workshop | submission | created | |
mod_workshop\event\submission_reassessed | - | mod_workshop | submission | reassessed | |
mod_workshop\event\submission_updated | - | mod_workshop | submission | updated | |
mod_workshop\event\submission_viewed | - | mod_workshop | submission | viewed | |
report_log\event\content_viewed | - | report_log | content | viewed | |
report_loglive\event\content_viewed | - | report_loglive | content | viewed | |
report_outline\event\content_viewed | - | report_outline | content | viewed | |
report_participation\event\content_viewed | - | report_participation | content | viewed | |
report_stats\event\content_viewed | - | report_stats | content | viewed |
Verb list
All events must use a verb from this list. New verbs can be added to this list if required, but additions should only be made if there is no valid alternative (we want to keep this list as small as possible).
verb | Explanation | Source |
---|---|---|
abandoned | When a attempt is abandoned by user (Quiz attempt) | Moodle |
accepted | Example: Accepting a statement when submitting an assignment. | Moodle |
added | Used to represent "something that already exists is now part of/bound to another entity". Examples: "Admin added role to user X", "Admin added user X to group A". Wrong example: "User added course in category" because it is a 'move' action, except if a course can be part of multiple categories. The good examples work because: A user can have multiple roles, a user can be in multiple groups. | Moodle |
answered | Indicates the actor responded to a Question | Tincan |
assessed | Some submitted material has been assessed | Moodle |
assigned | Assign some privilege or role to user. | Moodle |
attempted | Trying to do an activity. Example: attempting a Math class. | Tincan |
awarded | ex:-teacher awarded student a badge. | Moodle |
backedup | When a backup has been performed. | Moodle |
becomeoverdue | When an activity is overdue Example: Quiz attempt is overdue | Moodle |
called | When a call to something is made like an API @see unknown_service_api_called.php | Moodle |
commented | Offered an opinion or written experience of the activity. Can be used with the learner as the actor or a system as an actor. Comments can be sent from either party with the idea that the other will read and react to the content. | Tincan |
completed | To experience the activity in its entirety. Used to affirm the completion of content. This can be simply experiencing all the content, be tied to objectives or interactions, or determined in any other way. Any content that has been initialized, but not yet completed, should be considered incomplete. There is no verb to 'incomplete' an activity, one would void the statement which completes the activity." | Tincan |
created | Used to represent "something new has been created". | Moodle |
deleted | Used to indicate the object in context was deleted. | Moodle |
disabled | When an activity is disabled. Example: forum read tracking disabled. | Moodle |
downloaded | When a user download file from user. Example submission/report downloaded. | Moodle |
duplicated | For something that has been copied. | Moodle |
enabled | When some setting is enabled. Example: forum read tracking enabled. | Moodle |
ended | When a process ends. Example: Lesson ended or course reset ended. | Moodle |
evaluated | Material has been evaluated. | Moodle |
exported | When a report is exported in certain format. | Moodle |
failed | Learner did not perform the activity to a level of pre-determined satisfaction. Used to affirm the lack of success a learner experienced within the learning content in relation to a threshold. If the user performed below the minimum to the level of this threshold, the content is 'failed'. The opposite of 'passed'. This is also used in case when message sending is failed. | Tincan |
graded | Used to represent an activity was graded. | Moodle |
granted | User is granted some extension or capability. Example: extension granted for submission. | Moodle |
imported | The act of moving an object into another location or system. | Tincan |
launched | When an external object is launched. Try consider started if there is related stopped event. | Moodle |
locked | When an activity is locked. Should have a related unlocked event. | Moodle |
loggedin/loggedout | For login and logout. | Moodle |
loggedinas | If user is logged in as different user. This is used by only one event (user_loggedinas). Adding this verb makes event name more clear, then using loggedin verb. | Moodle |
locked | Moodle | |
moved | Used to indicate the object in context was moved. | Moodle |
passed | Used to affirm the success a learner experienced within the learning content in relation to a threshold. If the user performed at a minimum to the level of this threshold, the content is 'passed'. The opposite of 'failed'. | Tincan |
printed | Something is printed. | Moodle |
reassessed | Submitted material has been assessed again. | Moodle |
reevaluated | Material has been evaluated again. | Moodle |
removed | By opposition to "Added". This does not mean that the object has been deleted, but removed from the entity, or not bound to it any more. | Moodle |
replaced | Similar to "Updated". But when the change does not apply to a clear identifiable identity and somehow is global, broader. | Moodle |
reset | Sets one or more properties back to the default value. | Moodle |
restored | When restoring a backup. Rolling back to a previous state. | Moodle |
revealed | Some identity is revealed. Example: Identities revealed after blind marking. | Moodle |
searched | Something is searched. Example: searched in course. | Moodle |
sent | Message sent. | Moodle |
started | Some activity started | Moodle |
submitted | This is very close to "Attempted". Depends on context which one should be used. For example:- "Admin submitted a form. Student attempted a quiz." is correct, however some cases might not be as clear as the previous example. We can say both "Student submitted an assignment" or "student attempted an assignment". We need to make the difference clear. | Moodle |
suspended | Suspend something. (example a user) | Tincan (However the context is different) |
switched | Something has been switched. For example:- The workshop phase has been switched to assessment" | Moodle |
unassigned | As opposed to assigned. When some role is unassigned. | Moodle |
unlocked | Moodle | |
upgraded | Something was upgraded, some module probably | Moodle |
updated | Used to indicate the object in context was updated. Simple example is "Admin updated course xyz". | Moodle |
uploaded | When an assignment is uploaded. | Moodle |
viewed | Something has been viewed. For example:- "Student viewed chapter 1 of book 1." | Moodle |
Rules
Use singular
Plurals must be used on objects when it's a One to Many relationship. Ex: bulk import, mass deletion, ... In any other case, use the singular.
Ends with a verb
The last word (after the last underscore) must be a verb.
Consistency among child events
If an event is extending a parent class, it should have the same exact name as the parent event.
Deprecated events
Following are the events that were supported in 2.5, but deprecated in 2.6 or later
groups_groupings_deleted (see MDL-41312)
groups_groupings_groups_removed (see MDL-41312)
groups_groups_deleted (see MDL-41312)
groups_members_removed (see MDL-41312)
Decision: Not supported at this stage.
In Moodle 2.5 we have a good example of a shared event: 'assessable_content_uploaded' which is triggered in forum, 'assignment and workshop.
The problem with shared events is that we cannot easily track what component triggered them. Of course we could add a new property to the event to keep track of that, but we would soon need more information and more properties. Also, in the case of a logger, the event received would be unique, where in fact it should be considered different depending on the component firing it.
In our first implementation, we will create one specific event per module. This flexibility does not prevent any observer from capturing them, but still makes sure that the consistency and specificity of each event is maintained.
It could happen that some events are defined in core and shared, but this should not really happen as low-level APIs should trigger the event, and a module should call that low API instead of doing the job itself.
One to many
Decision: Each event should have a one to one relationship. We can reconsider this at a later stage, if the performance hit is extremely high.
In 2.5, some events are triggered when an action happens on multiple objects. We have to decide whether we want to keep supporting One to Many events or not.
Keeping a list of all changes for multiple actions may be problematic because you would have to keep them all in memory until all things are processed. This might also result in the order of events being incorrect. The only correct solution seems to be to trigger each item individually and then many things at the end. Performance needs to be improved elsewhere...
Accuracy
It is important to trigger individual events for each and every action, bulk events are very strongly discouraged because existing observers would not receive important information. Changes from one to many would break backwards compatibility with observers and reporting.
Performance
Triggering one event is cheaper then repeating the same events x number of times...
Information tracking
A bulk event, might not be verbose enough to allow for proper logging afterwards. Though this is the responsibility of the logger, we probably want to make it easy to store relevant information.
Double event
In the case of a bulk user import, if we were to trigger an event per user created, we probably want to trigger one event 'user_bulk_upload_started' when the action starts.
Unit Testing
With unit testing for this system we want to assert the following:
- That event strict validation and custom validation works.
- Missing event data is auto filled with accurate data.
- Typos in properties passed to ::create() are captured (if we decide to validate).
- The legacy methods return the expected values (use assertEventLegacyData() and assertEventLegacyData())
- The class properties are correctly overridden (crud, level, action, object, ...).
- The properties automatically generated (component, name, ...) are correct.
- Events are dispatched to the corresponding observers.
- Events are dispatched to the corresponding legacy handlers.
- Events are dispatched to the * observers.
- Events perform an add_to_log() if it has legacy log data.
- 'Events restore' restored the whole event data, and does not miss any information.
- 'Events restore' does not generate any extra information.
- Event methods should check context object or avoid using it, as context might not be valid at time of event restore (use assertEventContextNotUsed())
PHP docs
- All events php docs must include @since parameter, indicating when the event was first included in standard Moodle distribution.
- All events must declare the $other properties using mark down in the php docs. This later might be converted to a self documenting structure. (See MDL-45108)
/**
* blog_association_created
*
* Class for event to be triggered when a new blog entry is associated with a context.
*
* @property-read array $other {
* Extra information about event.
*
* - string associatetype: type of blog association, course/coursemodule.
* - int blogid: id of blog.
* - int associateid: id of associate.
* - string subject: blog subject.
* }
*
* @package core
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
Example events
Assignment
Assumption: Course contains groups with students in each group.
1. Teacher creates an assignment with group mode set to 'Separate groups' and Feedback type set to comments and files.
- Event: User 'Teacher' has created assignment 'B' in course 'C101'.
2. A student views the assignment.
- Event: User 'Student' has viewed assignment 'B' in course 'C101'.
3. A member from one of the groups submits an assignment
- Event: User 'Student' has added a submission for assignment 'B' for group 'C' in course 'C101'.
- Event: Email sent to the teacher and all students in that group.
4. User 'Adrian' adds some changes to the assignment and updates it.
- Event: User 'Adrian' has updated the submission for assignment 'B' for group 'C' in course 'C101'.
- Event: Email sent to the teacher and all students in that group.
5. Teacher views the assignment.
- Event: User 'Teacher' has viewed assignment 'B' in course 'C101'.
6. Teacher clicks on 'View/grade all submissions'
- Event: User 'Teacher' has viewed the assignment 'B' grade area in course 'C101'.
7. Teacher clicks to grade the student's submission.
- Event: User 'Teacher' has viewed the submission for user 'student' for assignment 'B' in course 'C101'.
8. Teacher marks the assignment with the setting 'Apply grades and feedback to entire group' set to 'Yes' leaving a comment and a file.
- Event: User 'Teacher' has marked assignment 'B' for group 'C' in course 'C101'.
- Event: User 'Teacher' has left a comment for assignment 'B' for group 'C' in course 'C101'.
- Event: User 'Teacher' has uploaded a feedback file for assignment 'B' for group 'C' in course 'C101'.
- Event: User 'Teacher' has uploaded a file to the course 'C101'.
- Event: Email sent to user 'Student' notifying them their submission for assignment 'B' has been marked. - This is done for all users in the group.
9. User 'Adrian' views the feedback.
- Event: User 'Adrian' has viewed assignment 'B' in course 'C101'.
10. User 'Adrian' opens the feedback file.
- Event: User 'Adrian' has viewed the file 'A' for assignment 'B' in course 'C101'.
11. User 'Adrian' adds some changes to the assignment insulting the teachers marking and updates it.
- Event: User 'Adrian' has updated the submission for assignment 'B' for group 'C' in course 'C101'.
- Event: Email sent to user 'Teacher' notifying them that user 'Adrian' has updated the submission for assignment 'B'.
- Event: Email sent to user 'Student' notifying them their submission for assignment 'B' has been updated. - This is done for all users in the group.
12. The teacher clicks directly on the link in the email to be taken to the grading page.
- Event: User 'Teacher' has viewed the submission for user 'student' for assignment 'B' in course 'C101'.
13. The teacher is upset due to the harsh comments and decides to mark Adrian down, but not the rest of the group by setting 'Apply grades and feedback to entire group' set to 'No'.
- Event: User 'Teacher' has marked assignment 'B' for user 'Adrian' in course 'C101'.
- Event: User 'Teacher' has left a comment for assignment 'B' for user 'Adrian' in course 'C101'.
- Event: User 'Teacher' has uploaded a feedback file for assignment 'B' for user 'Adrian' in course 'C101'.
- Event: Email sent to user 'Adrian' notifying them their submission for assignment 'B' has been marked.
FAQs
- Why not create events in core subsystems?
- Because we could not see all core events in one place, it would create problems when naming event classes and finally subsystems are incomplete, we would have to add more now because we could not move the events in the future.
Possible future work
- MDL-45108 "Other" parameters should be defined in a method similar to webservices
- MDL-45217 Create traits for refactoring event triggers
- MDL-42897 Converting completion to use events
- MDL-42898 Develop a timeline page/block