<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://docs.moodle.org/dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Emerrill</id>
	<title>MoodleDocs - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://docs.moodle.org/dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Emerrill"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Emerrill"/>
	<updated>2026-06-14T15:58:59Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Callbacks&amp;diff=64041</id>
		<title>Callbacks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Callbacks&amp;diff=64041"/>
		<updated>2023-12-08T19:14:20Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Adding missing callbacks&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle does not have well-defined Hooks API but still allows plugins to hook into different processes by &#039;&#039;&#039;implementing callbacks&#039;&#039;&#039;. See also [[Communication Between Components]].&lt;br /&gt;
&lt;br /&gt;
Some old APIs that were created before object-oriented programming use only callbacks. One of them is [[Activity modules]].&lt;br /&gt;
==Types of callbacks in Moodle==&lt;br /&gt;
=== Callback functions one-to-many ===&lt;br /&gt;
Core has a &amp;quot;hook point&amp;quot; where it allows plugins to execute some code, modify variables or return a result. Sometimes (especially for old callbacks) only plugins of a specific plugin type are allowed, however this is not recommended for the new callbacks because the list of callbacks is now cached and performance is not a constraint any more. If plugins want to implement the function for this callback they normally must place it in &#039;&#039;&#039;lib.php&#039;&#039;&#039; with the name &#039;&#039;&#039;pluginfullname_callbackname()&#039;&#039;&#039;. Here pluginname should be a full plugin name with a prefix, however activity modules can often omit the &amp;quot;mod_&amp;quot; prefix for historical reasons. Implementing this type of callback is optional and the &amp;quot;hook point&amp;quot; does not care how many and which plugins implement callback. Also &amp;quot;hook point&amp;quot; does not check if the plugin is enabled on this site, inside the callback plugins must check it themselves if it is important. Methods &#039;&#039;&#039;get_plugin_list_with_function()&#039;&#039;&#039; and &#039;&#039;&#039;get_plugins_with_function()&#039;&#039;&#039; are normally used in the &amp;quot;hook points&amp;quot; to find all plugins implementing a callback.&lt;br /&gt;
&lt;br /&gt;
This document aims to cover ALL callbacks of this type that are present in Moodle core APIs or in standard Moodle plugins.&lt;br /&gt;
=== Callback functions one-to-one ===&lt;br /&gt;
Core API (or plugins such as blocks or reports) looks for and executes a method from the plugin/component that &amp;quot;owns&amp;quot; some entity, usually for checking permissions, pre-processing or providing the human-readable representation of this entity. Similar to one-to-many callbacks, the component/plugin must define a function with the name &#039;&#039;&#039;pluginfullname_callbackname()&#039;&#039;&#039; in lib.php . In some cases the function &#039;&#039;must&#039;&#039; be present or otherwise an exception is thrown or debugging message is displayed. Methods &#039;&#039;&#039;component_callback()&#039;&#039;&#039; and &#039;&#039;&#039;component_callback_exists()&#039;&#039;&#039; are normally used to find an implementation of a callback. Unlike one-to-many callbacks the implementing functions can exist not only in plugins but also in core components.&lt;br /&gt;
&lt;br /&gt;
The list of one-to-one callbacks may not be 100% complete in this document, the aim is to cover the &amp;quot;magic&amp;quot; functions that are actually used despite IDE hints.&lt;br /&gt;
=== Other ===&lt;br /&gt;
* &#039;&#039;&#039;Files in the specific location&#039;&#039;&#039;. Core APIs may look for plugins that have a file with a specific file name, for example &#039;&#039;&#039;version.php&#039;&#039;&#039;, &#039;&#039;&#039;settings.php&#039;&#039;&#039;, &#039;&#039;&#039;styles.css&#039;&#039;&#039;, &#039;&#039;&#039;module.js&#039;&#039;&#039;, files in &#039;&#039;&#039;db/&#039;&#039;&#039; folder, etc. See [[Plugin files]]&lt;br /&gt;
* &#039;&#039;&#039;Event observers&#039;&#039;&#039;. Each plugin can implement event observers that execute code when event is triggered. See [[Events API#Event_dispatching_and_observers|Events API]] about how to listen to events. Event observers can not always substitute callbacks because they do not return any value and can only happen in case of event. Many &amp;quot;hook points&amp;quot; are necessary before something has happened or when nothing is happening at all, for example a report/summary is gathered. However if it is possible to achieve the desired outcome with event observer, Moodle will not accept requests for adding a &amp;quot;hook point&amp;quot;.&lt;br /&gt;
* Callbacks that are specified in the db/* file, for example in &#039;&#039;&#039;db/service.php&#039;&#039;&#039; developers must specify callback responsible for implementing web service, in &#039;&#039;&#039;db/tags.php&#039;&#039;&#039; developers specify callback for finding tagged items, etc. These types of callbacks are not covered on this page, information about them can be found in the respective APIs.&lt;br /&gt;
== Callbacks and external / webservices / consumers (mobile / apps) support ==&lt;br /&gt;
=== The problem ===&lt;br /&gt;
Our external functions are able to [[Web service API functions|perform a lot of actions]] against core. Those external functions use to implement their functionality reusing [[Core APIs]]. And, those APIs can contain hook points/callbacks to customize Moodle behavior.&lt;br /&gt;
&lt;br /&gt;
Of course, ultimately, those external functions are used by the [[Web_services| web services layer]] providing all the functionality over REST/XMLRCP/SOAP &amp;quot;protocols&amp;quot;. All those &amp;quot;clients&amp;quot; consuming Moodle Webservices (integrations in general, mobile &amp;amp; desktop apps...) should work ok, and we must guarantee that all the code executed in those underlying external functions (that may include hook points/callbacks) is supported by them.&lt;br /&gt;
&lt;br /&gt;
In the other side, some of those callbacks may be focussed entirely in the web version of Moodle and have no application outside from there. Also, sometime, there are callbacks which goal is incompatible with uses from web service consumers, mainly because the consumers don&#039;t support this or that Moodle functionality used by the callback implementations.&lt;br /&gt;
&lt;br /&gt;
Imagine a callback that provides to the consumer with something that it&#039;s not able to manage (say a mustache template, or a moodle form, or any other stuff in general that only core is able to provide). It&#039;s a pretty &amp;quot;possible&amp;quot; case a we need a way to handle it in an organised way.&lt;br /&gt;
=== The solution ===&lt;br /&gt;
To be able advance with those &amp;quot;problematic&amp;quot; hooks (not being supported by web-service consumers), it has been agreed to proceed with the following points in order to allow them to work via web, avoiding them to impact to consumers and registering the need of introducing (future) viable solutions to enable consumers to work with them.&lt;br /&gt;
&lt;br /&gt;
All the following points must be considered as real requirements in order to allow a new callback (called from externals/web services) to land to core.&lt;br /&gt;
# &#039;&#039;&#039;Create a linked MOBILE issue&#039;&#039;&#039; requesting the callback to be supported in the app (for the record and future reference). Clearly explaining which are the externals / web services affected, what should be supported and, ideally, a way to provide that.&lt;br /&gt;
# &#039;&#039;&#039;Clearly indicate in the issue&#039;&#039;&#039; introducing the new callback if the hook is supported or not via WebServices. Callbacks documentation (below in this page) &#039;&#039;&#039;must include the comment&#039;&#039;&#039; &#039;&#039;&amp;quot;Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-xxxx and MOBILE-xxxx for more info.&amp;quot;&#039;&#039;&amp;quot; (where the MDL and MOBILE issues are the related ones).&lt;br /&gt;
# &#039;&#039;&#039;Add manual tests&#039;&#039;&#039; in the issue for checking that the related functionality in the mobile / desktop app is not broken and everything continues working ok.&lt;br /&gt;
# Check that the &#039;&#039;&#039;modified code or APIs do not impact on the external / WebServices&#039;&#039;&#039; API. You can just check if the modified function is used by the external API just adding a debug message and then running WebServices.&lt;br /&gt;
# If you need to add code in an API already used by the external API, you can always &#039;&#039;&#039;use the WS_SERVER constant&#039;&#039;&#039; to check if the current request comes from the WS layer to &#039;&#039;&#039;avoid its execution&#039;&#039;&#039;. With links to the corresponding MDL-xxxx and MOBILE-xxxx issues.&lt;br /&gt;
# In any case, &#039;&#039;&#039;it&#039;s the responsibility of every plugin implementing a not compatible callback to prevent any action&#039;&#039;&#039; that may affect / impact the WS layer. Core (hook points) will always perform the calls to the callbacks, unconditionally.&lt;br /&gt;
== List of Moodle callbacks ==&lt;br /&gt;
=== List of one-to-many callbacks ===&lt;br /&gt;
To implement one of these callbacks plugins must add a function &#039;&#039;&#039;pluginfullname_callbackname()&#039;&#039;&#039; in &#039;&#039;&#039;lib.php&#039;&#039;&#039; unless stated otherwise. The &#039;&#039;pluginfullname&#039;&#039; is the name of the plugin with the frankenstyle prefix. For activity modules it is acceptable to omit the &#039;&#039;mod_&#039;&#039; prefix. Refer to the linked documentation or search Moodle code by the callback name to find where it is called, what arguments are supplied and what return value is expected.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Callback&lt;br /&gt;
! Plugin type&lt;br /&gt;
! Version&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Navigation, see [[Navigation API]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| extend_navigation_course&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| before Moodle 3.0 was only supported by &#039;&#039;report&#039;&#039; and &#039;&#039;tool&#039;&#039; plugin types&lt;br /&gt;
|-&lt;br /&gt;
| extend_settings_navigation&lt;br /&gt;
| local, booktool&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;Note, modules have a one-to-one callback with the same name, see below&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| extend_navigation&lt;br /&gt;
| local&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;Note, modules have a one-to-one callback with the same name, see below&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| extend_navigation_user_settings&lt;br /&gt;
| *&lt;br /&gt;
| 3.0+&lt;br /&gt;
| before Moodle 3.0 was only supported by &#039;&#039;tool&#039;&#039; plugin types&lt;br /&gt;
|-&lt;br /&gt;
| extend_navigation_category_settings&lt;br /&gt;
| *&lt;br /&gt;
| 3.0+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| extend_navigation_frontpage&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| extend_navigation_user&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;My profile, see [[My profile API]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| myprofile_navigation&lt;br /&gt;
| *&lt;br /&gt;
| 2.9+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Before-actions hooks&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| course_module_background_deletion_recommended&lt;br /&gt;
| *&lt;br /&gt;
| 3.2+&lt;br /&gt;
| see [https://github.com/moodle/moodle/blob/MOODLE_32_STABLE/lib/upgrade.txt#L142 upgrade.txt]&lt;br /&gt;
|-&lt;br /&gt;
| pre_block_delete&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| pre_course_category_delete&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|pre_course_category_delete_move&lt;br /&gt;
|*&lt;br /&gt;
|3.9+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| pre_course_delete&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| pre_course_module_delete&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| pre_user_delete&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;[[Login_callbacks]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#after_config | after_config]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+&lt;br /&gt;
| Triggered as soon as practical on every moodle bootstrap after config has been loaded. The $USER object is available at this point too.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#after_require_login | after_require_login]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.7+&lt;br /&gt;
| eg Add permissions logic across a site or course&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#check_password_policy | check_password_policy]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.6+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#print_password_policy | print_password_policy]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#pre_signup_requests | pre_signup_requests]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.5&lt;br /&gt;
| eg to do actions before sign up such as acceptance of policies, validations, etc&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#extend_change_password_form | extend_change_password_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Injecting form elements into the change password form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#extend_set_password_form | extend_set_password_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Injecting form elements into the set password form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#extend_forgot_password_form | extend_forgot_password_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Injecting form elements into the forgot password form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#extend_signup_form | extend_signup_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Injecting form elements into the signup form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#validate_extend_change_password_form | validate_extend_change_password_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Adding additional validation to the change password form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#validate_extend_set_password_form | validate_extend_set_password_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Adding additional validation to the set password form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#validate_extend_forgot_password_form | validate_extend_forgot_password_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Adding additional validation to the forgot password form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#validate_extend_signup_form | validate_extend_signup_form]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Adding additional validation to the signup form. Note: This hook is not compatible with the Webservices API or the Moodle mobile app. See MDL-66173 and MOBILE-3181 for more info.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#post_change_password_requests | post_change_password_requests]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Fire additional actions after a user changes password.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#post_set_password_requests | post_set_password_requests]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Fire additional actions after a user resets password.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#post_forgot_password_requests | post_forgot_password_requests]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Fire additional actions after a user submits a password reset request.&lt;br /&gt;
|-&lt;br /&gt;
| [[Login_callbacks#post_signup_requests | post_signup_requests]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.8+ (WIP)&lt;br /&gt;
| eg Fire additional actions after a user creates an account.&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Page rendering, see [[Output_callbacks]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| [[Output_callbacks#add_htmlattributes | add_htmlattributes]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| eg Add open graph xml namespace attributes&lt;br /&gt;
|-&lt;br /&gt;
| [[Output_callbacks#before_footer | before_footer]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| eg injecting JS across the site, like analytics&lt;br /&gt;
|-&lt;br /&gt;
| [[Output_callbacks#before_http_headers | before_http_headers]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| Setting http headers across the site like CSP&lt;br /&gt;
|-&lt;br /&gt;
| [[Output_callbacks#before_standard_html_head | before_standard_html_head]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| A better API alternative to appending to $CFG-&amp;gt;additionalhtmlhead&lt;br /&gt;
|-&lt;br /&gt;
| [[Output_callbacks#before_standard_top_of_body_html | before_standard_top_of_body_html]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| [[Output_callbacks#render_navbar_output | render_navbar_output]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.2+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Course module edit form&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| coursemodule_edit_post_actions&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| coursemodule_standard_elements&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| Takes parameters $formwrapper and $mform, allows manipulation of editing form, e.g. additional fields. See https://github.com/marcusgreen/moodle-local_callbacks&lt;br /&gt;
|-&lt;br /&gt;
|coursemodule_definition_after_data&lt;br /&gt;
|*&lt;br /&gt;
|3.11+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| coursemodule_validation&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Modules&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| check_updates_since&lt;br /&gt;
| mod&lt;br /&gt;
| 3.2+&lt;br /&gt;
| See [[NEWMODULE_Documentation]]&lt;br /&gt;
|-&lt;br /&gt;
| dndupload_register&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Admin&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| bulk_user_actions&lt;br /&gt;
| *&lt;br /&gt;
| 3.9+&lt;br /&gt;
| Any plugin typically an admin tool can add new bulk user actions&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Other&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| get_fontawesome_icon_map&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| https://docs.moodle.org/dev/Moodle_icons#Font_awesome_icons&lt;br /&gt;
|-&lt;br /&gt;
| get_question_bank_search_conditions&lt;br /&gt;
| local&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| oauth2_system_scopes&lt;br /&gt;
| *&lt;br /&gt;
| 3.3+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| rss_get_feed&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| supports_logstore&lt;br /&gt;
| report&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_shortcuts&lt;br /&gt;
| ltisource&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| before_launch&lt;br /&gt;
| ltisource&lt;br /&gt;
| 2.8+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| [[Miscellaneous_callbacks#control_view_profile | control_view_profile]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.6+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| [[Miscellaneous_callbacks#store_profiling_data | store_profiling_data ]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.6+&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| [[Miscellaneous_callbacks#override_webservice_execution | override_webservice_execution]]&lt;br /&gt;
| *&lt;br /&gt;
| 3.6+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|can_course_category_delete&lt;br /&gt;
|*&lt;br /&gt;
|3.9+&lt;br /&gt;
|Indicate if an empty course category can be deleted&lt;br /&gt;
|-&lt;br /&gt;
|can_course_category_delete_move&lt;br /&gt;
|*&lt;br /&gt;
|3.9+&lt;br /&gt;
|Indicate if a course category with content in it can have it&#039;s content moved to a selected new category, so it can be deleted.&lt;br /&gt;
|-&lt;br /&gt;
|get_course_category_contents&lt;br /&gt;
|*&lt;br /&gt;
|3.9+&lt;br /&gt;
|Provides content information to be displayed on the course category delete/move content page&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Deprecated&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| cron&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| See [[Task API]]&lt;br /&gt;
|-&lt;br /&gt;
| delete_course&lt;br /&gt;
| mod,report,coursereport,format&lt;br /&gt;
| &lt;br /&gt;
| Replace with observer to course_contents_deleted event&lt;br /&gt;
|-&lt;br /&gt;
| print_overview&lt;br /&gt;
| mod&lt;br /&gt;
| up to 3.2&lt;br /&gt;
| New dashboard uses [[Calendar API]] to populate events on the timeline&lt;br /&gt;
|-&lt;br /&gt;
| report_extend_navigation&lt;br /&gt;
| coursereport&lt;br /&gt;
| &lt;br /&gt;
| Plugin type &#039;&#039;coursereport&#039;&#039; is deprecated, plugin type &#039;&#039;report&#039;&#039; should be used instead&lt;br /&gt;
|}&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
if Moodle version is not specified, this means that callback existed for a long time and can be found in all [[Releases|currently supported]] Moodle versions&lt;br /&gt;
=== List of one-to-one callbacks ===&lt;br /&gt;
To implement one of these callbacks plugins must add a function &#039;&#039;&#039;pluginfullname_callbackname()&#039;&#039;&#039; in &#039;&#039;&#039;lib.php&#039;&#039;&#039; unless stated otherwise. The &#039;&#039;pluginfullname&#039;&#039; is the name of the plugin with the frankenstyle prefix. For activity modules it is acceptable to omit the &#039;&#039;mod_&#039;&#039; prefix. Refer to the linked documentation or search Moodle code by the callback name to find where it is called, what arguments are supplied and what return value is expected.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Callback&lt;br /&gt;
! Plugin type&lt;br /&gt;
! Version&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Comments support&#039;&#039;&#039;, see [[Comment API]]&lt;br /&gt;
|-&lt;br /&gt;
| comment_add&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| comment_display&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| comment_permissions&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| comment_template&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| comment_url&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| comment_validate&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Gradebook&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| export_%_settings_definition&lt;br /&gt;
| gradeexport&lt;br /&gt;
| &lt;br /&gt;
| % is a plugin name &#039;&#039;without&#039;&#039; prefix&lt;br /&gt;
|-&lt;br /&gt;
| import_%_settings_definition&lt;br /&gt;
| gradeimport&lt;br /&gt;
| &lt;br /&gt;
| % is a plugin name &#039;&#039;without&#039;&#039; prefix&lt;br /&gt;
|-&lt;br /&gt;
| report_%_profilereport&lt;br /&gt;
| gradereport&lt;br /&gt;
| &lt;br /&gt;
| % is a plugin name &#039;&#039;without&#039;&#039; prefix&lt;br /&gt;
|-&lt;br /&gt;
| report_%_settings_definition&lt;br /&gt;
| gradereport&lt;br /&gt;
| &lt;br /&gt;
| % is a plugin name &#039;&#039;without&#039;&#039; prefix&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Groups support&#039;&#039;&#039;, see [[Groups API]]&lt;br /&gt;
|-&lt;br /&gt;
| allow_group_member_remove&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| restore_group_member&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| For plugins that create their own groups&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Installation and upgrade&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| xmldb_%_install&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Located in db/install.php , % refer to the full plugin name, in case of modules without prefix&lt;br /&gt;
|-&lt;br /&gt;
| xmldb_%_install_recovery&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Located in db/install.php , % refer to the full plugin name, in case of modules without prefix&lt;br /&gt;
|-&lt;br /&gt;
| xmldb_%_uninstall&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Located in db/uninstall.php , % refer to the full plugin name, in case of modules without prefix&lt;br /&gt;
|-&lt;br /&gt;
| xmldb_%_upgrade&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Located in db/upgrade.php , % refer to the full plugin name, in case of modules without prefix&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;LTI source&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| add_instance_hook&lt;br /&gt;
| ltisource&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;messagetype&#039;&#039;&lt;br /&gt;
| ltisource&lt;br /&gt;
| &lt;br /&gt;
| Callback name is the type of the message&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Question bank support&#039;&#039;&#039;, see [[Question API]]&lt;br /&gt;
|-&lt;br /&gt;
| question_pluginfile&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| question_preview_pluginfile&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| questions_in_use&lt;br /&gt;
| *&lt;br /&gt;
| since 3.7.5, 3.8.2 (previously only &amp;quot;mod&amp;quot;)&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Ratings support&#039;&#039;&#039;, see [[Rating API]]&lt;br /&gt;
|-&lt;br /&gt;
| rating_can_see_item_ratings&lt;br /&gt;
| *&lt;br /&gt;
| 2.9.2+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| rating_can_see_item_ratings&lt;br /&gt;
| *&lt;br /&gt;
| 2.9.2+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| rating_get_item_fields&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| rating_permissions&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| rating_validate&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Themes&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| $THEME-&amp;gt;csspostprocess&lt;br /&gt;
| theme&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| $THEME-&amp;gt;extralesscallback&lt;br /&gt;
| theme&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| $THEME-&amp;gt;lessvariablescallback&lt;br /&gt;
| theme&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| page_init&lt;br /&gt;
| theme&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Modules: Calendar and dashboard&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| core_calendar_event_action_shows_item_count&lt;br /&gt;
| mod&lt;br /&gt;
| 3.3+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| core_calendar_is_event_visible&lt;br /&gt;
| mod&lt;br /&gt;
| 3.3+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| core_calendar_provide_event_action&lt;br /&gt;
| mod&lt;br /&gt;
| 3.3+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Modules: Course cache&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| cm_info_dynamic&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| cm_info_view&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_coursemodule_info&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Modules: Course reset&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| reset_course_form_defaults&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| reset_course_form_definition&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| reset_userdata&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Modules&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| delete_instance&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| dndupload_handle&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| export_contents&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| Used in WS get_course_contents&lt;br /&gt;
|-&lt;br /&gt;
| extend_settings_navigation&lt;br /&gt;
| mod,booktool&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| extends_navigation&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_completion_active_rule_descriptions&lt;br /&gt;
| mod&lt;br /&gt;
| 3.3+&lt;br /&gt;
| Must be present for modules implementing custom completion rules&lt;br /&gt;
|-&lt;br /&gt;
| get_completion_state&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_extra_capabilities&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_file_areas&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_recent_mod_activity&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| get_shortcuts&lt;br /&gt;
| mod&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| grade_item_update&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| grading_areas_list&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| print_recent_activity&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| print_recent_mod_activity&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| refresh_events&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| rescale_activity_grades&lt;br /&gt;
| mod&lt;br /&gt;
| 3.1+&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| scale_used_anywhere&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| update_grades&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| user_complete&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| user_outline&lt;br /&gt;
| mod&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Glossary formats&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| glossary_print_entry_%&lt;br /&gt;
| for glossary formats&lt;br /&gt;
| &lt;br /&gt;
| Glossary formats is not a plugin type yet, however the list of formats is not hardcoded in the mod_glossary, instead the methods similar to callbacks are used for each subfolder in mod/glossar/format. % refer to the subfolder name, the functions are expected in lib.php&lt;br /&gt;
|-&lt;br /&gt;
| glossary_show_entry_%&lt;br /&gt;
| for glossary formats&lt;br /&gt;
| &lt;br /&gt;
|  - &amp;quot; -&lt;br /&gt;
|-&lt;br /&gt;
| glossary_show_entry_%&lt;br /&gt;
| for glossary formats&lt;br /&gt;
| &lt;br /&gt;
| - &amp;quot; -&lt;br /&gt;
|-&lt;br /&gt;
| glossary_show_entry_%&lt;br /&gt;
| for glossary formats&lt;br /&gt;
| &lt;br /&gt;
| - &amp;quot; -&lt;br /&gt;
|-&lt;br /&gt;
| colspan=4 align=center | &#039;&#039;&#039;Other&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| global_db_replace&lt;br /&gt;
| block&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| inplace_editable&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| See [[Inplace editable]]&lt;br /&gt;
|-&lt;br /&gt;
| output_fragment_*&lt;br /&gt;
| *&lt;br /&gt;
| 3.1+&lt;br /&gt;
| see [[Fragment]]&lt;br /&gt;
|-&lt;br /&gt;
| page_type_list&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Used when displaying page types in block configuration&lt;br /&gt;
|-&lt;br /&gt;
| params_for_js&lt;br /&gt;
| atto&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| pluginfile&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Callback checking permissions and preparing the file for serving plugin files, see [[File API]]. Note, in case of &#039;&#039;block&#039;&#039; plugins the list of arguments is slightly different&lt;br /&gt;
|-&lt;br /&gt;
| restore_role_assignment&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| For plugins that create their own role assignments&lt;br /&gt;
|-&lt;br /&gt;
| strings_for_js&lt;br /&gt;
| atto&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| supports&lt;br /&gt;
| *&lt;br /&gt;
| &lt;br /&gt;
| Required for activity modules&lt;br /&gt;
|-&lt;br /&gt;
|h5p\canedit::can_edit_content&lt;br /&gt;
|*&lt;br /&gt;
|4.0+&lt;br /&gt;
|Plugins can implement this class&amp;amp;method to define, if required, any custom behaviour for deciding whether an H5P content used inside the plugin can be edited or not&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; align=&amp;quot;center&amp;quot; |&#039;&#039;&#039;Deprecated&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ajax_section_move&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| ajax_support&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| delete_course&lt;br /&gt;
| mod&lt;br /&gt;
| up to 3.1&lt;br /&gt;
| use event observer&lt;br /&gt;
|-&lt;br /&gt;
| display_content&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| get_post_actions&lt;br /&gt;
| mod,booktool&lt;br /&gt;
| &lt;br /&gt;
| Only called if legacy log is used on the site&lt;br /&gt;
|-&lt;br /&gt;
| get_section_name&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| get_section_url&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| get_types&lt;br /&gt;
| mod,ltisource&lt;br /&gt;
| up to 3.0&lt;br /&gt;
| Replaced with &#039;&#039;get_shortcuts&#039;&#039; in 3.1&lt;br /&gt;
|-&lt;br /&gt;
| get_view_actions&lt;br /&gt;
| mod,booktool&lt;br /&gt;
| &lt;br /&gt;
| Only called if legacy log is used on the site&lt;br /&gt;
|-&lt;br /&gt;
| load_content&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| scale_used&lt;br /&gt;
| mod&lt;br /&gt;
| up to 3.0&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| uses_sections&lt;br /&gt;
| format&lt;br /&gt;
| up to 2.3&lt;br /&gt;
| See [[Course formats]]&lt;br /&gt;
|-&lt;br /&gt;
| question_list_instances&lt;br /&gt;
| mod&lt;br /&gt;
| up to 3.8&lt;br /&gt;
| Use &amp;quot;questions_in_use&amp;quot; instead&lt;br /&gt;
|}&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
if Moodle version is not specified, this means that callback existed for a long time and can be found in all [[Releases|currently supported]] Moodle versions&lt;br /&gt;
== Adding new callbacks to core ==&lt;br /&gt;
If you are preparing a patch for core which adds a new callback here are some things which should be considered.&lt;br /&gt;
&lt;br /&gt;
TBA see https://tracker.moodle.org/browse/MDL-60510&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=63301</id>
		<title>Cache API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=63301"/>
		<updated>2022-06-11T03:42:30Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: MDL-42012 has been resolved in 3.8.6 and 3.9.3.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This document details the Cache API aka MUC aka Moodle&#039;s Universal Cache.&lt;br /&gt;
I&#039;ve chosen to use a tutorial/example flow for this document always working with a theoretical module plugin called myplugin.&lt;br /&gt;
There is also a [[Cache API - Quick reference]] if you would rather read that.&lt;br /&gt;
==Basic usage==&lt;br /&gt;
It&#039;s very easy to get started with the Cache API. It is designed to be as easy and as quick to use as possible and is predominantely self contained.&lt;br /&gt;
All you need to do is add a definition for your cache and you are ready to start working with the Cache API.&lt;br /&gt;
===Creating a definition===&lt;br /&gt;
Cache definitions exist within the db/caches.php file for a component/plugin.&amp;lt;br /&amp;gt;&lt;br /&gt;
In the case of core that is the moodle/lib/db/caches.php file, in the case of a module that would be moodle/mod/myplugin/db/caches.php.&lt;br /&gt;
&lt;br /&gt;
The definition is used API in order to understand a little about the cache and what it is being used for, it also allows the administrator to set things up especially for the definition if they want.&lt;br /&gt;
From a development point of view the definition allows you to tell the API about your cache, what it requires, and any (if any) advanced features you want it to have.&amp;lt;br /&amp;gt;&lt;br /&gt;
The following shows a basic definition containing just the bare minimum:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// moodle/mod/myplugin/db/caches.php&lt;br /&gt;
$definitions = [&lt;br /&gt;
    &#039;somedata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;mode&#039; =&amp;gt; cache_store::MODE_APPLICATION,&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This informs the API that the myplugin module has a cache called &#039;&#039;somedata&#039;&#039; and that it is an application (globally shared) cache.&lt;br /&gt;
&lt;br /&gt;
When creating a definition thats the bare minimum, to provide an area &#039;&#039;(somedata)&#039;&#039; and declare the type of the cache application, session, or request.&lt;br /&gt;
* An application cache is a shared cache, all users can access it.&lt;br /&gt;
* Session caches are scoped to a single users session, but may not actually be stored in the session.&lt;br /&gt;
* Request caches you can think of as static caches, only available to the user owning the request, and only alive until the end of the request.&lt;br /&gt;
There are of course many more options available that allow you to really take the cache by the reigns, you can read about some of the important ones further on, or skip ahead to [[#The definition]] section which details the available options in full.&lt;br /&gt;
&lt;br /&gt;
Please note that for each definition a language string with the name &#039;&#039;cachedef_&#039;&#039; followed by the name of the definition is expected. Using the example above you would have to define:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// moodle/mod/myplugin/lang/en/mod_myplugin.php&lt;br /&gt;
$string[&#039;cachedef_somedata&#039;] = &#039;This is the description of the cache somedata&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Getting a cache object===&lt;br /&gt;
Once your definition has been created you should bump the version number so that Moodle upgrades and processes the definitions file at which point your definition will be useable.&lt;br /&gt;
&lt;br /&gt;
Now within code you can get a cache object corresponding to the definition created earlier.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$cache = cache::make(&#039;mod_myplugin&#039;, &#039;somedata&#039;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The cache::make method is a factory method, it will create a cache object to allow you to work with your cache. The cache object will be one of several classes chosen by the API based upon what your definition contains. All of these classes will extend the base cache class, and in nearly all cases you will get one of cache_application, cache_session, or cache_request depending upon the mode you selected.&lt;br /&gt;
===Using your cache object===&lt;br /&gt;
Once you have a cache object (will extend the cache class and implements cache_loader) you are ready to start interacting with the cache.&lt;br /&gt;
&lt;br /&gt;
Of course there are three basic basic operations. get, set, and delete.&lt;br /&gt;
&lt;br /&gt;
The first is to send something to the cache.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$result = $cache-&amp;gt;set(&#039;key&#039;, &#039;value&#039;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Easy enough. The key must be an int or a string. The value can be absolutely anything your want that is [https://www.php.net/manual/en/function.serialize.php serializable].&lt;br /&gt;
The result is true if the operation was a success, false otherwise.&lt;br /&gt;
&lt;br /&gt;
The second is to fetch something from the cache.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$data = $cache-&amp;gt;get(&#039;key&#039;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
$data will either be whatever was being stored in the cache, or false if the cache could not find the key.&lt;br /&gt;
&lt;br /&gt;
The third and final operation is delete.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$result = $cache-&amp;gt;delete(&#039;key&#039;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Again just like set the result will either be true if the operation was a success, or false otherwise.&lt;br /&gt;
&lt;br /&gt;
You can also set, get, and delete multiple key=&amp;gt;value pairs in a single transaction.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$result = $cache-&amp;gt;set_many([&lt;br /&gt;
    &#039;key1&#039; =&amp;gt; &#039;data1&#039;,&lt;br /&gt;
    &#039;key3&#039; =&amp;gt; &#039;data3&#039;&lt;br /&gt;
]);&lt;br /&gt;
// $result will be the number of pairs sucessfully set.&lt;br /&gt;
&lt;br /&gt;
$result = $cache-&amp;gt;get_many([&#039;key1&#039;, &#039;key2&#039;, &#039;key3&#039;]);&lt;br /&gt;
print_r($result);&lt;br /&gt;
// Will print the following:&lt;br /&gt;
// array(&lt;br /&gt;
//     &#039;key1&#039; =&amp;gt; &#039;data1&#039;,&lt;br /&gt;
//     &#039;key2&#039; =&amp;gt; false,&lt;br /&gt;
//     &#039;key3&#039; =&amp;gt; &#039;data3&#039;&lt;br /&gt;
// )&lt;br /&gt;
&lt;br /&gt;
$result = $cache-&amp;gt;delete_many([&#039;key1&#039;, &#039;key3&#039;]);&lt;br /&gt;
// $result will be the number of records sucessfully deleted.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
That covers the basic operation of the Cache API.&amp;lt;br /&amp;gt;&lt;br /&gt;
In many situations there is not going to be any more to it than that.&lt;br /&gt;
==Ad-hoc Caches==&lt;br /&gt;
This is the alternative method of using the cache API.&amp;lt;br /&amp;gt;&lt;br /&gt;
It involves creating a cache using just the required params at the time that it is required. It doesn&#039;t require that a definition exists making it quicker and easier to use, however it can only use the default settings and is only recommended for insignificant caches (rarely used during operation, never to be mapped or customised, only existsing in a single place in code).&lt;br /&gt;
&lt;br /&gt;
Once a cache object has been retrieved it operates exactly as the same as a cache that has been created for a definition.&lt;br /&gt;
&lt;br /&gt;
To create an ad-hoc cache you would use the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, &#039;mod_myplugin&#039;, &#039;mycache&#039;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Really don&#039;t be lazy, if you don&#039;t have a good reason to use an ad-hoc cache you should be spending a extra 5 minutes creating a definition.&lt;br /&gt;
==The definition==&lt;br /&gt;
The above section illustrated how to create a basic definition, specifying just the area name (the key) and the mode for the definition. Those being the two required properties for a definition.&amp;lt;br /&amp;gt;&lt;br /&gt;
There are many other options that will let you make the most of the Cache API and will undoubtedly be required when implementing and converting cache solutions to the Cache API.&lt;br /&gt;
&lt;br /&gt;
The following details the options available to a definition and their defaults if not applied:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$definitions = [&lt;br /&gt;
    // The name of the cache area is the key. The component/plugin will be picked up from the file location.&lt;br /&gt;
    &#039;area&#039; =&amp;gt; [&lt;br /&gt;
        &#039;mode&#039; =&amp;gt; cache_store::MODE_*,&lt;br /&gt;
        &#039;simplekeys&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;simpledata&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requireidentifiers&#039; =&amp;gt; [&#039;ident1&#039;, &#039;ident2&#039;],&lt;br /&gt;
        &#039;requiredataguarantee&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requiremultipleidentifiers&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requirelockingread&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requirelockingwrite&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;maxsize&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;overrideclass&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;overrideclassfile&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;datasource&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;datasourcefile&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;staticacceleration&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;staticaccelerationsize&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;ttl&#039; =&amp;gt; 0,&lt;br /&gt;
        &#039;mappingsonly&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;invalidationevents&#039; =&amp;gt; [&#039;event1&#039;, &#039;event2&#039;],&lt;br /&gt;
        &#039;canuselocalstore&#039; =&amp;gt; false&lt;br /&gt;
        &#039;sharingoptions&#039; =&amp;gt; cache_definition::SHARING_DEFAULT,&lt;br /&gt;
        &#039;defaultsharing&#039; =&amp;gt; cache_definition::SHARING_DEFAULT,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Setting requirements===&lt;br /&gt;
The definition can specify several requirements for the cache.&amp;lt;br /&amp;gt;&lt;br /&gt;
This includes identifiers that must be provided when creating the cache object, that the store guarantees data stored in it will remain there until removed, a store that supports multiple identifiers, and finally read/write locking.&lt;br /&gt;
The options for these are as follows:&lt;br /&gt;
; simplekeys : [bool] Set to true if your cache will only use simple keys for its items.&amp;lt;br /&amp;gt;Simple keys consist of digits, underscores and the 26 chars of the english language. a-zA-Z0-9_&amp;lt;br /&amp;gt;If true the keys won&#039;t be hashed before being passed to the cache store for gets/sets/deletes. It will be better for performance and possible only because we know the keys are safe.&lt;br /&gt;
; simpledata : [bool] If set to true we know that the data is scalar or array of scalar.&amp;lt;br /&amp;gt;If true, the data values will be stored as they are. Otherwise they will be serialised first.&lt;br /&gt;
; requireidentifiers : [array] An array of identifiers that must be provided to the cache when it is created.&lt;br /&gt;
; requiredataguarantee : [bool] If set to true then only stores that can guarantee data will remain available once set will be used.&lt;br /&gt;
; requiremultipleidentifiers : [bool] If set to true then only stores that support multiple identifiers will be used.&lt;br /&gt;
; requirelockingread : [bool] If set to true then a lock will be gained before reading from the cache store. It is recommended not to use this setting unless 100% absolutely positively required.&amp;lt;br /&amp;gt;Remember 99.9% of caches will NOT need this setting.&amp;lt;br /&amp;gt;This setting will only be used for application caches presently.&lt;br /&gt;
; requirelockingwrite : [bool] If set to true then a lock will be gained before writing to the cache store. As above this is not recommended unless truly needed. Please think about the order of your code and deal with race conditions there first.&amp;lt;br /&amp;gt;This setting will only be used for application caches presently.&lt;br /&gt;
===Cache modifiers===&lt;br /&gt;
You are also to modify the way in which the cache is going to operate when working for your definition.&amp;lt;br /&amp;gt;&lt;br /&gt;
By enabling the static option the Cache API will only ever generate a single cache object for your definition on the first request for it, further requests will be returned the original instance.&amp;lt;br /&amp;gt;This greatly speeds up the collecting of a cache object.&amp;lt;br /&amp;gt;&lt;br /&gt;
Enabling persistence also enables a static store within the cache object, anything set to the cache, or retrieved from it will be stored in that static array for the life of the request.&lt;br /&gt;
This makes the persistence options some of the most powerful. If you know you are going to be using you cache over and over again or if you know you will be making lots of requests for the same items then this will provide a great performance boost.&lt;br /&gt;
Of course the static storage of cache objects and of data is costly in terms of memory and should only be used when actually required, as such it is turned off by default.&lt;br /&gt;
As well as persistence you can also set a maximum number of items that the cache should store (not a hard limit, its up to each store) and a time to live (ttl) although both are discouraged as efficient design negates the need for both in most situations.&lt;br /&gt;
; staticacceleration : [bool] This setting does two important things. First it tells the cache API to only instantiate the cache structure for this definition once, further requests will be given the original instance.&amp;lt;br /&amp;gt;Second the cache loader will keep an array of the items set and retrieved to the cache during the request.&amp;lt;br /&amp;gt;This has several advantages including better performance without needing to start passing the cache instance between function calls, the downside is that the cache instance + the items used stay within memory.&amp;lt;br /&amp;gt;Consider using this setting when you know that there are going to be many calls to the cache for the same information or when you are converting existing code to the cache and need to access the cache within functions but don&#039;t want to add it as an argument to the function.&lt;br /&gt;
; staticaccelerationsize : [int] This supplements the above setting by limiting the number of items in the caches persistent array of items.&amp;lt;br /&amp;gt;Tweaking this setting lower will allow you to minimise the memory implications above while hopefully still managing to offset calls to the cache store.&lt;br /&gt;
; ttl : [int] A time to live for the data (in seconds). It is strongly recommended that you don&#039;t make use of this and instead try to create an event driven invalidation system (even if the event is just time expiring, better not to rely on ttl).&amp;lt;br /&amp;gt;Not all cache stores will support this natively and there are undesired performance impacts if the cache store does not.&lt;br /&gt;
; maxsize : [int] If set this will be used as the maximum number of entries within the cache store for this definition.&amp;lt;br /&amp;gt;Its important to note that cache stores don&#039;t actually have to acknowledge this setting or maintain it as a hard limit.&lt;br /&gt;
; canuselocalstore : [bool] This setting specifies whether the cache can safely be local to each frontend in a cluster which can avoid latency costs to a shared central cache server. The cache needs to be carefully written for this to be safe. It is conceptually similar to using $CFG-&amp;gt;localcachedir (can be local) vs $CFG-&amp;gt;cachedir (must be shared). Look at purify_html() in lib/weblib.php for an example.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Overriding a cache loader===&lt;br /&gt;
This is a super advanced feature and should not be done. ever. Unless you have an very good reason to do so.&lt;br /&gt;
It allows you to create your own cache loader and have it be used instead of the default cache loader class. The cache object you get back from the make operations will be an instance of this class.&lt;br /&gt;
; overrideclass : [string] A class to use as the loader for this cache. This is an advanced setting and will allow the developer of the definition to take 100% control of the caching solution.&amp;lt;br /&amp;gt;Any class used here must inherit the cache_loader interface and must extend default cache loader for the mode they are using.&lt;br /&gt;
; overrideclassfile : [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.&lt;br /&gt;
===Specifying a data source===&lt;br /&gt;
This is a great wee feature, especially if your code is object orientated.&lt;br /&gt;
&lt;br /&gt;
It allows you to specify a class that must inherit the cache_data_source object and will be used to load any information requested from the cache that is not already being stored.&lt;br /&gt;
&lt;br /&gt;
When the requested key cannot be found in the cache the data source will be asked to load it. The data source will then return the information to the cache, the cache will store it, and it will then return it to the user as a request of their get request. Essentially no get request should ever fail if you have a data source specified.&lt;br /&gt;
; datasource : [string] A class to use as the data loader for this definition.&amp;lt;br /&amp;gt;Any class used here must inherit the cache_data_source interface.&lt;br /&gt;
; datasourcefile : [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.&lt;br /&gt;
GOTCHA: Note that, in Moodle versions prior to 3.8.6 and 3.9.3, if caching is disabled then *nothing* will be loaded through the data source which is probably not what you expect (rather than the data source being loaded every time but never cached). See also:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-42012&lt;br /&gt;
===Misc settings===&lt;br /&gt;
The following are stand along settings that don&#039;t fall into any of the above categories.&lt;br /&gt;
; invalidationevents :[array] An array of events that should cause this cache to invalidate some or all of the items within it. Note that these are NOT normal moodle events and predates the [[Events API]] . Instead these are arbitrary strings which can be used by cache_helper::purge_by_event(&#039;changesincoursecat&#039;); to mark multiple caches as invalid at once without the calling code knowing which caches are affected.&lt;br /&gt;
; mappingsonly : [bool] If set to true only the mapped cache store(s) will be used and the default mode store will not. This is a super advanced setting and should not be used unless absolutely required. It allows you to avoid the default stores for one reason or another.&lt;br /&gt;
; sharingoptions : [int] The sharing options that are appropriate for this definition. Should be the sum of the possible options.&lt;br /&gt;
; defaultsharing : [int] The default sharing option to use. It&#039;s highly recommended that you don&#039;t set this unless there is a very specific reason not to use the system default.&lt;br /&gt;
==The loader==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Localized stores for distributed high performance caching ==&lt;br /&gt;
Most cache definitions are simple in that the code expects to be able to purge the cache, or update it, and for this to be universally available from then on to all code which loads from this cache again. But as you scale up having a single mega shared cache store doesn&#039;t work well for a variety of reasons, including extra latency between multiple front ends and the shared cache service, the number of connections the cache server can handle, the cost of IO between services, and depending on the cache definition issues with locking while writing.&lt;br /&gt;
&lt;br /&gt;
So if you want very high performance caching then you need to write you code so that it can support being distributed, or localized, which means that each front end can have it&#039;s own independent cache store. But this architecture means that you have no direct way to communicate from code running in one place to invalidate the caches on all the other front ends. In order to achieve this you need to carefully construct cache keys so that if the content changes then it uses a new cache key, which will of course be a cache miss and then it will regenerate using fresh data. There are multiple ways to achieve this, a couple common example strategies are below.&lt;br /&gt;
=== When should you localize? ===&lt;br /&gt;
Not all caches are good candidates to localize and some can have a detrimental effect if localized for the wrong reasons.&lt;br /&gt;
[[File:When to localize cache.png|alt=When to localize a cache|none|thumb|683x683px|When to localize a cache]]&lt;br /&gt;
=== Revision numbers as key suffix ===&lt;br /&gt;
A simple method is storing a version number somewhere and appending that to your key. This is the most common method used in Moodle, simply store a number somewhere which is globally shared such as in a custom db field, or using set_config / get_config.&lt;br /&gt;
&lt;br /&gt;
One small edge case with this approach is you now need to make sure that the incrementing code is atomic, which means you should use a DB transaction, or gain a lock, before bumping the version so you don&#039;t get two conflicting changes ending up on the same version with a race condition. However if you are anticipating high turnover rate of the cache you probably have a deeper issue, see &#039;Fast churning keys&#039; below.&lt;br /&gt;
&lt;br /&gt;
One potential benefit of a simple version number strategy if your cache misses are very expensive, is that you can check for the presence of a version, and if it doesn&#039;t exist it is easy to simply retrieve the previous version and use it in the mean time, while you could generate the new version asynchronously. This is an advanced concept and depends on the use case and has the obvious disadvantage of showing stale data.&lt;br /&gt;
&lt;br /&gt;
An example of this strategy in Moodle is the theme versions cache (NOTE: this is not actually in MUC but the caching concepts are the same):&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/lib/outputlib.php#L88-L100&lt;br /&gt;
&lt;br /&gt;
It works best with a cache store that supports Least Recently Used garbage collection.&lt;br /&gt;
=== Revision numbers as value suffix instead of key suffix ===&lt;br /&gt;
This is conceptually the same as above but has two important differences. Firstly because the key itself is always the same then only a single version of some value will be stored on disk or in memory for any given cache store which makes it much more efficient in terms of storage. But secondly this comes with a higher coding complexity cost because it will no longer be guaranteed to be correct because a local cache could return a hit with a stale version. So if you need it to be correct you will need to parse out the revision from the value and then confirm the revision is correct before using the value. If it is invalid then you need to treat it as a miss and handle that. One way is to rebuild and set it, but this loses the advantage of primary and final caches (see below). A better way is to delete the local cache but not the final cache by passing a second param of false to delete, and then getting the value again which will repopulate the local cache from the shared cache:&lt;br /&gt;
 $cache-&amp;gt;delete(&#039;foo&#039;, false); // Delete the primary / local stale version&lt;br /&gt;
 $cache-&amp;gt;get(&#039;foo&#039;);           // Get the final / shared version (which may also be stale!)&lt;br /&gt;
But this also is imperfect if there are 3 layers of caching, see [https://tracker.moodle.org/browse/MDL-72837 MDL-72837] for a full discussion and a possible new api to handle this.&lt;br /&gt;
&lt;br /&gt;
This method is how the course modinfo cache is localized.&lt;br /&gt;
=== Using time as a key suffix ===&lt;br /&gt;
Another common approach is to use a timestamp, this is how some of the core cache numbers work, see increment_revision_number() for some examples. This has the benefit of not needing any transaction or locking, but you do run the risk of two processes clashing if they happen to run in the same second.&lt;br /&gt;
&lt;br /&gt;
It may look like Moodle theme-related caching uses this strategy, but actually if you look at [https://github.com/moodle/moodle/blob/df0e58adb140f90712bcd3229ad936d3b4bc15d9/lib/outputlib.php#L88 the code for theme_get_next_revision], you will see that it is actually guaranteeing to generate a unique new integer which mitigates the clashing mentioned above. It is just making that integer close to current time-stamp, to make it more self-documenting.&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/lib/datalib.php#L1131-L1145&lt;br /&gt;
&lt;br /&gt;
It works best with a cache store that supports Least Recently Used garbage collection.&lt;br /&gt;
=== Content hashes as keys ===&lt;br /&gt;
A great strategy is to use a digest or hash such as sha1 / sha256 of the content stored inside the cache, or in even more advanced scenarios a hash of the dependencies of the content in the cache. This guarantees that the key will be unique, and can be truly distributed without any synchronous communication between the front ends. &lt;br /&gt;
&lt;br /&gt;
This strategy can work very well when building up large cache items from many smaller cache fragments in a nested tree structure, eg when caching a structure which is built of other cache items, eg 10 chunks of html to get combined into a large chunk to be cached, you would append the 10 hashes of the 10 smaller chunks and then hash that to use as the key for the combined cache item. This means you can mutate a small part of a tree structure and quickly re-generate the whole tree without expensively regenerating all of the other branches and leaves in the tree.&lt;br /&gt;
&lt;br /&gt;
This is known as &#039;Russian Doll&#039; caching:&lt;br /&gt;
&lt;br /&gt;
https://blog.appsignal.com/2018/04/03/russian-doll-caching-in-rails.html&lt;br /&gt;
&lt;br /&gt;
This is a very common strategy is many distributed systems, outside of the context of caching, and is conceptually how git works internally which is why commits are a sha1 hash.&lt;br /&gt;
&lt;br /&gt;
This strategy works well if you may periodically change back and forth to previous states which will still be present and so be immediate hits without re-warming. It works best with a cache store that supports Least Recently Used garbage collection.&lt;br /&gt;
=== Primary and Final caches ===&lt;br /&gt;
Because http requests are generally assigned randomly or round-robin to front ends, when a cache item version changes you will now effectively have an empty cache on every front end. As you get the same cache item again and again on each front end it will continue to be a cache miss on each local box until they are all warm, which can ironically mean that on average for a fast changing, but not often requested cache item, your cache hit rate will be very low and much worse than if you had a shared cache. The solution here is to have multiple levels of caches setup, a local cache backed by a shared cache. You do not need to do anything special in the code to support this if your cache is already localizable, the MUC manages this for you, ie if you request a key which is missing from the local cache it will then request it from the shared cache. If present it will copy it back to the local cache and then return the value. If it is not present in either then your fall back code will generate the item, and it will be written to both cache stores at the same time.&lt;br /&gt;
&lt;br /&gt;
This is especially important if you are scaling up and down the number of frontends quickly. If you suddenly need more horizontal scale and create a bunch of new front ends with empty caches and no shared cache they will all consume even more resources warming up and loading the shared services such as the database and filesystem.&lt;br /&gt;
&lt;br /&gt;
A good rule of thumb is to pair similar types of local and shared caches together. For instance it is very common to store the Moodle string cache in APCu because it is very heavily used, so an in-memory cache is the fastest and is well paired this a shared cache like Redis. coursemodinfo on the other hand is often very large so isn&#039;t as practical to store in Redis so it is usually cached on disk, so you could have a local file cache (which could often be very fast SSD) and pair it with a shared disk cache in dataroot (often much slower over NFS or Gluster etc).&lt;br /&gt;
&lt;br /&gt;
As you scale even bigger, a new bottleneck can appear when purging a shared disk cache ie when you deploy a new version. A full purge needs to iterate over and remove and sync a very large number of files, which can take some time. See MDL-69088 for a proposed fix.&lt;br /&gt;
=== Beware fast churning keys ===&lt;br /&gt;
A big concern when designing a cache is how fast you anticipate it changing. If it contains very fast moving data but sparsely requested data (see above) then you can end up in a situation where you are effectively just using the shared final cache, and wasting latency and space and IO cloning data to the local cache where is may not be hit again very much. As always caching is a balancing act trading off between CPU, time and disk, and ultimately money.&lt;br /&gt;
&lt;br /&gt;
Even if your cache is able to use a local store that doesn&#039;t mean it actually will be configured to be local (and your code can&#039;t tell either way). So a wasteful cache item will consume much more space storing all the previous versions of its items even if it isn&#039;t localized, and it will be much worse if it is.&lt;br /&gt;
=== Time-To-Live for distributed caches ===&lt;br /&gt;
Another consideration is the total size of your cache stores across all the front ends. As cache keys change they are never be invalidated or purged. So you should have in place some process to garbage collect stale items. This is more a concern for the cache store implementations and the configuration but worth considering. Some stores are deleted on upgrade, or have a Time-To-Live or a Least Recently Used strategy for deleting stale items.&lt;br /&gt;
==Using a data source==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Developing cache plugins==&lt;br /&gt;
==Miscellaneous==&lt;br /&gt;
* Checkout important [https://moodle.org/local/chatlogs/index.php?q=cache discussion about the Cache API at the Moodle developer (Telegram) chat]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Output_callbacks&amp;diff=61753</id>
		<title>Output callbacks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Output_callbacks&amp;diff=61753"/>
		<updated>2022-02-24T23:30:06Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Fixing minor type&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are cases where we want any plugin to contribute to a chunk of the output of any given page. We want this to be loosely coupled so you don&#039;t have say an admin tool&#039;s code leaking into your theme. In the past many plugins had an installation step like &amp;quot;Copy this into your theme header&amp;quot; which is what we want to completely avoid.&lt;br /&gt;
&lt;br /&gt;
There are a variety of [[Callbacks]] which enable any plugin to add or modify certain parts of the output and at certain stages in the rendering process. These callbacks are probably most useful for local plugins or admin tools which bring some functionality to the whole site instead of just certain pages. But they can also be used by any plugin to conditionally augment the output too.&lt;br /&gt;
= add_htmlattributes =&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
&lt;br /&gt;
This is used to append extra attributes into the html element of the page which are required elsewhere. An example might be a facebook block plugin which uses the opengraph js libraries which need the xml namespace to be setup. It should return an array of attribute key and values like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function tool_facebook_add_htmlattributes() {&lt;br /&gt;
     return array(&lt;br /&gt;
         &#039;xmlns:og&#039; =&amp;gt; &#039;http://ogp.me/ns#&#039;,&lt;br /&gt;
     );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Because multiple plugins can add extra attributes there is potential namespace clash issues. ie in the Facebook opengraph example stick with &amp;quot;xmlns:og&amp;quot; as specified by Facebook so that if multiple plugins declare the same key / value then they will match. Not that duplicates attribute keys are merged.&lt;br /&gt;
= before_footer =&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
&lt;br /&gt;
This enables you to easily inject a chunk of JS or CSS into every page, for instance an analytics tool like Google Analytics or Facebook pixel. It only has side effects and it&#039;s return value is ignored:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function tool_mytool_before_footer() {&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
   $PAGE-&amp;gt;requires-&amp;gt;js_init_code(&amp;quot;alert(&#039;before_footer&#039;);&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
= before_http_headers =&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
&lt;br /&gt;
This enables you to easily inject a HTTP header into every page. It only has side effects and it&#039;s return value is ignored:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function tool_headertest_before_http_headers() {&lt;br /&gt;
    header(&amp;quot;X-CustomHeader: SomeValue&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that this is called after the page is generated and just prior to the headers being sent. So internal system state may have already been changed (eg adding events to the log) and the page generation may have been expensive and the student will be considered to have viewed the page.&lt;br /&gt;
&lt;br /&gt;
You should generally not use this callback for things like redirecting away under some conditions, instead consider either the [[Login_callbacks#after_config|after_config]] or the [[Login_callbacks#after_require_login|after_require_login]] callbacks which fire earlier in the request lifecycle.&lt;br /&gt;
= before_standard_html_head =&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
&lt;br /&gt;
This is an API alternative to appending to $CFG-&amp;gt;additionalhtmlhead and could be used for adding meta tags or similar to the page. It MUST return a string containing a well formed html chunk, or at minimum an empty string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function tool_headtag_before_standard_html_head() {&lt;br /&gt;
    return &amp;quot;&amp;lt;meta name=&#039;foo&#039; value=&#039;before_top_of_body_html&#039; /&amp;gt;\n&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
= before_standard_top_of_body_html =&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
&lt;br /&gt;
This enables a plugin to insert a chunk of html at the start of the html document. Typical use cases include some sort of alert notification, but in many cases the [[Notifications]] may be a better fit. It MUST return a string containing a well formed chunk of html, or at minimum an empty string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function tool_callbacktest_before_standard_top_of_body_html() {&lt;br /&gt;
    return &amp;quot;&amp;lt;div style=&#039;background: red&#039;&amp;gt;Before standard top of body html&amp;lt;/div&amp;gt;&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
= render_navbar_output =&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
TBA&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=51031</id>
		<title>Acceptance testing/Browsers/Working combinations of OS+Browser+selenium</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=51031"/>
		<updated>2016-11-07T17:02:48Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Typo in last change&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Working combinations of OS+Browser+selenium =&lt;br /&gt;
As OS, Browsers and Selenium keeps updating, some combination might fail on different Moodle releases.&lt;br /&gt;
&lt;br /&gt;
Following combinations have been tested at the time of release of Moodle version and will be supported for that combination. &lt;br /&gt;
NOTE:  Chromedriver &amp;gt;= 2.17 is required for behat to capture exceptions&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.2 and master ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=2.53/ 2.53.1] and [http://selenium-release.storage.googleapis.com/index.html?path=3.0-beta4/ 3.0.0-beta4]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.24/ 2.24]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v53.0&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=2.53/ 2.53.1] and [http://selenium-release.storage.googleapis.com/index.html?path=3.0-beta4/ 3.0.0-beta4]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.24/ 2.24]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| [http://www.slimjet.com/chrome/google-chrome-old-version.php Chrome v53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=2.53/ 2.53.1] and [http://selenium-release.storage.googleapis.com/index.html?path=3.0-beta4/ 3.0.0-beta4]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.24/ 2.24]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.1 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 51.0]&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.0 and lower ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Firefox 42.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.19.346067&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Phantomjs 2.0.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Chrome 47.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 48.0&lt;br /&gt;
|2.51.0&lt;br /&gt;
| 2.21&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|PhantomJS - 2.0.0 &amp;amp; 2.1.1&lt;br /&gt;
|2.48.2&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=51030</id>
		<title>Acceptance testing/Browsers/Working combinations of OS+Browser+selenium</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=51030"/>
		<updated>2016-11-07T17:01:52Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Add link to old chrome bundles&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Working combinations of OS+Browser+selenium =&lt;br /&gt;
As OS, Browsers and Selenium keeps updating, some combination might fail on different Moodle releases.&lt;br /&gt;
&lt;br /&gt;
Following combinations have been tested at the time of release of Moodle version and will be supported for that combination. &lt;br /&gt;
NOTE:  Chromedriver &amp;gt;= 2.17 is required for behat to capture exceptions&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.2 and master ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=2.53/ 2.53.1] and [http://selenium-release.storage.googleapis.com/index.html?path=3.0-beta4/ 3.0.0-beta4]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.24/ 2.24]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v53.0&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=2.53/ 2.53.1] and [http://selenium-release.storage.googleapis.com/index.html?path=3.0-beta4/ 3.0.0-beta4]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.24/ 2.24]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| [http://www.slimjet.com/chrome/google-chrome-old-version.php/ Chrome v53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=2.53/ 2.53.1] and [http://selenium-release.storage.googleapis.com/index.html?path=3.0-beta4/ 3.0.0-beta4]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.24/ 2.24]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.1 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 51.0]&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.0 and lower ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Firefox 42.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.19.346067&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Phantomjs 2.0.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Chrome 47.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 48.0&lt;br /&gt;
|2.51.0&lt;br /&gt;
| 2.21&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|PhantomJS - 2.0.0 &amp;amp; 2.1.1&lt;br /&gt;
|2.48.2&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50060</id>
		<title>Search engines</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50060"/>
		<updated>2016-05-12T14:04:17Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Update information about file indexing.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Search engine plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Search engines index big amounts of data in a structured way that allow users to query them and extract relevant data. There are many search engines with nice APIs to set data on and retrieve data from. We made Moodle&#039;s global search pluggable so different backends can be used, from a simple database table (ok for small sites but unusable for big sites) to open sourced systems like solr or elasticsearch (on top of Apache Lucene) or proprietary cloud based systems.&lt;br /&gt;
&lt;br /&gt;
== Terms ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Index&#039;&#039;&#039;: You know what it means, but in this page we use &#039;&#039;&#039;index&#039;&#039;&#039; as the data container in your search engine. It can be an instance in your search engine server or a database table name if you are writing a search engine for mongodb (just an example)&lt;br /&gt;
&#039;&#039;&#039;Document&#039;&#039;&#039;: A &amp;quot;searchable&amp;quot; unit of data like a database entry, a forum post... You can see it as one of the search results you might expect to get returned by a search engine.&lt;br /&gt;
&lt;br /&gt;
== Writing your own search engine plugin ==&lt;br /&gt;
&lt;br /&gt;
To write your own search engine you need to code methods to set, retrieve and delete data from your search engine. You will need to add a &#039;&#039;&#039;\search_yourplugin\engine&#039;&#039;&#039; class in &#039;&#039;&#039;search/engine/yourplugin/classes/engine.php&#039;&#039;&#039; extending &#039;&#039;&#039;\core_search\engine&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Search engine setup ===&lt;br /&gt;
&lt;br /&gt;
Your search engine needs to be prepared to index data, you can have a script for your plugin users so they can easily create the required structure the search engine. Otherwise add instructions about how to do it.&lt;br /&gt;
&lt;br /&gt;
You can get the list of fields Moodle needs along with some other info you might need like the field types calling &#039;&#039;&#039;\core_search\document::get_default_fields_definition()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Add contents ===&lt;br /&gt;
&lt;br /&gt;
This method is executed when Moodle contents are being indexed in the search engine. Moodle iterates through all search areas extracting which contents should be indexed and assigns them a unique id based on the search area.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function add_document(array $doc, $fileindexing = false) {&lt;br /&gt;
    // Use curl or any other method or extension to push the document to your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;$doc&#039;&#039;&#039; will contain a document data with all required fields (+ maybe some optionals) and its contents will be already validated so a integer field will come with an integer value...&lt;br /&gt;
&#039;&#039;&#039;$fileindexing&#039;&#039;&#039; will be true the search area that generated the document supports attached files. Will be false if your plugin does not support file indexing&lt;br /&gt;
&lt;br /&gt;
==== File indexing ====&lt;br /&gt;
&lt;br /&gt;
If the engine supports file indexing, and $fileindexing is passed as true to add_document (indicating the area supports indexing), then the document sent will have all files associated with it attached as &#039;&#039;&#039;stored_file&#039;&#039;&#039; instances. They are retrieved from the document with get_files(), and it It is up to the engine to determine how these are to be indexed, including content extraction.&lt;br /&gt;
&lt;br /&gt;
The files attached to a document for indexing represent the authoritative set of files for that document. This means the engine should ensure that when re-indexing a document, any files no longer attached to it are not in the index.&lt;br /&gt;
&lt;br /&gt;
=== Retrieve contents ===&lt;br /&gt;
&lt;br /&gt;
This is the key method, as search engine plugins have a lot of flexibility here.&lt;br /&gt;
&lt;br /&gt;
You will get the search filters the user specified and the list of contexts the user can access and this function should return an array of \core_search\document objects.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function execute_query($filters, $usercontexts, $limit = 0) {&lt;br /&gt;
    // Prepare a query applyting all filters.&lt;br /&gt;
    // Include $usercontexts as a filter to contextid field.&lt;br /&gt;
    // Send a request to the server.&lt;br /&gt;
    // Iterate through results.&lt;br /&gt;
    // Check user access, read https://docs.moodle.org/dev/Search_engines#Security for more info&lt;br /&gt;
    // Convert results to &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; type objects using &#039;&#039;&#039;\core_search\document::set_data_from_engine&#039;&#039;&#039;&lt;br /&gt;
    // Return an array of &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; objects, limiting to $limit or \core_search\manager::MAX_RESULTS if empty.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== File indexing ====&lt;br /&gt;
&lt;br /&gt;
When retrieving results based on a file hit, you can attach &#039;&#039;&#039;stored_file&#039;&#039;&#039; instances to the document to indicate what file(s) produced the match. This information is rendered as a part of the results page. Because of rendering considerations, this should be limited to a reasonable number of &#039;match files&#039; for a given document. The search_solr limits to a maximum of 3 matching files.&lt;br /&gt;
&lt;br /&gt;
==== Security ====&lt;br /&gt;
&lt;br /&gt;
It is crucial that this function is checking &#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; results and do not return results where the user do not have access. Moodle already performs part of the required security checkings, but search areas always have the last word and it should be respected.&lt;br /&gt;
&lt;br /&gt;
==== Getting enough results ====&lt;br /&gt;
&lt;br /&gt;
Because in some cases many records may fail check_access(), engines should make provisions to make sure enough a full set of documents is returned, even if it must check many more documents. See MDL-53758 for a better discussion of this.&lt;br /&gt;
&lt;br /&gt;
=== Record counts ===&lt;br /&gt;
get_query_total_count() must be implemented to return the number of results that available for the most recent call to execute_query(). This is used to determine how many pages will be displayed in the paging bar. For more discussion see MDL-53758.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_query_total_count() {&lt;br /&gt;
    // Return an approximate count of total records for the most recently completed execute_query().&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value can be an estimate. The search manager will ensure that if a page is requested that is beyond the last page of actual results, the user will seamlessly see the last available page.&lt;br /&gt;
&lt;br /&gt;
There are a number of ways to determine what value to return from get_query_total_count().  Note that if the method you choose requires you to process more than $limit valid documents, you still must only return $limit records from execute_query(). Some of the ways to do this are:&lt;br /&gt;
&lt;br /&gt;
==== Return how many possible there are ====&lt;br /&gt;
This would mean how many results we have processed and passed (using check_access()), plus any candidate results that are left. Alternately it is the total count of records for the query, minus the ones we have rejected so far. search_solr uses this method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User sees a full compliment of pages. If the pass-to-fail ration on check_access() is very high (and we generally expect it to be), then the number of pages should generally be accurate. This method will always error on the high side. It is possible that when clicking on a higher page there will be no results available, so the search manager will seamlessly show them the last actual page with actual results.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free or very cheap with some engines - no need to check access/process records beyond the current page. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Future page count is not perfect, so can result in page not being available when clicked (but the manager mitigates this).&lt;br /&gt;
&lt;br /&gt;
==== Return the current count plus 1 ====&lt;br /&gt;
In this case, you would calculate all the records through $limit plus one. If the plus one exists, you would return that count, otherwise you would return the actual count.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User would only see up to the current page, plus one more, except when on the last page of results. Gmail search works similar to this in the way you can only navigate to the next page of results, not an arbitrary page.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Relatively cheap. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; User can’t jump to an arbitrary page even if they know what page a particular result is on&lt;br /&gt;
&lt;br /&gt;
==== Calculate all results up to MAX_RESULTS ====&lt;br /&gt;
This would mean calculating the full set of results up to MAX_RESULTS, and returning the actual count of results.&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; The user will see the exact number of pages they should&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; cleanest user experience&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Very expensive, as you are calculating up to MAX_RESULTS results on every page, even if you are only showing the first page&lt;br /&gt;
&lt;br /&gt;
==== Just return MAX_RESULTS ====&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User will always see 10 pages, except when they are on the last page of actual results. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Worst user experience &lt;br /&gt;
&lt;br /&gt;
=== Delete contents ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete($areaid = false) {&lt;br /&gt;
    if ($areaid === false) {&lt;br /&gt;
        // Delete all your search engine index contents.&lt;br /&gt;
    } else {&lt;br /&gt;
        // Delete all your search engine contents where areaid = $areaid.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; will return \core_search\manager::ACCESS_DELETED if a document returned from the search engine is not available in Moodle any more, you can use this to clean up the search engine contents with some kind of &#039;&#039;&#039;\search_yourplugin\engine::delete_by_id&#039;&#039;&#039; method. You can look at &#039;&#039;&#039;search/engine/solr/classes/engine.php&#039;&#039;&#039; &#039;&#039;&#039;execute_query&#039;&#039;&#039; method for an example of this.&lt;br /&gt;
&lt;br /&gt;
=== Other abstract methods you need to overwrite ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function file_indexing_enabled() {&lt;br /&gt;
    // Defaults to false, overwrite it if your search engine supports file indexing.&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_server_ready() {&lt;br /&gt;
    // Check if your search engine is ready.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Other methods you might be interested in overwriting ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_installed() {&lt;br /&gt;
    // Check if the required PHP extensions you need to make the search engine work are installed.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function optimize() {&lt;br /&gt;
    // Optimize or defragment the index contents.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These methods are called while the indexing process is running and allow search engine to hook the indexing process.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_starting($fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_complete($numdocs = 0, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_starting($searcharea, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_complete($searcharea, $numdocs = 0, $fullindex = false) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adapting document formats to your search engine format ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document&#039;&#039;&#039; is the class that represents a document, depending on your search engine backend limitations or on how it stores time values you might be interested in overwriting this class in &#039;&#039;&#039;\search_yourplugin\document&#039;&#039;&#039;. The main functions you might be interested in overwriting are:&lt;br /&gt;
&lt;br /&gt;
==== Format date/time fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_time_for_engine($timestamp) {&lt;br /&gt;
    // Convert $timestamp to a string using the format used by your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_time_for_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Import date/time contents from the search engine ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function import_time_from_engine($time) {&lt;br /&gt;
    // Convert the string returned from the search engine as a date/time format to a timestamp (integer).&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::import_time_from_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Format string fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_string_for_engine() {&lt;br /&gt;
    // Limit the string length, convert iconv if your search engine only supports an specific charset...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_string_for_engine&#039;&#039;&#039; returns the string as it is.&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Global_search&amp;diff=50059</id>
		<title>Global search</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Global_search&amp;diff=50059"/>
		<updated>2016-05-12T13:47:41Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Update SSL info&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Installation =&lt;br /&gt;
&lt;br /&gt;
To use Solr as Moodle&#039;s search engine you need the PHP Solr extension and a Solr server.&lt;br /&gt;
&lt;br /&gt;
== PHP Solr extension ==&lt;br /&gt;
&lt;br /&gt;
You need PHP Solr extension installed. You can download the official latest versions from [http://pecl.php.net/package/solr](http://pecl.php.net/package/solr) The minimum required version is PECL Solr 2.1 for PHP 5 branch and PECL Solr 2.4 for PHP 7 branch.&lt;br /&gt;
&lt;br /&gt;
Basic installation steps (using apache web server):&lt;br /&gt;
&lt;br /&gt;
=== Linux (Debian/Ubuntu) ===&lt;br /&gt;
&lt;br /&gt;
    sudo apt-get install libpcre3-dev libxml2-dev libcurl4-openssl-dev&lt;br /&gt;
    sudo apt-get install php5-dev&lt;br /&gt;
    sudo apt-get install php-pear&lt;br /&gt;
    sudo pecl install solr&lt;br /&gt;
    sudo service apache2 restart&lt;br /&gt;
    sudo sh -c &amp;quot;echo &#039;extension=solr.so&#039; &amp;gt; /etc/php5/apache2/conf.d/solr.ini&amp;quot;&lt;br /&gt;
    sudo sh -c &amp;quot;echo &#039;extension=solr.so&#039; &amp;gt; /etc/php5/cli/conf.d/solr.ini&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== OSX using macports ===&lt;br /&gt;
&lt;br /&gt;
    sudo port install apache-solr4&lt;br /&gt;
    sudo port install php54-solr&lt;br /&gt;
&lt;br /&gt;
=== OSX using homebrew ===&lt;br /&gt;
&lt;br /&gt;
    brew install homebrew/php/php56-solr&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
&lt;br /&gt;
Install the pecl package as usual. Sorry it has not been tested yet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Solr server ==&lt;br /&gt;
&lt;br /&gt;
Moodle 3.1 supports Solr server from 4.0 onwards, although you can only use the Solr schema setup script that we provide with Moodle from Solr 5. The latest Solr 5 available version is the recommended one, same applicable to Solr 6 once it is released. &lt;br /&gt;
&lt;br /&gt;
The following example snippet (feel free to copy &amp;amp; paste into a .sh script with execution permissions) will download Solr 5.4.1 (replace it with latest 5.x) in the current directory, start the solr server and create an index in it named &#039;&#039;&#039;moodle&#039;&#039;&#039; to add moodle data to it.&lt;br /&gt;
&lt;br /&gt;
    #!/bin/bash&lt;br /&gt;
    set -e&lt;br /&gt;
    SOLRVERSION=5.4.1&lt;br /&gt;
    SOLRNAME=solr-$SOLRVERSION&lt;br /&gt;
    SOLRTAR=$SOLRNAME&#039;.tgz&#039;&lt;br /&gt;
    INDEXNAME=moodle&lt;br /&gt;
    if [ -d $SOLRNAME ]; then&lt;br /&gt;
        echo &amp;quot;Error: Directory $SOLRNAME already exists, remove it before starting the setup again.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
    fi&lt;br /&gt;
    if [ ! -f $SOLRTAR ]; then&lt;br /&gt;
        wget http://apache.mirror.digitalpacific.com.au/lucene/solr/$SOLRVERSION/$SOLRTAR&lt;br /&gt;
    fi&lt;br /&gt;
    tar -xvzf $SOLRTAR&lt;br /&gt;
    cd $SOLRNAME&lt;br /&gt;
    bin/solr start&lt;br /&gt;
    bin/solr create -c $INDEXNAME&lt;br /&gt;
    # After setting it up and creating the index use:&lt;br /&gt;
    # - &amp;quot;/yourdirectory/solrdir/bin/solr start&amp;quot; from CLI to start the server&lt;br /&gt;
    # - &amp;quot;/yourdirectory/solrdir/bin/solr stop&amp;quot; from CLI to stop the server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Setup =&lt;br /&gt;
&lt;br /&gt;
# Go to &#039;&#039;&#039;Site administration -&amp;gt; Plugins -&amp;gt; Search -&amp;gt; Manage global search&#039;&#039;&#039;&lt;br /&gt;
# Follow &#039;&#039;&#039;Search setup&#039;&#039;&#039; steps to complete the setup process. Basically you have to:&lt;br /&gt;
## Enable global search&lt;br /&gt;
## Set &#039;&#039;&#039;Solr&#039;&#039;&#039; as search engine and tick all search areas checkboxes&lt;br /&gt;
## Go to &#039;&#039;&#039;Site administration -&amp;gt; Plugins -&amp;gt; Search -&amp;gt; Solr&#039;&#039;&#039; and set &#039;&#039;&#039;Host name&#039;&#039;&#039; to localhost, &#039;&#039;&#039;Port&#039;&#039;&#039; to 8983 and &#039;&#039;&#039;Index name&#039;&#039;&#039; to &#039;moodle&#039; (the name of the index in Solr)&lt;br /&gt;
&lt;br /&gt;
== Solr 4 schema setup ==&lt;br /&gt;
&lt;br /&gt;
As mentioned above you can not use the schema setup script when using a Solr 4 server. Here you can find the field types description below in case you really want to use Solr 4.x branch.&lt;br /&gt;
&lt;br /&gt;
Extracted from search/classes/document.php&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field name&lt;br /&gt;
! Field type&lt;br /&gt;
! Stored&lt;br /&gt;
! Indexed&lt;br /&gt;
! Query field&lt;br /&gt;
|-&lt;br /&gt;
| id&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| itemid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| title&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| content&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| contextid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| areaid&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| type&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| courseid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| owneruserid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| modified&lt;br /&gt;
| org.apache.solr.schema.TrieDateField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| userid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| description1&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| description2&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| solr_filegroupingid&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_fileid&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_filecontenthash&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_fileindexstatus&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_filecontent&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| false&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== SSL Setup ==&lt;br /&gt;
&lt;br /&gt;
If you are using Solr with SSL encryption, you will need to configure Moodle as such.&lt;br /&gt;
&lt;br /&gt;
# You will need a separate key file and cacert file, both in pem format, located on your server Moodle, and readable by the PHP process.&lt;br /&gt;
# Go to &#039;&#039;Site administration &amp;gt; Plugins &amp;gt; Search &amp;gt; Solr&#039;&#039;&lt;br /&gt;
# Set &#039;&#039;&#039;Secure mode&#039;&#039;&#039; to Yes&lt;br /&gt;
# &#039;&#039;&#039;SSL certificate&#039;&#039;&#039; to /path/to/certs/solr-ssl.cacert.pem&lt;br /&gt;
# &#039;&#039;&#039;SSL key&#039;&#039;&#039; to /path/to/certs/solr-ssl.key.pem&lt;br /&gt;
# &#039;&#039;&#039;SSL key Password&#039;&#039;&#039; to The password used to lock the SSL Key&lt;br /&gt;
# &#039;&#039;&#039;SSL CA certificates name&#039;&#039;&#039; to /path/to/certs/solr-ssl.cacert.pem&lt;br /&gt;
# Save&lt;br /&gt;
# On the search management page it should indicate it can connect to Solr.&lt;br /&gt;
&lt;br /&gt;
= Indexing =&lt;br /&gt;
&lt;br /&gt;
Once global search is enabled a new task will be running through cron, although you can force documents indexing by:&lt;br /&gt;
&lt;br /&gt;
# Going to &#039;&#039;&#039;Reports -&amp;gt; Global search info&#039;&#039;&#039;&lt;br /&gt;
# Tick &#039;&#039;&#039;Index all site contents&#039;&#039;&#039; and press &#039;&#039;&#039;Execute&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
* Executing the following task from CLI&lt;br /&gt;
&lt;br /&gt;
    php admin/tool/task/cli/schedule_task.php --execute=&amp;quot;\\core\\task\\search_index_task&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Querying =&lt;br /&gt;
&lt;br /&gt;
# Hover the search icon in the navigation bar, type your query and press &#039;&#039;&#039;Enter&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
# Add &#039;&#039;&#039;Global search&#039;&#039;&#039; block somewhere&lt;br /&gt;
# Type your query and press &#039;&#039;&#039;Search&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Global_search&amp;diff=50058</id>
		<title>Global search</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Global_search&amp;diff=50058"/>
		<updated>2016-05-12T13:32:10Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Add information about encrypting Solr connection&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Installation =&lt;br /&gt;
&lt;br /&gt;
To use Solr as Moodle&#039;s search engine you need the PHP Solr extension and a Solr server.&lt;br /&gt;
&lt;br /&gt;
== PHP Solr extension ==&lt;br /&gt;
&lt;br /&gt;
You need PHP Solr extension installed. You can download the official latest versions from [http://pecl.php.net/package/solr](http://pecl.php.net/package/solr) The minimum required version is PECL Solr 2.1 for PHP 5 branch and PECL Solr 2.4 for PHP 7 branch.&lt;br /&gt;
&lt;br /&gt;
Basic installation steps (using apache web server):&lt;br /&gt;
&lt;br /&gt;
=== Linux (Debian/Ubuntu) ===&lt;br /&gt;
&lt;br /&gt;
    sudo apt-get install libpcre3-dev libxml2-dev libcurl4-openssl-dev&lt;br /&gt;
    sudo apt-get install php5-dev&lt;br /&gt;
    sudo apt-get install php-pear&lt;br /&gt;
    sudo pecl install solr&lt;br /&gt;
    sudo service apache2 restart&lt;br /&gt;
    sudo sh -c &amp;quot;echo &#039;extension=solr.so&#039; &amp;gt; /etc/php5/apache2/conf.d/solr.ini&amp;quot;&lt;br /&gt;
    sudo sh -c &amp;quot;echo &#039;extension=solr.so&#039; &amp;gt; /etc/php5/cli/conf.d/solr.ini&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== OSX using macports ===&lt;br /&gt;
&lt;br /&gt;
    sudo port install apache-solr4&lt;br /&gt;
    sudo port install php54-solr&lt;br /&gt;
&lt;br /&gt;
=== OSX using homebrew ===&lt;br /&gt;
&lt;br /&gt;
    brew install homebrew/php/php56-solr&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
&lt;br /&gt;
Install the pecl package as usual. Sorry it has not been tested yet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Solr server ==&lt;br /&gt;
&lt;br /&gt;
Moodle 3.1 supports Solr server from 4.0 onwards, although you can only use the Solr schema setup script that we provide with Moodle from Solr 5. The latest Solr 5 available version is the recommended one, same applicable to Solr 6 once it is released. &lt;br /&gt;
&lt;br /&gt;
The following example snippet (feel free to copy &amp;amp; paste into a .sh script with execution permissions) will download Solr 5.4.1 (replace it with latest 5.x) in the current directory, start the solr server and create an index in it named &#039;&#039;&#039;moodle&#039;&#039;&#039; to add moodle data to it.&lt;br /&gt;
&lt;br /&gt;
    #!/bin/bash&lt;br /&gt;
    set -e&lt;br /&gt;
    SOLRVERSION=5.4.1&lt;br /&gt;
    SOLRNAME=solr-$SOLRVERSION&lt;br /&gt;
    SOLRTAR=$SOLRNAME&#039;.tgz&#039;&lt;br /&gt;
    INDEXNAME=moodle&lt;br /&gt;
    if [ -d $SOLRNAME ]; then&lt;br /&gt;
        echo &amp;quot;Error: Directory $SOLRNAME already exists, remove it before starting the setup again.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
    fi&lt;br /&gt;
    if [ ! -f $SOLRTAR ]; then&lt;br /&gt;
        wget http://apache.mirror.digitalpacific.com.au/lucene/solr/$SOLRVERSION/$SOLRTAR&lt;br /&gt;
    fi&lt;br /&gt;
    tar -xvzf $SOLRTAR&lt;br /&gt;
    cd $SOLRNAME&lt;br /&gt;
    bin/solr start&lt;br /&gt;
    bin/solr create -c $INDEXNAME&lt;br /&gt;
    # After setting it up and creating the index use:&lt;br /&gt;
    # - &amp;quot;/yourdirectory/solrdir/bin/solr start&amp;quot; from CLI to start the server&lt;br /&gt;
    # - &amp;quot;/yourdirectory/solrdir/bin/solr stop&amp;quot; from CLI to stop the server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Setup =&lt;br /&gt;
&lt;br /&gt;
# Go to &#039;&#039;&#039;Site administration -&amp;gt; Plugins -&amp;gt; Search -&amp;gt; Manage global search&#039;&#039;&#039;&lt;br /&gt;
# Follow &#039;&#039;&#039;Search setup&#039;&#039;&#039; steps to complete the setup process. Basically you have to:&lt;br /&gt;
## Enable global search&lt;br /&gt;
## Set &#039;&#039;&#039;Solr&#039;&#039;&#039; as search engine and tick all search areas checkboxes&lt;br /&gt;
## Go to &#039;&#039;&#039;Site administration -&amp;gt; Plugins -&amp;gt; Search -&amp;gt; Solr&#039;&#039;&#039; and set &#039;&#039;&#039;Host name&#039;&#039;&#039; to localhost, &#039;&#039;&#039;Port&#039;&#039;&#039; to 8983 and &#039;&#039;&#039;Index name&#039;&#039;&#039; to &#039;moodle&#039; (the name of the index in Solr)&lt;br /&gt;
&lt;br /&gt;
== Solr 4 schema setup ==&lt;br /&gt;
&lt;br /&gt;
As mentioned above you can not use the schema setup script when using a Solr 4 server. Here you can find the field types description below in case you really want to use Solr 4.x branch.&lt;br /&gt;
&lt;br /&gt;
Extracted from search/classes/document.php&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field name&lt;br /&gt;
! Field type&lt;br /&gt;
! Stored&lt;br /&gt;
! Indexed&lt;br /&gt;
! Query field&lt;br /&gt;
|-&lt;br /&gt;
| id&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| itemid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| title&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| content&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| contextid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| areaid&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| type&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| courseid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| owneruserid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| modified&lt;br /&gt;
| org.apache.solr.schema.TrieDateField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| userid&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| description1&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| description2&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|-&lt;br /&gt;
| solr_filegroupingid&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_fileid&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_filecontenthash&lt;br /&gt;
| org.apache.solr.schema.StrField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_fileindexstatus&lt;br /&gt;
| org.apache.solr.schema.TrieIntField&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
| false&lt;br /&gt;
|-&lt;br /&gt;
| solr_filecontent&lt;br /&gt;
| org.apache.solr.schema.TextField&lt;br /&gt;
| false&lt;br /&gt;
| true&lt;br /&gt;
| true&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== SSL Setup ==&lt;br /&gt;
&lt;br /&gt;
If you are using Solr with SSL encryption, you will need to configure Moodle as such.&lt;br /&gt;
&lt;br /&gt;
# You will need a separate key file and cacert file, both in pem format, located on your server Moodle, and readable by the PHP process.&lt;br /&gt;
# In the Moodle Solr config page:&lt;br /&gt;
## Enable Secure&lt;br /&gt;
## SSL certificate: /path/to/certs/solr-ssl.cacert.pem&lt;br /&gt;
## SSL key: /path/to/certs/solr-ssl.key.pem&lt;br /&gt;
## SSL key Password: The password used to lock the SSL Key&lt;br /&gt;
## SSL CA certificates name: /path/to/certs/solr-ssl.cacert.pem&lt;br /&gt;
# Save&lt;br /&gt;
# On the search management page it still indicates it can connect to Solr.&lt;br /&gt;
&lt;br /&gt;
= Indexing =&lt;br /&gt;
&lt;br /&gt;
Once global search is enabled a new task will be running through cron, although you can force documents indexing by:&lt;br /&gt;
&lt;br /&gt;
# Going to &#039;&#039;&#039;Reports -&amp;gt; Global search info&#039;&#039;&#039;&lt;br /&gt;
# Tick &#039;&#039;&#039;Index all site contents&#039;&#039;&#039; and press &#039;&#039;&#039;Execute&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
* Executing the following task from CLI&lt;br /&gt;
&lt;br /&gt;
    php admin/tool/task/cli/schedule_task.php --execute=&amp;quot;\\core\\task\\search_index_task&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Querying =&lt;br /&gt;
&lt;br /&gt;
# Hover the search icon in the navigation bar, type your query and press &#039;&#039;&#039;Enter&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
# Add &#039;&#039;&#039;Global search&#039;&#039;&#039; block somewhere&lt;br /&gt;
# Type your query and press &#039;&#039;&#039;Search&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50057</id>
		<title>Search engines</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50057"/>
		<updated>2016-05-12T13:21:53Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Retrieve contents */ Update limit discussion&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Search engine plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Search engines index big amounts of data in a structured way that allow users to query them and extract relevant data. There are many search engines with nice APIs to set data on and retrieve data from. We made Moodle&#039;s global search pluggable so different backends can be used, from a simple database table (ok for small sites but unusable for big sites) to open sourced systems like solr or elasticsearch (on top of Apache Lucene) or proprietary cloud based systems.&lt;br /&gt;
&lt;br /&gt;
== Terms ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Index&#039;&#039;&#039;: You know what it means, but in this page we use &#039;&#039;&#039;index&#039;&#039;&#039; as the data container in your search engine. It can be an instance in your search engine server or a database table name if you are writing a search engine for mongodb (just an example)&lt;br /&gt;
&#039;&#039;&#039;Document&#039;&#039;&#039;: A &amp;quot;searchable&amp;quot; unit of data like a database entry, a forum post... You can see it as one of the search results you might expect to get returned by a search engine.&lt;br /&gt;
&lt;br /&gt;
== Writing your own search engine plugin ==&lt;br /&gt;
&lt;br /&gt;
To write your own search engine you need to code methods to set, retrieve and delete data from your search engine. You will need to add a &#039;&#039;&#039;\search_yourplugin\engine&#039;&#039;&#039; class in &#039;&#039;&#039;search/engine/yourplugin/classes/engine.php&#039;&#039;&#039; extending &#039;&#039;&#039;\core_search\engine&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Search engine setup ===&lt;br /&gt;
&lt;br /&gt;
Your search engine needs to be prepared to index data, you can have a script for your plugin users so they can easily create the required structure the search engine. Otherwise add instructions about how to do it.&lt;br /&gt;
&lt;br /&gt;
You can get the list of fields Moodle needs along with some other info you might need like the field types calling &#039;&#039;&#039;\core_search\document::get_default_fields_definition()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Add contents ===&lt;br /&gt;
&lt;br /&gt;
This method is executed when Moodle contents are being indexed in the search engine. Moodle iterates through all search areas extracting which contents should be indexed and assigns them a unique id based on the search area.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function add_document(array $doc, $fileindexing = false) {&lt;br /&gt;
    // Use curl or any other method or extension to push the document to your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;$doc&#039;&#039;&#039; will contain a document data with all required fields (+ maybe some optionals) and its contents will be already validated so a integer field will come with an integer value...&lt;br /&gt;
&#039;&#039;&#039;$fileindexing&#039;&#039;&#039; will be true if file indexing if files should be indexed. Will be false if your plugin does not support file indexing&lt;br /&gt;
&lt;br /&gt;
=== Retrieve contents ===&lt;br /&gt;
&lt;br /&gt;
This is the key method, as search engine plugins have a lot of flexibility here.&lt;br /&gt;
&lt;br /&gt;
You will get the search filters the user specified and the list of contexts the user can access and this function should return an array of \core_search\document objects.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function execute_query($filters, $usercontexts, $limit = 0) {&lt;br /&gt;
    // Prepare a query applyting all filters.&lt;br /&gt;
    // Include $usercontexts as a filter to contextid field.&lt;br /&gt;
    // Send a request to the server.&lt;br /&gt;
    // Iterate through results.&lt;br /&gt;
    // Check user access, read https://docs.moodle.org/dev/Search_engines#Security for more info&lt;br /&gt;
    // Convert results to &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; type objects using &#039;&#039;&#039;\core_search\document::set_data_from_engine&#039;&#039;&#039;&lt;br /&gt;
    // Return an array of &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; objects, limiting to $limit or \core_search\manager::MAX_RESULTS if empty.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Security ====&lt;br /&gt;
&lt;br /&gt;
It is crucial that this function is checking &#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; results and do not return results where the user do not have access. Moodle already performs part of the required security checkings, but search areas always have the last word and it should be respected.&lt;br /&gt;
&lt;br /&gt;
==== Getting enough results ====&lt;br /&gt;
&lt;br /&gt;
Because in some cases many records may fail check_access(), engines should make provisions to make sure enough a full set of documents is returned, even if it must check many more documents. See MDL-53758 for a better discussion of this.&lt;br /&gt;
&lt;br /&gt;
=== Record counts ===&lt;br /&gt;
get_query_total_count() must be implemented to return the number of results that available for the most recent call to execute_query(). This is used to determine how many pages will be displayed in the paging bar. For more discussion see MDL-53758.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_query_total_count() {&lt;br /&gt;
    // Return an approximate count of total records for the most recently completed execute_query().&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value can be an estimate. The search manager will ensure that if a page is requested that is beyond the last page of actual results, the user will seamlessly see the last available page.&lt;br /&gt;
&lt;br /&gt;
There are a number of ways to determine what value to return from get_query_total_count().  Note that if the method you choose requires you to process more than $limit valid documents, you still must only return $limit records from execute_query(). Some of the ways to do this are:&lt;br /&gt;
&lt;br /&gt;
==== Return how many possible there are ====&lt;br /&gt;
This would mean how many results we have processed and passed (using check_access()), plus any candidate results that are left. Alternately it is the total count of records for the query, minus the ones we have rejected so far. search_solr uses this method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User sees a full compliment of pages. If the pass-to-fail ration on check_access() is very high (and we generally expect it to be), then the number of pages should generally be accurate. This method will always error on the high side. It is possible that when clicking on a higher page there will be no results available, so the search manager will seamlessly show them the last actual page with actual results.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free or very cheap with some engines - no need to check access/process records beyond the current page. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Future page count is not perfect, so can result in page not being available when clicked (but the manager mitigates this).&lt;br /&gt;
&lt;br /&gt;
==== Return the current count plus 1 ====&lt;br /&gt;
In this case, you would calculate all the records through $limit plus one. If the plus one exists, you would return that count, otherwise you would return the actual count.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User would only see up to the current page, plus one more, except when on the last page of results. Gmail search works similar to this in the way you can only navigate to the next page of results, not an arbitrary page.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Relatively cheap. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; User can’t jump to an arbitrary page even if they know what page a particular result is on&lt;br /&gt;
&lt;br /&gt;
==== Calculate all results up to MAX_RESULTS ====&lt;br /&gt;
This would mean calculating the full set of results up to MAX_RESULTS, and returning the actual count of results.&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; The user will see the exact number of pages they should&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; cleanest user experience&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Very expensive, as you are calculating up to MAX_RESULTS results on every page, even if you are only showing the first page&lt;br /&gt;
&lt;br /&gt;
==== Just return MAX_RESULTS ====&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User will always see 10 pages, except when they are on the last page of actual results. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Worst user experience &lt;br /&gt;
&lt;br /&gt;
=== Delete contents ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete($areaid = false) {&lt;br /&gt;
    if ($areaid === false) {&lt;br /&gt;
        // Delete all your search engine index contents.&lt;br /&gt;
    } else {&lt;br /&gt;
        // Delete all your search engine contents where areaid = $areaid.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; will return \core_search\manager::ACCESS_DELETED if a document returned from the search engine is not available in Moodle any more, you can use this to clean up the search engine contents with some kind of &#039;&#039;&#039;\search_yourplugin\engine::delete_by_id&#039;&#039;&#039; method. You can look at &#039;&#039;&#039;search/engine/solr/classes/engine.php&#039;&#039;&#039; &#039;&#039;&#039;execute_query&#039;&#039;&#039; method for an example of this.&lt;br /&gt;
&lt;br /&gt;
=== Other abstract methods you need to overwrite ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function file_indexing_enabled() {&lt;br /&gt;
    // Defaults to false, overwrite it if your search engine supports file indexing.&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_server_ready() {&lt;br /&gt;
    // Check if your search engine is ready.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Other methods you might be interested in overwriting ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_installed() {&lt;br /&gt;
    // Check if the required PHP extensions you need to make the search engine work are installed.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function optimize() {&lt;br /&gt;
    // Optimize or defragment the index contents.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These methods are called while the indexing process is running and allow search engine to hook the indexing process.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_starting($fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_complete($numdocs = 0, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_starting($searcharea, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_complete($searcharea, $numdocs = 0, $fullindex = false) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adapting document formats to your search engine format ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document&#039;&#039;&#039; is the class that represents a document, depending on your search engine backend limitations or on how it stores time values you might be interested in overwriting this class in &#039;&#039;&#039;\search_yourplugin\document&#039;&#039;&#039;. The main functions you might be interested in overwriting are:&lt;br /&gt;
&lt;br /&gt;
==== Format date/time fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_time_for_engine($timestamp) {&lt;br /&gt;
    // Convert $timestamp to a string using the format used by your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_time_for_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Import date/time contents from the search engine ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function import_time_from_engine($time) {&lt;br /&gt;
    // Convert the string returned from the search engine as a date/time format to a timestamp (integer).&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::import_time_from_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Format string fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_string_for_engine() {&lt;br /&gt;
    // Limit the string length, convert iconv if your search engine only supports an specific charset...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_string_for_engine&#039;&#039;&#039; returns the string as it is.&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50056</id>
		<title>Search engines</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50056"/>
		<updated>2016-05-12T13:08:33Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Formatting update, info from MDL-53758&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Search engine plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Search engines index big amounts of data in a structured way that allow users to query them and extract relevant data. There are many search engines with nice APIs to set data on and retrieve data from. We made Moodle&#039;s global search pluggable so different backends can be used, from a simple database table (ok for small sites but unusable for big sites) to open sourced systems like solr or elasticsearch (on top of Apache Lucene) or proprietary cloud based systems.&lt;br /&gt;
&lt;br /&gt;
== Terms ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Index&#039;&#039;&#039;: You know what it means, but in this page we use &#039;&#039;&#039;index&#039;&#039;&#039; as the data container in your search engine. It can be an instance in your search engine server or a database table name if you are writing a search engine for mongodb (just an example)&lt;br /&gt;
&#039;&#039;&#039;Document&#039;&#039;&#039;: A &amp;quot;searchable&amp;quot; unit of data like a database entry, a forum post... You can see it as one of the search results you might expect to get returned by a search engine.&lt;br /&gt;
&lt;br /&gt;
== Writing your own search engine plugin ==&lt;br /&gt;
&lt;br /&gt;
To write your own search engine you need to code methods to set, retrieve and delete data from your search engine. You will need to add a &#039;&#039;&#039;\search_yourplugin\engine&#039;&#039;&#039; class in &#039;&#039;&#039;search/engine/yourplugin/classes/engine.php&#039;&#039;&#039; extending &#039;&#039;&#039;\core_search\engine&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Search engine setup ===&lt;br /&gt;
&lt;br /&gt;
Your search engine needs to be prepared to index data, you can have a script for your plugin users so they can easily create the required structure the search engine. Otherwise add instructions about how to do it.&lt;br /&gt;
&lt;br /&gt;
You can get the list of fields Moodle needs along with some other info you might need like the field types calling &#039;&#039;&#039;\core_search\document::get_default_fields_definition()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Add contents ===&lt;br /&gt;
&lt;br /&gt;
This method is executed when Moodle contents are being indexed in the search engine. Moodle iterates through all search areas extracting which contents should be indexed and assigns them a unique id based on the search area.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function add_document(array $doc, $fileindexing = false) {&lt;br /&gt;
    // Use curl or any other method or extension to push the document to your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;$doc&#039;&#039;&#039; will contain a document data with all required fields (+ maybe some optionals) and its contents will be already validated so a integer field will come with an integer value...&lt;br /&gt;
&#039;&#039;&#039;$fileindexing&#039;&#039;&#039; will be true if file indexing if files should be indexed. Will be false if your plugin does not support file indexing&lt;br /&gt;
&lt;br /&gt;
=== Retrieve contents ===&lt;br /&gt;
&lt;br /&gt;
This is the key method, as search engine plugins have a lot of flexibility here.&lt;br /&gt;
&lt;br /&gt;
You will get the search filters the user specified and the list of contexts the user can access and this function should return an array of \core_search\document objects.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function execute_query($filters, $usercontexts, $limit = 0) {&lt;br /&gt;
    // Prepare a query applyting all filters.&lt;br /&gt;
    // Include $usercontexts as a filter to contextid field.&lt;br /&gt;
    // Send a request to the server.&lt;br /&gt;
    // Iterate through results (respecting $limit or  \core_search\manager::MAX_RESULTS if empty).&lt;br /&gt;
    // Check user access, read https://docs.moodle.org/dev/Search_engines#Security for more info&lt;br /&gt;
    // Convert results to &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; type objects using &#039;&#039;&#039;\core_search\document::set_data_from_engine&#039;&#039;&#039;&lt;br /&gt;
    // Return an array of &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; objects.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Security ====&lt;br /&gt;
&lt;br /&gt;
It is crucial that this function is checking &#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; results and do not return results where the user do not have access. Moodle already performs part of the required security checkings, but search areas always have the last word and it should be respected.&lt;br /&gt;
&lt;br /&gt;
==== Getting enough results ====&lt;br /&gt;
&lt;br /&gt;
Because in some cases many records may fail check_access(), engines should make provisions to make sure enough a full set of documents is returned, even if it must check many more documents. See MDL-53758 for a better discussion of this.&lt;br /&gt;
&lt;br /&gt;
=== Record counts ===&lt;br /&gt;
get_query_total_count() must be implemented to return the number of results that available for the most recent call to execute_query(). This is used to determine how many pages will be displayed in the paging bar. For more discussion see MDL-53758.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_query_total_count() {&lt;br /&gt;
    // Return an approximate count of total records for the most recently completed execute_query().&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value can be an estimate. The search manager will ensure that if a page is requested that is beyond the last page of actual results, the user will seamlessly see the last available page.&lt;br /&gt;
&lt;br /&gt;
There are a number of ways to determine what value to return from get_query_total_count().  Note that if the method you choose requires you to process more than $limit valid documents, you still must only return $limit records from execute_query(). Some of the ways to do this are:&lt;br /&gt;
&lt;br /&gt;
==== Return how many possible there are ====&lt;br /&gt;
This would mean how many results we have processed and passed (using check_access()), plus any candidate results that are left. Alternately it is the total count of records for the query, minus the ones we have rejected so far. search_solr uses this method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User sees a full compliment of pages. If the pass-to-fail ration on check_access() is very high (and we generally expect it to be), then the number of pages should generally be accurate. This method will always error on the high side. It is possible that when clicking on a higher page there will be no results available, so the search manager will seamlessly show them the last actual page with actual results.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free or very cheap with some engines - no need to check access/process records beyond the current page. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Future page count is not perfect, so can result in page not being available when clicked (but the manager mitigates this).&lt;br /&gt;
&lt;br /&gt;
==== Return the current count plus 1 ====&lt;br /&gt;
In this case, you would calculate all the records through $limit plus one. If the plus one exists, you would return that count, otherwise you would return the actual count.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User would only see up to the current page, plus one more, except when on the last page of results. Gmail search works similar to this in the way you can only navigate to the next page of results, not an arbitrary page.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Relatively cheap. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; User can’t jump to an arbitrary page even if they know what page a particular result is on&lt;br /&gt;
&lt;br /&gt;
==== Calculate all results up to MAX_RESULTS ====&lt;br /&gt;
This would mean calculating the full set of results up to MAX_RESULTS, and returning the actual count of results.&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; The user will see the exact number of pages they should&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; cleanest user experience&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Very expensive, as you are calculating up to MAX_RESULTS results on every page, even if you are only showing the first page&lt;br /&gt;
&lt;br /&gt;
==== Just return MAX_RESULTS ====&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User will always see 10 pages, except when they are on the last page of actual results. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Worst user experience &lt;br /&gt;
&lt;br /&gt;
=== Delete contents ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete($areaid = false) {&lt;br /&gt;
    if ($areaid === false) {&lt;br /&gt;
        // Delete all your search engine index contents.&lt;br /&gt;
    } else {&lt;br /&gt;
        // Delete all your search engine contents where areaid = $areaid.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; will return \core_search\manager::ACCESS_DELETED if a document returned from the search engine is not available in Moodle any more, you can use this to clean up the search engine contents with some kind of &#039;&#039;&#039;\search_yourplugin\engine::delete_by_id&#039;&#039;&#039; method. You can look at &#039;&#039;&#039;search/engine/solr/classes/engine.php&#039;&#039;&#039; &#039;&#039;&#039;execute_query&#039;&#039;&#039; method for an example of this.&lt;br /&gt;
&lt;br /&gt;
=== Other abstract methods you need to overwrite ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function file_indexing_enabled() {&lt;br /&gt;
    // Defaults to false, overwrite it if your search engine supports file indexing.&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_server_ready() {&lt;br /&gt;
    // Check if your search engine is ready.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Other methods you might be interested in overwriting ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_installed() {&lt;br /&gt;
    // Check if the required PHP extensions you need to make the search engine work are installed.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function optimize() {&lt;br /&gt;
    // Optimize or defragment the index contents.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These methods are called while the indexing process is running and allow search engine to hook the indexing process.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_starting($fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_complete($numdocs = 0, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_starting($searcharea, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_complete($searcharea, $numdocs = 0, $fullindex = false) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adapting document formats to your search engine format ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document&#039;&#039;&#039; is the class that represents a document, depending on your search engine backend limitations or on how it stores time values you might be interested in overwriting this class in &#039;&#039;&#039;\search_yourplugin\document&#039;&#039;&#039;. The main functions you might be interested in overwriting are:&lt;br /&gt;
&lt;br /&gt;
==== Format date/time fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_time_for_engine($timestamp) {&lt;br /&gt;
    // Convert $timestamp to a string using the format used by your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_time_for_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Import date/time contents from the search engine ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function import_time_from_engine($time) {&lt;br /&gt;
    // Convert the string returned from the search engine as a date/time format to a timestamp (integer).&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::import_time_from_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Format string fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_string_for_engine() {&lt;br /&gt;
    // Limit the string length, convert iconv if your search engine only supports an specific charset...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_string_for_engine&#039;&#039;&#039; returns the string as it is.&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50049</id>
		<title>Search engines</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50049"/>
		<updated>2016-05-12T01:42:17Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Add details about get_query_total_count()&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Search engine plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Search engines index big amounts of data in a structured way that allow users to query them and extract relevant data. There are many search engines with nice APIs to set data on and retrieve data from. We made Moodle&#039;s global search pluggable so different backends can be used, from a simple database table (ok for small sites but unusable for big sites) to open sourced systems like solr or elasticsearch (on top of Apache Lucene) or proprietary cloud based systems.&lt;br /&gt;
&lt;br /&gt;
== Terms ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Index&#039;&#039;&#039;: You know what it means, but in this page we use &#039;&#039;&#039;index&#039;&#039;&#039; as the data container in your search engine. It can be an instance in your search engine server or a database table name if you are writing a search engine for mongodb (just an example)&lt;br /&gt;
&#039;&#039;&#039;Document&#039;&#039;&#039;: A &amp;quot;searchable&amp;quot; unit of data like a database entry, a forum post... You can see it as one of the search results you might expect to get returned by a search engine.&lt;br /&gt;
&lt;br /&gt;
== Writing your own search engine plugin ==&lt;br /&gt;
&lt;br /&gt;
To write your own search engine you need to code methods to set, retrieve and delete data from your search engine. You will need to add a &#039;&#039;&#039;\search_yourplugin\engine&#039;&#039;&#039; class in &#039;&#039;&#039;search/engine/yourplugin/classes/engine.php&#039;&#039;&#039; extending &#039;&#039;&#039;\core_search\engine&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Search engine setup ===&lt;br /&gt;
&lt;br /&gt;
Your search engine needs to be prepared to index data, you can have a script for your plugin users so they can easily create the required structure the search engine. Otherwise add instructions about how to do it.&lt;br /&gt;
&lt;br /&gt;
You can get the list of fields Moodle needs along with some other info you might need like the field types calling &#039;&#039;&#039;\core_search\document::get_default_fields_definition()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Add contents ===&lt;br /&gt;
&lt;br /&gt;
This method is executed when Moodle contents are being indexed in the search engine. Moodle iterates through all search areas extracting which contents should be indexed and assigns them a unique id based on the search area.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function add_document(array $doc, $fileindexing = false) {&lt;br /&gt;
    // Use curl or any other method or extension to push the document to your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;$doc&#039;&#039;&#039; will contain a document data with all required fields (+ maybe some optionals) and its contents will be already validated so a integer field will come with an integer value...&lt;br /&gt;
&#039;&#039;&#039;$fileindexing&#039;&#039;&#039; will be true if file indexing if files should be indexed. Will be false if your plugin does not support file indexing&lt;br /&gt;
&lt;br /&gt;
=== Retrieve contents ===&lt;br /&gt;
&lt;br /&gt;
This is the key method, as search engine plugins have a lot of flexibility here.&lt;br /&gt;
&lt;br /&gt;
You will get the search filters the user specified and the list of contexts the user can access and this function should return an array of \core_search\document objects.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function execute_query($filters, $usercontexts, $limit = 0) {&lt;br /&gt;
    // Prepare a query applyting all filters.&lt;br /&gt;
    // Include $usercontexts as a filter to contextid field.&lt;br /&gt;
    // Send a request to the server.&lt;br /&gt;
    // Iterate through results (respecting $limit or  \core_search\manager::MAX_RESULTS if empty).&lt;br /&gt;
    // Check user access, read https://docs.moodle.org/dev/Search_engines#Security for more info&lt;br /&gt;
    // Convert results to &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; type objects using &#039;&#039;&#039;\core_search\document::set_data_from_engine&#039;&#039;&#039;&lt;br /&gt;
    // Return an array of &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; objects.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Record counts ===&lt;br /&gt;
get_query_total_count() must be implemented to return the number of results that available for the most recent call to execute_query(). This is used to determine how many pages will be displayed in the paging bar.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_query_total_count() {&lt;br /&gt;
    // Return an approximate count of total records for the most recently completed execute_query().&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value can be an estimate. The search manager will ensure that if a page is requested that is beyond the last page of actual results, the user will seamlessly see the last available page.&lt;br /&gt;
&lt;br /&gt;
There are a number of ways to determine what value to return from get_query_total_count().  Note that if the method you choose requires you to process more than $limit valid documents, you still must only return $limit records from execute_query(). Some of the ways to do this are:&lt;br /&gt;
&lt;br /&gt;
==== Return how many possible there are ====&lt;br /&gt;
This would mean how many results we have processed and passed (using check_access()), plus any candidate results that are left. Alternately it is the total count of records for the query, minus the ones we have rejected so far. search_solr uses this method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User sees a full compliment of pages. If the pass-to-fail ration on check_access() is very high (and we generally expect it to be), then the number of pages should generally be accurate. This method will always error on the high side. It is possible that when clicking on a higher page there will be no results available, so the search manager will seamlessly show them the last actual page with actual results.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free or very cheap with some engines - no need to check access/process records beyond the current page. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Future page count is not perfect, so can result in page not being available when clicked (but the manager mitigates this).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Return the current count plus 1 ====&lt;br /&gt;
In this case, you would calculate all the records through $limit plus one. If the plus one exists, you would return that count, otherwise you would return the actual count.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User would only see up to the current page, plus one more, except when on the last page of results. Gmail search works similar to this in the way you can only navigate to the next page of results, not an arbitrary page.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Relatively cheap. Reasonable user experience.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; User can’t jump to an arbitrary page even if they know what page a particular result is on&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Calculate all results up to MAX_RESULTS ====&lt;br /&gt;
This would mean calculating the full set of results up to MAX_RESULTS, and returning the actual count of results.&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; The user will see the exact number of pages they should&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; cleanest user experience&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Very expensive, as you are calculating up to MAX_RESULTS results on every page, even if you are only showing the first page&lt;br /&gt;
&lt;br /&gt;
==== Just return MAX_RESULTS ====&lt;br /&gt;
&#039;&#039;&#039;User experience:&#039;&#039;&#039; User will always see 10 pages, except when they are on the last page of actual results. &lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039; Free&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039; Worst user experience &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Security ===&lt;br /&gt;
&lt;br /&gt;
It is crucial that this function is checking &#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; results and do not return results where the user do not have access. Moodle already performs part of the required security checkings, but search areas always have the last word and it should be respected.&lt;br /&gt;
&lt;br /&gt;
=== Delete contents ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete($areaid = false) {&lt;br /&gt;
    if ($areaid === false) {&lt;br /&gt;
        // Delete all your search engine index contents.&lt;br /&gt;
    } else {&lt;br /&gt;
        // Delete all your search engine contents where areaid = $areaid.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; will return \core_search\manager::ACCESS_DELETED if a document returned from the search engine is not available in Moodle any more, you can use this to clean up the search engine contents with some kind of &#039;&#039;&#039;\search_yourplugin\engine::delete_by_id&#039;&#039;&#039; method. You can look at &#039;&#039;&#039;search/engine/solr/classes/engine.php&#039;&#039;&#039; &#039;&#039;&#039;execute_query&#039;&#039;&#039; method for an example of this.&lt;br /&gt;
&lt;br /&gt;
=== Other abstract methods you need to overwrite ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function file_indexing_enabled() {&lt;br /&gt;
    // Defaults to false, overwrite it if your search engine supports file indexing.&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_server_ready() {&lt;br /&gt;
    // Check if your search engine is ready.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Other methods you might be interested in overwriting ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_installed() {&lt;br /&gt;
    // Check if the required PHP extensions you need to make the search engine work are installed.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function optimize() {&lt;br /&gt;
    // Optimize or defragment the index contents.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These methods are called while the indexing process is running and allow search engine to hook the indexing process.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_starting($fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_complete($numdocs = 0, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_starting($searcharea, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_complete($searcharea, $numdocs = 0, $fullindex = false) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adapting document formats to your search engine format ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document&#039;&#039;&#039; is the class that represents a document, depending on your search engine backend limitations or on how it stores time values you might be interested in overwriting this class in &#039;&#039;&#039;\search_yourplugin\document&#039;&#039;&#039;. The main functions you might be interested in overwriting are:&lt;br /&gt;
&lt;br /&gt;
==== Format date/time fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_time_for_engine($timestamp) {&lt;br /&gt;
    // Convert $timestamp to a string using the format used by your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_time_for_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Import date/time contents from the search engine ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function import_time_from_engine($time) {&lt;br /&gt;
    // Convert the string returned from the search engine as a date/time format to a timestamp (integer).&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::import_time_from_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Format string fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_string_for_engine() {&lt;br /&gt;
    // Limit the string length, convert iconv if your search engine only supports an specific charset...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_string_for_engine&#039;&#039;&#039; returns the string as it is.&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50031</id>
		<title>Search engines</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_engines&amp;diff=50031"/>
		<updated>2016-05-11T06:49:58Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Add limit to execute_query&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Search engine plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Search engines index big amounts of data in a structured way that allow users to query them and extract relevant data. There are many search engines with nice APIs to set data on and retrieve data from. We made Moodle&#039;s global search pluggable so different backends can be used, from a simple database table (ok for small sites but unusable for big sites) to open sourced systems like solr or elasticsearch (on top of Apache Lucene) or proprietary cloud based systems.&lt;br /&gt;
&lt;br /&gt;
== Terms ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Index&#039;&#039;&#039;: You know what it means, but in this page we use &#039;&#039;&#039;index&#039;&#039;&#039; as the data container in your search engine. It can be an instance in your search engine server or a database table name if you are writing a search engine for mongodb (just an example)&lt;br /&gt;
&#039;&#039;&#039;Document&#039;&#039;&#039;: A &amp;quot;searchable&amp;quot; unit of data like a database entry, a forum post... You can see it as one of the search results you might expect to get returned by a search engine.&lt;br /&gt;
&lt;br /&gt;
== Writing your own search engine plugin ==&lt;br /&gt;
&lt;br /&gt;
To write your own search engine you need to code methods to set, retrieve and delete data from your search engine. You will need to add a &#039;&#039;&#039;\search_yourplugin\engine&#039;&#039;&#039; class in &#039;&#039;&#039;search/engine/yourplugin/classes/engine.php&#039;&#039;&#039; extending &#039;&#039;&#039;\core_search\engine&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Search engine setup ===&lt;br /&gt;
&lt;br /&gt;
Your search engine needs to be prepared to index data, you can have a script for your plugin users so they can easily create the required structure the search engine. Otherwise add instructions about how to do it.&lt;br /&gt;
&lt;br /&gt;
You can get the list of fields Moodle needs along with some other info you might need like the field types calling &#039;&#039;&#039;\core_search\document::get_default_fields_definition()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Add contents ===&lt;br /&gt;
&lt;br /&gt;
This method is executed when Moodle contents are being indexed in the search engine. Moodle iterates through all search areas extracting which contents should be indexed and assigns them a unique id based on the search area.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function add_document(array $doc, $fileindexing = false) {&lt;br /&gt;
    // Use curl or any other method or extension to push the document to your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;$doc&#039;&#039;&#039; will contain a document data with all required fields (+ maybe some optionals) and its contents will be already validated so a integer field will come with an integer value...&lt;br /&gt;
&#039;&#039;&#039;$fileindexing&#039;&#039;&#039; will be true if file indexing if files should be indexed. Will be false if your plugin does not support file indexing&lt;br /&gt;
&lt;br /&gt;
=== Retrieve contents ===&lt;br /&gt;
&lt;br /&gt;
This is the key method, as search engine plugins have a lot of flexibility here.&lt;br /&gt;
&lt;br /&gt;
You will get the search filters the user specified and the list of contexts the user can access and this function should return an array of \core_search\document objects.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function execute_query($filters, $usercontexts, $limit = 0) {&lt;br /&gt;
    // Prepare a query applyting all filters.&lt;br /&gt;
    // Include $usercontexts as a filter to contextid field.&lt;br /&gt;
    // Send a request to the server.&lt;br /&gt;
    // Iterate through results (respecting $limit or  \core_search\manager::MAX_RESULTS if empty).&lt;br /&gt;
    // Check user access, read https://docs.moodle.org/dev/Search_engines#Security for more info&lt;br /&gt;
    // Convert results to &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; type objects using &#039;&#039;&#039;\core_search\document::set_data_from_engine&#039;&#039;&#039;&lt;br /&gt;
    // Return an array of &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; objects.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Security ====&lt;br /&gt;
&lt;br /&gt;
It is crucial that this function is checking &#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; results and do not return results where the user do not have access. Moodle already performs part of the required security checkings, but search areas always have the last word and it should be respected.&lt;br /&gt;
&lt;br /&gt;
=== Delete contents ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete($areaid = false) {&lt;br /&gt;
    if ($areaid === false) {&lt;br /&gt;
        // Delete all your search engine index contents.&lt;br /&gt;
    } else {&lt;br /&gt;
        // Delete all your search engine contents where areaid = $areaid.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document::check_access&#039;&#039;&#039; will return \core_search\manager::ACCESS_DELETED if a document returned from the search engine is not available in Moodle any more, you can use this to clean up the search engine contents with some kind of &#039;&#039;&#039;\search_yourplugin\engine::delete_by_id&#039;&#039;&#039; method. You can look at &#039;&#039;&#039;search/engine/solr/classes/engine.php&#039;&#039;&#039; &#039;&#039;&#039;execute_query&#039;&#039;&#039; method for an example of this.&lt;br /&gt;
&lt;br /&gt;
=== Other abstract methods you need to overwrite ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function file_indexing_enabled() {&lt;br /&gt;
    // Defaults to false, overwrite it if your search engine supports file indexing.&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_server_ready() {&lt;br /&gt;
    // Check if your search engine is ready.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Other methods you might be interested in overwriting ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function is_installed() {&lt;br /&gt;
    // Check if the required PHP extensions you need to make the search engine work are installed.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function optimize() {&lt;br /&gt;
    // Optimize or defragment the index contents.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These methods are called while the indexing process is running and allow search engine to hook the indexing process.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_starting($fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function index_complete($numdocs = 0, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_starting($searcharea, $fullindex = false) {&lt;br /&gt;
    // Nothing by default.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function area_index_complete($searcharea, $numdocs = 0, $fullindex = false) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adapting document formats to your search engine format ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;\core_search\document&#039;&#039;&#039; is the class that represents a document, depending on your search engine backend limitations or on how it stores time values you might be interested in overwriting this class in &#039;&#039;&#039;\search_yourplugin\document&#039;&#039;&#039;. The main functions you might be interested in overwriting are:&lt;br /&gt;
&lt;br /&gt;
==== Format date/time fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_time_for_engine($timestamp) {&lt;br /&gt;
    // Convert $timestamp to a string using the format used by your search engine.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_time_for_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Import date/time contents from the search engine ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function import_time_from_engine($time) {&lt;br /&gt;
    // Convert the string returned from the search engine as a date/time format to a timestamp (integer).&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::import_time_from_engine&#039;&#039;&#039; returns a timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
==== Format string fields ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function format_string_for_engine() {&lt;br /&gt;
    // Limit the string length, convert iconv if your search engine only supports an specific charset...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;&#039;\core_search\document::format_string_for_engine&#039;&#039;&#039; returns the string as it is.&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Common_unit_test_problems&amp;diff=49739</id>
		<title>Common unit test problems</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Common_unit_test_problems&amp;diff=49739"/>
		<updated>2016-03-30T13:29:36Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* core_phpunit_advanced_testcase::test_locale_reset */ Marking which one works for Debian&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== General Issues ==&lt;br /&gt;
&lt;br /&gt;
=== Segfaults on Oracle ===&lt;br /&gt;
&lt;br /&gt;
When I was running tests on oracle I was getting phpunit sefaulting.. --[[User:Dan Poltawski|Dan Poltawski]] 00:29, 16 May 2012 (WST)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Set statement_cache_size in php.ini: &amp;lt;code&amp;gt;oci8.statement_cache_size = 0&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
(More info: [https://bugs.php.net/bug.php?id=49803 PHP Bug #49803]).&lt;br /&gt;
&lt;br /&gt;
=== Execution ends with &amp;quot;zend_mm_heap corrupted&amp;quot; error ===&lt;br /&gt;
&lt;br /&gt;
This was happening with a standard XAMPP stack, running Moodle against Oracle.&lt;br /&gt;
&lt;br /&gt;
# Verify that you are not using multiple PHP opcode accelerators together (APC, Opcode...). Also try disabling all them (they really don&#039;t help much in a phpunit execution).&lt;br /&gt;
# Set output_buffering in php.ini to 65536 (from default 4096).&lt;br /&gt;
&lt;br /&gt;
=== Many random errors about &amp;quot;Warning: rmdir dataroot/cache/.../.... Directory not empty&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
Use a different $CFG-&amp;gt;phpunit_dataroot , completely apart from your current [LXMW]AMP stack, for example in windows &#039;C:\\Windows\\Temp\\dataroot&#039;. For some reason, unknown at the time of writing this, using any directory within the [LXMW]AMP stack lead to randomly busy/locked (aka, non deletable) files.&lt;br /&gt;
&lt;br /&gt;
== Specific failures ==&lt;br /&gt;
&lt;br /&gt;
=== dml_testcase::test_unique_index_collation_trouble===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Unique index is accent insensitive, this may cause problems for non-ascii languages. This is usually caused by accent insensitive default collation.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; These tests should pass if you use the &amp;lt;code&amp;gt;utf8_bin&amp;lt;/code&amp;gt; collation on your database for unit tests, but we recommend &amp;lt;code&amp;gt;utf8_unicode_ci&amp;lt;/code&amp;gt; for actual live Moodle sites. Work is being done to resolve this issue, read more about the [[Database collation issue]] for further info.&lt;br /&gt;
&lt;br /&gt;
=== dml_testcase::test_sql_binary_equal ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;SQL operator &amp;quot;=&amp;quot; is expected to be case sensitive Failed asserting that 1 matches expected 2.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; These tests should pass if you use the &amp;lt;code&amp;gt;utf8_bin&amp;lt;/code&amp;gt; collation on your database for unit tests, but we recommend &amp;lt;code&amp;gt;utf8_unicode_ci&amp;lt;/code&amp;gt; for actual live Moodle sites. Work is being done to resolve this issue, read more about the [[Database collation issue]] for further info.&lt;br /&gt;
&lt;br /&gt;
=== grading_manager_testcase::test_tokenize===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;A test using UTF-8 characters has failed. Consider updating PHP and PHP&#039;s PCRE or INTL extensions (MDL-30494) Failed asserting that false is true.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; These tests should pass if you use the &amp;lt;code&amp;gt;utf8_bin&amp;lt;/code&amp;gt; collation on your database for unit tests, but we recommend &amp;lt;code&amp;gt;utf8_unicode_ci&amp;lt;/code&amp;gt; for actual live Moodle sites. Work is being done to resolve this issue, read more about the [[Database collation issue]] for further info.&lt;br /&gt;
&lt;br /&gt;
=== moodlesimplepie_testcase::test_getfeed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Failed to load the sample RSS file. Please check your proxy settings in Moodle. %s&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Your moodle needs network connectivity, please check proxy settings.&lt;br /&gt;
&lt;br /&gt;
=== moodlesimplepie_testcase::test_redirect ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Failed asserting that &#039;cURL Error: Operation timed out after 2000 milliseconds with 0 bytes received&#039; is null.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Your moodle needs network connectivity, please check proxy settings&lt;br /&gt;
&lt;br /&gt;
=== collatorlib_testcase::test_asort_objects_by_method ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Collation aware sorting not supported, PHP extension &amp;quot;intl&amp;quot; is not available.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Install php intl extension.&lt;br /&gt;
&lt;br /&gt;
Note that, on Windows, this extension does not seem to be included in the installer, but you can get it by downloading the zip, and copying the right DLLs across. If you Google, there are more detailed instructions on Stack Exchange.&lt;br /&gt;
&lt;br /&gt;
===  moodlelib_testcase::test_fix_utf8 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Failed asserting that false is identical to &#039;aaabbb&#039;.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; This problem indicates buggy iconv. See [http://tracker.moodle.org/browse/MDL-33007] for discussion.  So this is a real problem, to be fixed in a future integration cycle&lt;br /&gt;
&lt;br /&gt;
=== filestoragelib_testcase::test_get_file_preview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Failed asserting that false is an instance of class &amp;quot;stored_file&amp;quot;.&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Unfortunately, the php GD extension is a requirement for this test (see MDL-36447).&lt;br /&gt;
&lt;br /&gt;
Go to your Moodle directory and type &amp;lt;code&amp;gt;php admin/tool/phpunit/cli/util.php --drop&amp;lt;/code&amp;gt;, then reinitialise PHPunit and run the test again.&lt;br /&gt;
&lt;br /&gt;
===core_ddl_testcase::test_row_size_limits===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Row size limit reached in MySQL using InnoDB, configure server to use innodb_file_format=Barracuda and innodb_file_per_table=1&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;Solution&amp;quot;&amp;quot; default InnoDB database tables in MySQL cannot handle more than 10 text columns, so innodb file format should be Barracuda (see MDL-46971)&lt;br /&gt;
&lt;br /&gt;
Running &amp;lt;code&amp;gt;php admin/cli/mysql_compressed_rows.php&amp;lt;/code&amp;gt; will solve the problem.&lt;br /&gt;
&lt;br /&gt;
===  core_phpunit_advanced_testcase::test_locale_reset ===&lt;br /&gt;
&lt;br /&gt;
Failed asserting that two strings are identical. Since 2.9, en_AU.UTF-8 locale is required to run phpunit.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Install the AU locale on your server if you want to run phpunit. Here are sample commands for installing the en_AU locale:&lt;br /&gt;
&lt;br /&gt;
* Debian/Centos:  &amp;quot;sudo localedef -v -c -i en_AU -f UTF-8 en_AU.UTF-8&amp;quot;&lt;br /&gt;
* Ubuntu: &amp;quot;sudo locale-gen en_AU.UTF-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== MSSQL failures with core_course_externallib_testcase===&lt;br /&gt;
&lt;br /&gt;
On the MSSQL database driver, some problems occur with  core_course_externallib_testcase, for example:&lt;br /&gt;
&lt;br /&gt;
core_course_externallib_testcase::test_duplicate_course&lt;br /&gt;
&lt;br /&gt;
unserialize(): Error at offset 24191 of 24192 bytes&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Ensure that the correct configuration is set in freedts.conf (from https://docs.moodle.org/26/en/FreeTDS), e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;text size = 20971520&lt;br /&gt;
client charset = UTF8&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PHP Warning: include_once(PHPUnit/Extensions/Database/Autoload.php): failed to open stream ===&lt;br /&gt;
&lt;br /&gt;
This occurs on Mac installs from experience. The reason this occurs is because DBUnit was not installed during the PHPUnit installation via PEAR.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; In terminal type: &amp;lt;code&amp;gt;sudo pear install -f phpunit/DbUnit&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== !!! Error reading from database !!!!! Unicode data in a Unicode-only collation or ntext data cannot be sent to clients using DB-Library (such as ISQL) or ODBC version 3.7 or earlier. ===&lt;br /&gt;
&lt;br /&gt;
If running unit tests under FreeTDS, remember that without a valid FREETDS environment variable, PHP won&#039;t be able to find the freetds.conf file and will default to version 5.0 which has poor support for unicode collations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039; Ensure a FREETDS environment variable is set prior to running the automated tests, which points to a valid path that contains a freetds.conf file. Also ensure the tds version element is set to a recent enough version (8.0)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution2&#039;&#039;&#039; Under windows you can put the freetds.conf file in the disk root directory where php is being executed (for example C:\freetds.conf). That directory is always looked. Also ensure the tds version element is set to a recent enough version (8.0)&lt;br /&gt;
&lt;br /&gt;
Note: Another potential solution may be to set TDSVER (http://freetds.schemamania.org/userguide/freetdsconf.htm)&lt;br /&gt;
&lt;br /&gt;
[[Category:Unit testing]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Errors running unit tests against Oracle, related with locales, decimal points and friends ===&lt;br /&gt;
&lt;br /&gt;
Surely your PHP CLI (the one executing phpunit) is not aware of the expected Oracle NLS configuration. To enforce it, set at least these 2 env variables in your system (globally or for the current user):&lt;br /&gt;
&lt;br /&gt;
- NLS_LANG to AMERICAN_AMERICA.AL32UTF8&lt;br /&gt;
- NLS_NUMERIC_CHARACTERS to ., (dot &amp;amp; comma)&lt;br /&gt;
&lt;br /&gt;
=== The test file &amp;quot;evolution.test&amp;quot; should not contain section named &amp;quot;[lots of content]&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
Likely that you&#039;ve got git setting autocrlf set to true and its altered the line endings. Set autocrlf to false in your git config and reset your checkout following [https://help.github.com/articles/dealing-with-line-endings/#refreshing-a-repository-after-changing-line-endings this guide].&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Eric_Merrill/gradereports&amp;diff=48272</id>
		<title>User:Eric Merrill/gradereports</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Eric_Merrill/gradereports&amp;diff=48272"/>
		<updated>2015-07-13T21:26:41Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== The problem ==&lt;br /&gt;
Right now, grades that need to be computed for display in reports are handled in multiple ways and in multiple locations. Reports get grade_grade::get_hiding_affected() and then each report uses it in a different way. Leading to different and inconsistent displays.&lt;br /&gt;
&lt;br /&gt;
== Proposal ==&lt;br /&gt;
I am proposing moving the computation layer deeper into the API, so that all computations are done at the same point in the code.&lt;br /&gt;
New class *grade_report_grade*. This would be a sub-class grade_grade, but would represent the computed grade for display. You would specify when it is &amp;quot;computed&amp;quot; what the re-computation behavior, and the user visibility behavior is.&lt;br /&gt;
&lt;br /&gt;
Possible methods:&lt;br /&gt;
* static compute_report_grades() - would recursively compute grade_report_grades based on a passed node, or user/courseid &lt;br /&gt;
* user_visible() - Should the item be visible to the user&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Eric_Merrill/gradereports&amp;diff=48271</id>
		<title>User:Eric Merrill/gradereports</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Eric_Merrill/gradereports&amp;diff=48271"/>
		<updated>2015-07-13T21:18:25Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Created page with &amp;quot;Test&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Test&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=46141</id>
		<title>Core APIs</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=46141"/>
		<updated>2014-08-11T18:12:13Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Fix typo in MUC update.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle has a number of core APIs that provide tools for Moodle scripts.&lt;br /&gt;
&lt;br /&gt;
They are essential when writing [[Plugins|Moodle plugins]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Most-used General APIs==&lt;br /&gt;
&lt;br /&gt;
These APIs are critical and will be used by nearly every Moodle plugin.&lt;br /&gt;
&lt;br /&gt;
=== Access API (access) ===&lt;br /&gt;
&lt;br /&gt;
The [[Access API]] gives you functions so you can determine what the current user is allowed to do, and it allows modules to extend Moodle with new capabilities.&lt;br /&gt;
&lt;br /&gt;
=== Data manipulation API (dml) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data manipulation API]] allows you to read/write to databases in a consistent and safe way.&lt;br /&gt;
&lt;br /&gt;
=== File API (files) ===&lt;br /&gt;
&lt;br /&gt;
The [[File API]] controls the storage of files in connection to various plugins.&lt;br /&gt;
&lt;br /&gt;
=== Form API (form) ===&lt;br /&gt;
&lt;br /&gt;
The [[Form API]] defines and handles user data via web forms.&lt;br /&gt;
&lt;br /&gt;
=== Logging API (log) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] API allows you to log events in Moodle, while [[Logging 2]] describes how logs are stored and retrieved.&lt;br /&gt;
&lt;br /&gt;
=== Navigation API (navigation) ===&lt;br /&gt;
&lt;br /&gt;
The [[Navigation API]] allows you to manipulate the navigation tree to add and remove items as you wish.&lt;br /&gt;
&lt;br /&gt;
=== Page API (page) ===&lt;br /&gt;
&lt;br /&gt;
The [[Page API]] is used to set up the current page, add JavaScript, and configure how things will be displayed to the user.&lt;br /&gt;
&lt;br /&gt;
=== Output API (output) ===&lt;br /&gt;
&lt;br /&gt;
The [[Output API]] is used to render the HTML for all parts of the page.&lt;br /&gt;
&lt;br /&gt;
=== String API (string) ===&lt;br /&gt;
&lt;br /&gt;
The [[String API]] is how you get language text strings to use in the user interface.   It handles any language translations that might be available.&lt;br /&gt;
&lt;br /&gt;
=== Upgrade API (upgrade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Upgrade API]] is how your module installs and upgrades itself, by keeping track of it&#039;s own version.&lt;br /&gt;
&lt;br /&gt;
=== Moodlelib API (core) ===&lt;br /&gt;
&lt;br /&gt;
The [[Moodlelib API]] is the central library file of miscellaneous general-purpose Moodle functions. Functions can over the handling of request parameters, configs, user preferences, time, login, mnet, plugins, strings and others. There are plenty of defined constants too.&lt;br /&gt;
&lt;br /&gt;
==Other General APIs==&lt;br /&gt;
&lt;br /&gt;
=== Admin settings (admin) ===&lt;br /&gt;
&lt;br /&gt;
The [[Admin settings]] API deals with providing configuration options for each plugin and Moodle core.&lt;br /&gt;
&lt;br /&gt;
=== Availability (availability) ===&lt;br /&gt;
&lt;br /&gt;
The [[Availability API]] controls access to activities and sections.&lt;br /&gt;
&lt;br /&gt;
=== Backup API (backup) ===&lt;br /&gt;
&lt;br /&gt;
The [[Backup API]] defines exactly how to convert course data into XML for backup purposes, and the [[Restore API]] describes how to convert it back the other way.&lt;br /&gt;
&lt;br /&gt;
=== Cache API (backup) ===&lt;br /&gt;
&lt;br /&gt;
The [[The Moodle Universal Cache (MUC)]] is the structure for storing cache data within Moodle. [[Cache_API]] explains some of what is needed to use a cache in your code.&lt;br /&gt;
&lt;br /&gt;
=== Calendar API (calendar) ===&lt;br /&gt;
&lt;br /&gt;
The [[Calendar API]] allows you to add and modify events in the calendar for user, groups, courses, or the whole site.&lt;br /&gt;
&lt;br /&gt;
=== Comment API (comment) ===&lt;br /&gt;
&lt;br /&gt;
The [[Comment API]] allows you to save and retrieve user comments, so that you can easily add commenting to any of your code.&lt;br /&gt;
&lt;br /&gt;
=== Data definition API (ddl) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data definition API]] is what you use to create, change and delete tables and fields in the database during upgrades.&lt;br /&gt;
&lt;br /&gt;
=== Enrolment API (enrol) ===&lt;br /&gt;
&lt;br /&gt;
The [[Enrolment API]] deals with course participants.&lt;br /&gt;
&lt;br /&gt;
=== Events API (event) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] allows to define &amp;quot;events&amp;quot; with payload data to be fired whenever you like, and it also allows you to define handlers to react to these events when they happen.  This is the recommended form of inter-plugin communication. This also forms the basis for logging in Moodle.&lt;br /&gt;
&lt;br /&gt;
=== External functions API (external) ===&lt;br /&gt;
&lt;br /&gt;
The [[External functions API]] allows you to create fully parametrised methods that can be accessed by external programs (such as [[Web services]]).&lt;br /&gt;
&lt;br /&gt;
=== Lock API (lock) ===&lt;br /&gt;
&lt;br /&gt;
The [[Lock API]] lets you synchronise processing between multiple requests, even for separate nodes in a cluster.&lt;br /&gt;
&lt;br /&gt;
=== Message API (message) ===&lt;br /&gt;
&lt;br /&gt;
The [[Message API]] lets you post messages to users.  They decide how they want to receive them.&lt;br /&gt;
&lt;br /&gt;
=== Media API (media) ===&lt;br /&gt;
&lt;br /&gt;
The [[Media embedding]] API can be used to embed media items such as audio, video, and Flash.&lt;br /&gt;
&lt;br /&gt;
=== Preference API (preference) ===&lt;br /&gt;
&lt;br /&gt;
The [[Preference API]] is a simple way to store and retrieve preferences for individual users.&lt;br /&gt;
&lt;br /&gt;
=== Portfolio API (portfolio) ===&lt;br /&gt;
&lt;br /&gt;
The [[Portfolio API]] allows you to add portfolio interfaces on your pages and allows users to package up data to send to their portfolios.&lt;br /&gt;
&lt;br /&gt;
=== Rating API (rating) ===&lt;br /&gt;
&lt;br /&gt;
The [[Rating API]] lets you create AJAX rating interfaces so that users can rate items in your plugin.  In an activity module, you may choose to aggregate ratings to form grades.&lt;br /&gt;
&lt;br /&gt;
=== RSS API (rss) ===&lt;br /&gt;
&lt;br /&gt;
The [[RSS API]] allows you to create secure RSS feeds of data in your module. &lt;br /&gt;
&lt;br /&gt;
=== Tag API (tag) ===&lt;br /&gt;
&lt;br /&gt;
The [[Tag API]] allows you to store tags (and a tag cloud) to items in your module.&lt;br /&gt;
&lt;br /&gt;
=== Task API (task) ===&lt;br /&gt;
&lt;br /&gt;
The [[Task API]] lets you run jobs in the background. Either once off, or on a regular schedule.&lt;br /&gt;
&lt;br /&gt;
=== Time API (time) ===&lt;br /&gt;
&lt;br /&gt;
The [[Time API]] takes care of translating and displaying times between users in the site.&lt;br /&gt;
&lt;br /&gt;
=== Testing API (test) ===&lt;br /&gt;
&lt;br /&gt;
The testing API contains the Unit test API ([[PHPUnit]]) and Acceptance test API ([[Acceptance testing]]). Ideally all new code should have unit tests written FIRST.&lt;br /&gt;
&lt;br /&gt;
=== User-related APIs (user) ===&lt;br /&gt;
&lt;br /&gt;
This is a rather informal grouping of miscellaneous [[User-related APIs]] relating to sorting and searching lists of users.&lt;br /&gt;
&lt;br /&gt;
=== Web services API (webservice) ===&lt;br /&gt;
&lt;br /&gt;
The [[Web services API]] allows you to expose particular functions (usually external functions) as web services.&lt;br /&gt;
&lt;br /&gt;
== Activity module APIs ==&lt;br /&gt;
&lt;br /&gt;
Activity modules are the most important plugin in Moodle.  There are several core APIs that service only Activity modules.&lt;br /&gt;
&lt;br /&gt;
=== Activity completion API (completion) ===&lt;br /&gt;
&lt;br /&gt;
The [[Activity completion API]] is to indicate to the system how activities are completed.&lt;br /&gt;
&lt;br /&gt;
=== Advanced grading API (grading) ===&lt;br /&gt;
&lt;br /&gt;
The [[Advanced grading API]] allows you to add more advanced grading interfaces (such as rubrics) that can produce simple grades for the gradebook.&lt;br /&gt;
&lt;br /&gt;
=== Conditional activities API (condition) - deprecated in 2.7 ===&lt;br /&gt;
&lt;br /&gt;
The deprecated [[Conditional activities API]] used to provide conditional access to modules and sections in Moodle 2.6 and below. It has been replaced by the [[Availability API]].&lt;br /&gt;
&lt;br /&gt;
=== Groups API (group) ===&lt;br /&gt;
&lt;br /&gt;
The [[Groups API]] allows you to check the current activity group mode and set the current group.&lt;br /&gt;
&lt;br /&gt;
=== Gradebook API (grade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Gradebook API]] allows you to read and write from the gradebook.  It also allows you to provide an interface for detailed grading information.&lt;br /&gt;
&lt;br /&gt;
=== Plagiarism API (plagiarism) ===&lt;br /&gt;
&lt;br /&gt;
The [[Plagiarism API]] allows your activity module to send files and data to external services to have them checked for plagiarism.&lt;br /&gt;
&lt;br /&gt;
=== Question API (question) ===&lt;br /&gt;
&lt;br /&gt;
The [[Question API]] (which can be divided into the Question bank API and the Question engine API), can be used by activities that want to use questions from the question bank.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Plugins]] - plugin types also have their own APIs&lt;br /&gt;
* [[Coding style]] - general information about writing PHP code for Moodle&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=46140</id>
		<title>Core APIs</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=46140"/>
		<updated>2014-08-11T18:11:26Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle has a number of core APIs that provide tools for Moodle scripts.&lt;br /&gt;
&lt;br /&gt;
They are essential when writing [[Plugins|Moodle plugins]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Most-used General APIs==&lt;br /&gt;
&lt;br /&gt;
These APIs are critical and will be used by nearly every Moodle plugin.&lt;br /&gt;
&lt;br /&gt;
=== Access API (access) ===&lt;br /&gt;
&lt;br /&gt;
The [[Access API]] gives you functions so you can determine what the current user is allowed to do, and it allows modules to extend Moodle with new capabilities.&lt;br /&gt;
&lt;br /&gt;
=== Data manipulation API (dml) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data manipulation API]] allows you to read/write to databases in a consistent and safe way.&lt;br /&gt;
&lt;br /&gt;
=== File API (files) ===&lt;br /&gt;
&lt;br /&gt;
The [[File API]] controls the storage of files in connection to various plugins.&lt;br /&gt;
&lt;br /&gt;
=== Form API (form) ===&lt;br /&gt;
&lt;br /&gt;
The [[Form API]] defines and handles user data via web forms.&lt;br /&gt;
&lt;br /&gt;
=== Logging API (log) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] API allows you to log events in Moodle, while [[Logging 2]] describes how logs are stored and retrieved.&lt;br /&gt;
&lt;br /&gt;
=== Navigation API (navigation) ===&lt;br /&gt;
&lt;br /&gt;
The [[Navigation API]] allows you to manipulate the navigation tree to add and remove items as you wish.&lt;br /&gt;
&lt;br /&gt;
=== Page API (page) ===&lt;br /&gt;
&lt;br /&gt;
The [[Page API]] is used to set up the current page, add JavaScript, and configure how things will be displayed to the user.&lt;br /&gt;
&lt;br /&gt;
=== Output API (output) ===&lt;br /&gt;
&lt;br /&gt;
The [[Output API]] is used to render the HTML for all parts of the page.&lt;br /&gt;
&lt;br /&gt;
=== String API (string) ===&lt;br /&gt;
&lt;br /&gt;
The [[String API]] is how you get language text strings to use in the user interface.   It handles any language translations that might be available.&lt;br /&gt;
&lt;br /&gt;
=== Upgrade API (upgrade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Upgrade API]] is how your module installs and upgrades itself, by keeping track of it&#039;s own version.&lt;br /&gt;
&lt;br /&gt;
=== Moodlelib API (core) ===&lt;br /&gt;
&lt;br /&gt;
The [[Moodlelib API]] is the central library file of miscellaneous general-purpose Moodle functions. Functions can over the handling of request parameters, configs, user preferences, time, login, mnet, plugins, strings and others. There are plenty of defined constants too.&lt;br /&gt;
&lt;br /&gt;
==Other General APIs==&lt;br /&gt;
&lt;br /&gt;
=== Admin settings (admin) ===&lt;br /&gt;
&lt;br /&gt;
The [[Admin settings]] API deals with providing configuration options for each plugin and Moodle core.&lt;br /&gt;
&lt;br /&gt;
=== Availability (availability) ===&lt;br /&gt;
&lt;br /&gt;
The [[Availability API]] controls access to activities and sections.&lt;br /&gt;
&lt;br /&gt;
=== Backup API (backup) ===&lt;br /&gt;
&lt;br /&gt;
The [[Backup API]] defines exactly how to convert course data into XML for backup purposes, and the [[Restore API]] describes how to convert it back the other way.&lt;br /&gt;
&lt;br /&gt;
=== Cache API (backup) ===&lt;br /&gt;
&lt;br /&gt;
The [[The_Moodle_Universal_Cache_(MUC)]] is the structure for storing cache data within Moodle. [[Cache_API]] explains some of what is needed to use a cache in your code.&lt;br /&gt;
&lt;br /&gt;
=== Calendar API (calendar) ===&lt;br /&gt;
&lt;br /&gt;
The [[Calendar API]] allows you to add and modify events in the calendar for user, groups, courses, or the whole site.&lt;br /&gt;
&lt;br /&gt;
=== Comment API (comment) ===&lt;br /&gt;
&lt;br /&gt;
The [[Comment API]] allows you to save and retrieve user comments, so that you can easily add commenting to any of your code.&lt;br /&gt;
&lt;br /&gt;
=== Data definition API (ddl) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data definition API]] is what you use to create, change and delete tables and fields in the database during upgrades.&lt;br /&gt;
&lt;br /&gt;
=== Enrolment API (enrol) ===&lt;br /&gt;
&lt;br /&gt;
The [[Enrolment API]] deals with course participants.&lt;br /&gt;
&lt;br /&gt;
=== Events API (event) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] allows to define &amp;quot;events&amp;quot; with payload data to be fired whenever you like, and it also allows you to define handlers to react to these events when they happen.  This is the recommended form of inter-plugin communication. This also forms the basis for logging in Moodle.&lt;br /&gt;
&lt;br /&gt;
=== External functions API (external) ===&lt;br /&gt;
&lt;br /&gt;
The [[External functions API]] allows you to create fully parametrised methods that can be accessed by external programs (such as [[Web services]]).&lt;br /&gt;
&lt;br /&gt;
=== Lock API (lock) ===&lt;br /&gt;
&lt;br /&gt;
The [[Lock API]] lets you synchronise processing between multiple requests, even for separate nodes in a cluster.&lt;br /&gt;
&lt;br /&gt;
=== Message API (message) ===&lt;br /&gt;
&lt;br /&gt;
The [[Message API]] lets you post messages to users.  They decide how they want to receive them.&lt;br /&gt;
&lt;br /&gt;
=== Media API (media) ===&lt;br /&gt;
&lt;br /&gt;
The [[Media embedding]] API can be used to embed media items such as audio, video, and Flash.&lt;br /&gt;
&lt;br /&gt;
=== Preference API (preference) ===&lt;br /&gt;
&lt;br /&gt;
The [[Preference API]] is a simple way to store and retrieve preferences for individual users.&lt;br /&gt;
&lt;br /&gt;
=== Portfolio API (portfolio) ===&lt;br /&gt;
&lt;br /&gt;
The [[Portfolio API]] allows you to add portfolio interfaces on your pages and allows users to package up data to send to their portfolios.&lt;br /&gt;
&lt;br /&gt;
=== Rating API (rating) ===&lt;br /&gt;
&lt;br /&gt;
The [[Rating API]] lets you create AJAX rating interfaces so that users can rate items in your plugin.  In an activity module, you may choose to aggregate ratings to form grades.&lt;br /&gt;
&lt;br /&gt;
=== RSS API (rss) ===&lt;br /&gt;
&lt;br /&gt;
The [[RSS API]] allows you to create secure RSS feeds of data in your module. &lt;br /&gt;
&lt;br /&gt;
=== Tag API (tag) ===&lt;br /&gt;
&lt;br /&gt;
The [[Tag API]] allows you to store tags (and a tag cloud) to items in your module.&lt;br /&gt;
&lt;br /&gt;
=== Task API (task) ===&lt;br /&gt;
&lt;br /&gt;
The [[Task API]] lets you run jobs in the background. Either once off, or on a regular schedule.&lt;br /&gt;
&lt;br /&gt;
=== Time API (time) ===&lt;br /&gt;
&lt;br /&gt;
The [[Time API]] takes care of translating and displaying times between users in the site.&lt;br /&gt;
&lt;br /&gt;
=== Testing API (test) ===&lt;br /&gt;
&lt;br /&gt;
The testing API contains the Unit test API ([[PHPUnit]]) and Acceptance test API ([[Acceptance testing]]). Ideally all new code should have unit tests written FIRST.&lt;br /&gt;
&lt;br /&gt;
=== User-related APIs (user) ===&lt;br /&gt;
&lt;br /&gt;
This is a rather informal grouping of miscellaneous [[User-related APIs]] relating to sorting and searching lists of users.&lt;br /&gt;
&lt;br /&gt;
=== Web services API (webservice) ===&lt;br /&gt;
&lt;br /&gt;
The [[Web services API]] allows you to expose particular functions (usually external functions) as web services.&lt;br /&gt;
&lt;br /&gt;
== Activity module APIs ==&lt;br /&gt;
&lt;br /&gt;
Activity modules are the most important plugin in Moodle.  There are several core APIs that service only Activity modules.&lt;br /&gt;
&lt;br /&gt;
=== Activity completion API (completion) ===&lt;br /&gt;
&lt;br /&gt;
The [[Activity completion API]] is to indicate to the system how activities are completed.&lt;br /&gt;
&lt;br /&gt;
=== Advanced grading API (grading) ===&lt;br /&gt;
&lt;br /&gt;
The [[Advanced grading API]] allows you to add more advanced grading interfaces (such as rubrics) that can produce simple grades for the gradebook.&lt;br /&gt;
&lt;br /&gt;
=== Conditional activities API (condition) - deprecated in 2.7 ===&lt;br /&gt;
&lt;br /&gt;
The deprecated [[Conditional activities API]] used to provide conditional access to modules and sections in Moodle 2.6 and below. It has been replaced by the [[Availability API]].&lt;br /&gt;
&lt;br /&gt;
=== Groups API (group) ===&lt;br /&gt;
&lt;br /&gt;
The [[Groups API]] allows you to check the current activity group mode and set the current group.&lt;br /&gt;
&lt;br /&gt;
=== Gradebook API (grade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Gradebook API]] allows you to read and write from the gradebook.  It also allows you to provide an interface for detailed grading information.&lt;br /&gt;
&lt;br /&gt;
=== Plagiarism API (plagiarism) ===&lt;br /&gt;
&lt;br /&gt;
The [[Plagiarism API]] allows your activity module to send files and data to external services to have them checked for plagiarism.&lt;br /&gt;
&lt;br /&gt;
=== Question API (question) ===&lt;br /&gt;
&lt;br /&gt;
The [[Question API]] (which can be divided into the Question bank API and the Question engine API), can be used by activities that want to use questions from the question bank.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Plugins]] - plugin types also have their own APIs&lt;br /&gt;
* [[Coding style]] - general information about writing PHP code for Moodle&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=46139</id>
		<title>Core APIs</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=46139"/>
		<updated>2014-08-11T18:08:03Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Update event and log API listings.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle has a number of core APIs that provide tools for Moodle scripts.&lt;br /&gt;
&lt;br /&gt;
They are essential when writing [[Plugins|Moodle plugins]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Most-used General APIs==&lt;br /&gt;
&lt;br /&gt;
These APIs are critical and will be used by nearly every Moodle plugin.&lt;br /&gt;
&lt;br /&gt;
=== Access API (access) ===&lt;br /&gt;
&lt;br /&gt;
The [[Access API]] gives you functions so you can determine what the current user is allowed to do, and it allows modules to extend Moodle with new capabilities.&lt;br /&gt;
&lt;br /&gt;
=== Data manipulation API (dml) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data manipulation API]] allows you to read/write to databases in a consistent and safe way.&lt;br /&gt;
&lt;br /&gt;
=== File API (files) ===&lt;br /&gt;
&lt;br /&gt;
The [[File API]] controls the storage of files in connection to various plugins.&lt;br /&gt;
&lt;br /&gt;
=== Form API (form) ===&lt;br /&gt;
&lt;br /&gt;
The [[Form API]] defines and handles user data via web forms.&lt;br /&gt;
&lt;br /&gt;
=== Logging API (log) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] API allows you to log events in Moodle, while [[Logging 2]] describes how logs are stored and retrieved.&lt;br /&gt;
&lt;br /&gt;
=== Navigation API (navigation) ===&lt;br /&gt;
&lt;br /&gt;
The [[Navigation API]] allows you to manipulate the navigation tree to add and remove items as you wish.&lt;br /&gt;
&lt;br /&gt;
=== Page API (page) ===&lt;br /&gt;
&lt;br /&gt;
The [[Page API]] is used to set up the current page, add JavaScript, and configure how things will be displayed to the user.&lt;br /&gt;
&lt;br /&gt;
=== Output API (output) ===&lt;br /&gt;
&lt;br /&gt;
The [[Output API]] is used to render the HTML for all parts of the page.&lt;br /&gt;
&lt;br /&gt;
=== String API (string) ===&lt;br /&gt;
&lt;br /&gt;
The [[String API]] is how you get language text strings to use in the user interface.   It handles any language translations that might be available.&lt;br /&gt;
&lt;br /&gt;
=== Upgrade API (upgrade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Upgrade API]] is how your module installs and upgrades itself, by keeping track of it&#039;s own version.&lt;br /&gt;
&lt;br /&gt;
=== Moodlelib API (core) ===&lt;br /&gt;
&lt;br /&gt;
The [[Moodlelib API]] is the central library file of miscellaneous general-purpose Moodle functions. Functions can over the handling of request parameters, configs, user preferences, time, login, mnet, plugins, strings and others. There are plenty of defined constants too.&lt;br /&gt;
&lt;br /&gt;
==Other General APIs==&lt;br /&gt;
&lt;br /&gt;
=== Admin settings (admin) ===&lt;br /&gt;
&lt;br /&gt;
The [[Admin settings]] API deals with providing configuration options for each plugin and Moodle core.&lt;br /&gt;
&lt;br /&gt;
=== Availability (availability) ===&lt;br /&gt;
&lt;br /&gt;
The [[Availability API]] controls access to activities and sections.&lt;br /&gt;
&lt;br /&gt;
=== Backup API (backup) ===&lt;br /&gt;
&lt;br /&gt;
The [[Backup API]] defines exactly how to convert course data into XML for backup purposes, and the [[Restore API]] describes how to convert it back the other way.&lt;br /&gt;
&lt;br /&gt;
=== Calendar API (calendar) ===&lt;br /&gt;
&lt;br /&gt;
The [[Calendar API]] allows you to add and modify events in the calendar for user, groups, courses, or the whole site.&lt;br /&gt;
&lt;br /&gt;
=== Comment API (comment) ===&lt;br /&gt;
&lt;br /&gt;
The [[Comment API]] allows you to save and retrieve user comments, so that you can easily add commenting to any of your code.&lt;br /&gt;
&lt;br /&gt;
=== Data definition API (ddl) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data definition API]] is what you use to create, change and delete tables and fields in the database during upgrades.&lt;br /&gt;
&lt;br /&gt;
=== Enrolment API (enrol) ===&lt;br /&gt;
&lt;br /&gt;
The [[Enrolment API]] deals with course participants.&lt;br /&gt;
&lt;br /&gt;
=== Events API (event) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] allows to define &amp;quot;events&amp;quot; with payload data to be fired whenever you like, and it also allows you to define handlers to react to these events when they happen.  This is the recommended form of inter-plugin communication. This also forms the basis for logging in Moodle.&lt;br /&gt;
&lt;br /&gt;
=== External functions API (external) ===&lt;br /&gt;
&lt;br /&gt;
The [[External functions API]] allows you to create fully parametrised methods that can be accessed by external programs (such as [[Web services]]).&lt;br /&gt;
&lt;br /&gt;
=== Lock API (lock) ===&lt;br /&gt;
&lt;br /&gt;
The [[Lock API]] lets you synchronise processing between multiple requests, even for separate nodes in a cluster.&lt;br /&gt;
&lt;br /&gt;
=== Message API (message) ===&lt;br /&gt;
&lt;br /&gt;
The [[Message API]] lets you post messages to users.  They decide how they want to receive them.&lt;br /&gt;
&lt;br /&gt;
=== Media API (media) ===&lt;br /&gt;
&lt;br /&gt;
The [[Media embedding]] API can be used to embed media items such as audio, video, and Flash.&lt;br /&gt;
&lt;br /&gt;
=== Preference API (preference) ===&lt;br /&gt;
&lt;br /&gt;
The [[Preference API]] is a simple way to store and retrieve preferences for individual users.&lt;br /&gt;
&lt;br /&gt;
=== Portfolio API (portfolio) ===&lt;br /&gt;
&lt;br /&gt;
The [[Portfolio API]] allows you to add portfolio interfaces on your pages and allows users to package up data to send to their portfolios.&lt;br /&gt;
&lt;br /&gt;
=== Rating API (rating) ===&lt;br /&gt;
&lt;br /&gt;
The [[Rating API]] lets you create AJAX rating interfaces so that users can rate items in your plugin.  In an activity module, you may choose to aggregate ratings to form grades.&lt;br /&gt;
&lt;br /&gt;
=== RSS API (rss) ===&lt;br /&gt;
&lt;br /&gt;
The [[RSS API]] allows you to create secure RSS feeds of data in your module. &lt;br /&gt;
&lt;br /&gt;
=== Tag API (tag) ===&lt;br /&gt;
&lt;br /&gt;
The [[Tag API]] allows you to store tags (and a tag cloud) to items in your module.&lt;br /&gt;
&lt;br /&gt;
=== Task API (task) ===&lt;br /&gt;
&lt;br /&gt;
The [[Task API]] lets you run jobs in the background. Either once off, or on a regular schedule.&lt;br /&gt;
&lt;br /&gt;
=== Time API (time) ===&lt;br /&gt;
&lt;br /&gt;
The [[Time API]] takes care of translating and displaying times between users in the site.&lt;br /&gt;
&lt;br /&gt;
=== Testing API (test) ===&lt;br /&gt;
&lt;br /&gt;
The testing API contains the Unit test API ([[PHPUnit]]) and Acceptance test API ([[Acceptance testing]]). Ideally all new code should have unit tests written FIRST.&lt;br /&gt;
&lt;br /&gt;
=== User-related APIs (user) ===&lt;br /&gt;
&lt;br /&gt;
This is a rather informal grouping of miscellaneous [[User-related APIs]] relating to sorting and searching lists of users.&lt;br /&gt;
&lt;br /&gt;
=== Web services API (webservice) ===&lt;br /&gt;
&lt;br /&gt;
The [[Web services API]] allows you to expose particular functions (usually external functions) as web services.&lt;br /&gt;
&lt;br /&gt;
== Activity module APIs ==&lt;br /&gt;
&lt;br /&gt;
Activity modules are the most important plugin in Moodle.  There are several core APIs that service only Activity modules.&lt;br /&gt;
&lt;br /&gt;
=== Activity completion API (completion) ===&lt;br /&gt;
&lt;br /&gt;
The [[Activity completion API]] is to indicate to the system how activities are completed.&lt;br /&gt;
&lt;br /&gt;
=== Advanced grading API (grading) ===&lt;br /&gt;
&lt;br /&gt;
The [[Advanced grading API]] allows you to add more advanced grading interfaces (such as rubrics) that can produce simple grades for the gradebook.&lt;br /&gt;
&lt;br /&gt;
=== Conditional activities API (condition) - deprecated in 2.7 ===&lt;br /&gt;
&lt;br /&gt;
The deprecated [[Conditional activities API]] used to provide conditional access to modules and sections in Moodle 2.6 and below. It has been replaced by the [[Availability API]].&lt;br /&gt;
&lt;br /&gt;
=== Groups API (group) ===&lt;br /&gt;
&lt;br /&gt;
The [[Groups API]] allows you to check the current activity group mode and set the current group.&lt;br /&gt;
&lt;br /&gt;
=== Gradebook API (grade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Gradebook API]] allows you to read and write from the gradebook.  It also allows you to provide an interface for detailed grading information.&lt;br /&gt;
&lt;br /&gt;
=== Plagiarism API (plagiarism) ===&lt;br /&gt;
&lt;br /&gt;
The [[Plagiarism API]] allows your activity module to send files and data to external services to have them checked for plagiarism.&lt;br /&gt;
&lt;br /&gt;
=== Question API (question) ===&lt;br /&gt;
&lt;br /&gt;
The [[Question API]] (which can be divided into the Question bank API and the Question engine API), can be used by activities that want to use questions from the question bank.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Plugins]] - plugin types also have their own APIs&lt;br /&gt;
* [[Coding style]] - general information about writing PHP code for Moodle&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Old_Events_API&amp;diff=46138</id>
		<title>Old Events API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Old_Events_API&amp;diff=46138"/>
		<updated>2014-08-11T18:04:26Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Marking page as obsolete.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{obsolete}}&lt;br /&gt;
&#039;&#039;&#039;See [[Event 2]] for the new Events API.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The Events API is a core system in Moodle to allow communication between modules.  &lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;&#039;event&#039;&#039;&#039; is when something &amp;quot;interesting&amp;quot; happens in Moodle that is worth alerting the system about.&lt;br /&gt;
&lt;br /&gt;
Any Moodle modules can &#039;&#039;&#039;trigger&#039;&#039;&#039; new events (with attached data), and other modules can elect to &#039;&#039;&#039;handle&#039;&#039;&#039; those events with custom functions that operate on the given data.&lt;br /&gt;
&lt;br /&gt;
==Example==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example of how events are used to implement enrolment in Moodle 2.0. In the enrolment system, lib/enrollib.php will generate a &#039;user_enrolled&#039; event upon completion of an enrolment of a user.&lt;br /&gt;
&lt;br /&gt;
===Triggering an event===&lt;br /&gt;
&lt;br /&gt;
When a user is enrolled, a &#039;user_enrolled&#039; event is built and sent out by the enrol API. In this example let&#039;s pretend someone was just enrolled.&lt;br /&gt;
&lt;br /&gt;
The enrol lib (could even be a module thats creating this event) needs to create an object with the data that this event needs. This may vary completely for different types of events, it&#039;s just a data object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ue = new stdClass();&lt;br /&gt;
$ue-&amp;gt;enrolid      = $instance-&amp;gt;id;&lt;br /&gt;
$ue-&amp;gt;status       = is_null($status) ? ENROL_USER_ACTIVE : $status;&lt;br /&gt;
$ue-&amp;gt;userid       = $userid;&lt;br /&gt;
$ue-&amp;gt;timestart    = $timestart;&lt;br /&gt;
$ue-&amp;gt;timeend      = $timeend;&lt;br /&gt;
$ue-&amp;gt;modifierid   = $USER-&amp;gt;id;&lt;br /&gt;
...&lt;br /&gt;
$ue-&amp;gt;courseid  = $courseid;&lt;br /&gt;
$ue-&amp;gt;enrol     = $name;&lt;br /&gt;
&lt;br /&gt;
events_trigger(&#039;user_enrolled&#039;, $ue);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then we post the object as an event and forget about it:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
events_trigger(&#039;user_enrolled&#039;, $eventdata);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We have now broadcast an event that we think other parts of the system might need to know about.&lt;br /&gt;
&lt;br /&gt;
===Handling an event===&lt;br /&gt;
&lt;br /&gt;
Modules or core code can define an events.php in under its */db directory which defines events they want to be notified about, and describes which of their functions or class methods should be notified.  For this case, there is this definition of a handler in mod/forum/db/events.php.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$handlers = array (&lt;br /&gt;
    &#039;user_enrolled&#039; =&amp;gt; array (&lt;br /&gt;
        &#039;handlerfile&#039;      =&amp;gt; &#039;/mod/forum/lib.php&#039;,&lt;br /&gt;
        &#039;handlerfunction&#039;  =&amp;gt; &#039;forum_user_enrolled&#039;,&lt;br /&gt;
        &#039;schedule&#039;         =&amp;gt; &#039;instant&#039;,&lt;br /&gt;
        &#039;internal&#039;         =&amp;gt; 1,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    &#039;user_unenrolled&#039; =&amp;gt; array (&lt;br /&gt;
        &#039;handlerfile&#039;      =&amp;gt; &#039;/mod/forum/lib.php&#039;,&lt;br /&gt;
        &#039;handlerfunction&#039;  =&amp;gt; &#039;forum_user_unenrolled&#039;,&lt;br /&gt;
        &#039;schedule&#039;         =&amp;gt; &#039;instant&#039;,&lt;br /&gt;
        &#039;internal&#039;         =&amp;gt; 1,&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These events.php files are parsed during install / upgrade and stored in a simple database table.&lt;br /&gt;
&lt;br /&gt;
Now, when a &#039;&#039;&#039;user_enrolled&#039;&#039;&#039; event happens, all the registered handlers functions for that event will be called something like this (but with more error handling):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
          include_once($CFG-&amp;gt;dirroot.$handlers[&#039;user_enrolled&#039;][&#039;handlerfile&#039;]);&lt;br /&gt;
          call_user_func($handlers[&#039;user_enrolled&#039;][&#039;handlerfunction&#039;], $eventdata);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Any code can hook into any events this way.&lt;br /&gt;
&lt;br /&gt;
The handler function accepts one parameter (the event data object) and should return a boolean.  Returning false indicates that there was an error and the event will be left in the event queue.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    function forum_user_unenrolled($eventdata) {&lt;br /&gt;
        // handle event &lt;br /&gt;
        // ...&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
note: a lib/db/events.php exists as legacy and no events should be added here. Core API is not supposed to define events for consumption. Only modules and core components (non-api) should define events for consumption/handling.&lt;br /&gt;
&lt;br /&gt;
==Database structure==&lt;br /&gt;
&lt;br /&gt;
There are 3 core tables for events. Note that if a handler is queued, and yet to be processed or processing failed, then all subsequent calls on that handler must be queued.&lt;br /&gt;
&lt;br /&gt;
===events_handlers===&lt;br /&gt;
&lt;br /&gt;
This table is for storing which components requests what type of event, and the location of the responsible handler functions.&lt;br /&gt;
&lt;br /&gt;
These entries are created by parsing events.php files in all the modules, and can be rebuilt any time (during an upgrade, say).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Field&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Type&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|id&lt;br /&gt;
|int(10)&lt;br /&gt;
|auto increment identifier&lt;br /&gt;
|-&lt;br /&gt;
|eventname&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|name of the event, e.g. &#039;message_send&#039;&lt;br /&gt;
|-&lt;br /&gt;
|handlermodule&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|e.g. moodle, mod/forum, block/rss_client&lt;br /&gt;
|-&lt;br /&gt;
|handlerfile&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|path to the file of the function, eg /lib/messagelib.php&lt;br /&gt;
|-&lt;br /&gt;
|handlerfunction&lt;br /&gt;
|text&lt;br /&gt;
|serialized string or array describing function, suitable to be passed to &#039;&#039;&#039;call_user_func()&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|schedule 	&lt;br /&gt;
|varchar(255) 	&lt;br /&gt;
|&#039;cron&#039; or &#039;instant&#039;.&lt;br /&gt;
|-&lt;br /&gt;
|status&lt;br /&gt;
|int(10)&lt;br /&gt;
|number of failed attempts to process this handler&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===events_queue===&lt;br /&gt;
&lt;br /&gt;
This table is for storing queued events. It stores only one copy of the eventdata here, and entries from this table are being references by the events_queue_handlers table.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Field&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Type&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|id&lt;br /&gt;
|int(10)&lt;br /&gt;
|auto increment identifier&lt;br /&gt;
|-&lt;br /&gt;
|eventdata 	&lt;br /&gt;
|longtext 	&lt;br /&gt;
|serialized version of the data object passed to the event handler.&lt;br /&gt;
|-&lt;br /&gt;
|stackdump&lt;br /&gt;
|text&lt;br /&gt;
|serialized debug_backtrace showing where the event was fired from&lt;br /&gt;
|-&lt;br /&gt;
|userid&lt;br /&gt;
|int(10)&lt;br /&gt;
|$USER-&amp;gt;id when the event was fired&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10) 	&lt;br /&gt;
|time stamp of the first time this was added&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===events_queue_handlers===&lt;br /&gt;
&lt;br /&gt;
This is the list of queued handlers for processing. The event object is retrieved from the events_queue table. When no further reference is made to the events_queue table, the corresponding entry in the events_queue table should be deleted. Entry should get deleted (?) after a successful event processing by the specified handler.  The status field keeps track of failures, after it gets to a certain number (eg 10?) it should trigger an &amp;quot;event failed&amp;quot; event (that could result in admin being emailed etc, or perhaps even the originating module taking care of it or rolling something back etc).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Field&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Type&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039;&lt;br /&gt;
|- &lt;br /&gt;
|id&lt;br /&gt;
|int(10)&lt;br /&gt;
|auto increment identifier&lt;br /&gt;
|-&lt;br /&gt;
|queuedeventid&lt;br /&gt;
|int(10)&lt;br /&gt;
|foreign key id corresponding to the id of the event_queues table&lt;br /&gt;
|-&lt;br /&gt;
|handlerid&lt;br /&gt;
|int(10)&lt;br /&gt;
|foreign key id corresponding to the id of the event_handlers table&lt;br /&gt;
|-&lt;br /&gt;
|status&lt;br /&gt;
|int(10)&lt;br /&gt;
|number of failed attempts to process this handler&lt;br /&gt;
|-&lt;br /&gt;
|errormessage&lt;br /&gt;
|text&lt;br /&gt;
|if an error happened last time we tried to process this event, record it here.&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|time stamp of the last attempt to run this from the queue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Standards for naming events==&lt;br /&gt;
&lt;br /&gt;
All event names should follow a consistent naming pattern, such as componentname_noun_verb  (See [[Frankenstyle]] about the component name)&lt;br /&gt;
&lt;br /&gt;
If the event is being fired after the action has taken place (as in most cases) then use the past tense for the verb (created / deleted / updated / sent).&lt;br /&gt;
&lt;br /&gt;
If the event &#039;&#039;&#039;is&#039;&#039;&#039; the action, then use the present tense (create / delete / update / send).&lt;br /&gt;
&lt;br /&gt;
==Events which exist==&lt;br /&gt;
&lt;br /&gt;
When we add new events to core we should always add them here too.&lt;br /&gt;
&lt;br /&gt;
Under each event, list the data sent as part of the event.&lt;br /&gt;
&lt;br /&gt;
===Users===&lt;br /&gt;
* user_created&lt;br /&gt;
** full new record from &#039;user&#039; table&lt;br /&gt;
* user_deleted&lt;br /&gt;
** record from &#039;user&#039; table before marked as deleted&lt;br /&gt;
* user_updated&lt;br /&gt;
** full new record from &#039;user&#039; table&lt;br /&gt;
* user_enrolled&lt;br /&gt;
** full user enrolment record (TBC)&lt;br /&gt;
* user_logout&lt;br /&gt;
** params TBC&lt;br /&gt;
* user_loggedin&lt;br /&gt;
** full record from user table&lt;br /&gt;
* user_unenrol_modified&lt;br /&gt;
** full user enrolment record (TBC)&lt;br /&gt;
* user_unenrolled&lt;br /&gt;
** full user enrolment record (TBC)&lt;br /&gt;
&lt;br /&gt;
===Roles===&lt;br /&gt;
* role_assigned&lt;br /&gt;
** full new record from &#039;role_assignments&#039; table&lt;br /&gt;
* role_unassigned&lt;br /&gt;
** record from &#039;role_assignments&#039;, course context only&lt;br /&gt;
&lt;br /&gt;
===Courses===&lt;br /&gt;
* course_created&lt;br /&gt;
** full course record&lt;br /&gt;
* course_updated&lt;br /&gt;
** full course record&lt;br /&gt;
* course_deleted&lt;br /&gt;
** full course record&lt;br /&gt;
* course_category_deleted&lt;br /&gt;
** full category record&lt;br /&gt;
* course_content_removed&lt;br /&gt;
** full course record&lt;br /&gt;
* course_restored (2.5)&lt;br /&gt;
&lt;br /&gt;
===Groups===&lt;br /&gt;
* groups_member_added&lt;br /&gt;
** groupid, userid&lt;br /&gt;
* groups_member_removed&lt;br /&gt;
** groupid, userid&lt;br /&gt;
* groups_group_created&lt;br /&gt;
** id, courseid, name, description, timecreated, timemodified, picture&lt;br /&gt;
* groups_group_updated&lt;br /&gt;
** id, courseid, name, description, timecreated, timemodified, picture&lt;br /&gt;
* groups_group_deleted&lt;br /&gt;
** id, courseid, name, description, timecreated, timemodified, picture&lt;br /&gt;
* groups_grouping_created&lt;br /&gt;
** id, courseid, name, timecreated, timemodified&lt;br /&gt;
* groups_grouping_updated&lt;br /&gt;
** id, courseid, name, timecreated, timemodified&lt;br /&gt;
* groups_grouping_deleted&lt;br /&gt;
** id, courseid, name, timecreated, timemodified&lt;br /&gt;
* groups_members_removed &#039;&#039;(user deleted from all groups in a course)&#039;&#039;&lt;br /&gt;
** courseid, userid&lt;br /&gt;
* groups_groupings_groups_removed &#039;&#039;(remove all groups from all groupings in a course)&#039;&#039;&lt;br /&gt;
** courseid (as plain integer, not object)&lt;br /&gt;
* groups_groups_deleted &#039;&#039;(delete all groups in a course)&#039;&#039;&lt;br /&gt;
** courseid (as plain integer, not object)&lt;br /&gt;
* groups_groupings_deleted &#039;&#039;(delete all groupings in a course)&#039;&#039;&lt;br /&gt;
** courseid (as plain integer, not object)&lt;br /&gt;
&lt;br /&gt;
===Cohorts===&lt;br /&gt;
* cohort_added&lt;br /&gt;
** full cohort record (TBC)&lt;br /&gt;
* cohort_deleted&lt;br /&gt;
** full cohort record (TBC)&lt;br /&gt;
* cohort_member_added&lt;br /&gt;
** cohort ID, user ID (TBC)&lt;br /&gt;
* cohort_member_removed&lt;br /&gt;
** cohort ID, user ID (TBC)&lt;br /&gt;
* cohort_updated&lt;br /&gt;
** full cohort record (TBC)&lt;br /&gt;
&lt;br /&gt;
===Messaging===&lt;br /&gt;
* message_send&lt;br /&gt;
** component = &#039;mod/forum&#039;: path in Moodle&lt;br /&gt;
** name = &#039;posts&#039;: type of message from that module (as module defines it)&lt;br /&gt;
** userfrom = $userfrom: a user object to send from&lt;br /&gt;
** userto = $userto: a user object to send to&lt;br /&gt;
** subject = &#039;subject line&#039;: a short text line&lt;br /&gt;
** fullmessage = &#039;full plain text&#039;: raw text as entered by user&lt;br /&gt;
** fullmessageformat = FORMAT_PLAIN|FORMAT_HTML|FORMAT_MOODLE|FORMAT_MARKDOWN: the format of this text&lt;br /&gt;
** fullmessagehtml = &#039;long html text&#039;; html rendered version (optional)&lt;br /&gt;
** smallmessage = &#039;short text&#039;: useful for plugins like sms or twitter (optional)&lt;br /&gt;
&lt;br /&gt;
===Portfolio===&lt;br /&gt;
* portfolio_send&lt;br /&gt;
** id : recordid in portfolio_tempdata table, used for itemid in file storage&lt;br /&gt;
&lt;br /&gt;
===Other===&lt;br /&gt;
* assessable_file_uploaded&lt;br /&gt;
* assessable_files_done&lt;br /&gt;
* mod_created&lt;br /&gt;
* mod_deleted&lt;br /&gt;
* mod_updated&lt;br /&gt;
* quiz_attempt_started&lt;br /&gt;
* quiz_attempt_submitted - &#039;&#039;Note: these two quiz events changed in Moodle 2.1&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==Events wishlist==&lt;br /&gt;
&lt;br /&gt;
List of events which it would be nice to have.  Please add to this list if what you want is not shown here.&lt;br /&gt;
&lt;br /&gt;
* mform_print_form -- this for all types of form e.g. admin settings, user profile, module updating, + some sort of standard way of discriminating between them e.g. if ($form-&amp;gt;name == &#039;user_profile&#039;) {}. This would be better triggered at the end of the form generation process so that new bits can be inserted at any point, or existing bits could be removed.&lt;br /&gt;
* module_installed &#039;&#039;(= mod_created in v2?)&#039;&#039;&lt;br /&gt;
* module_removed &#039;&#039;(= mod_deleted in v2?)&#039;&#039;&lt;br /&gt;
* grade_update &#039;&#039;(= quiz_attempt_processed in v2?)&#039;&#039;&lt;br /&gt;
* assignment_submitted&lt;br /&gt;
* course - editing turned on or off&lt;br /&gt;
* course completed&lt;br /&gt;
* course module completion state changed (completed / not completed)&lt;br /&gt;
* course was reset&lt;br /&gt;
* Some will_be ... events that would allow another plugin to react before these things happen:&lt;br /&gt;
** course_will_be_restored (with info about course being restored and the target category)&lt;br /&gt;
** course_category_will_be_moved (with info about category being moved and target category)&lt;br /&gt;
** course_will_be_moved (with course info and target category info)&lt;br /&gt;
&lt;br /&gt;
Provide event trigger hooks for the modules, similar to what is done for the cron service which checks the LOCAL directory for a cron file. It is already possible to define an event trigger but the core/module code must be modified to actually make use of it.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=69103 Original General Developer Forum thread discussing this proposal]. &lt;br /&gt;
* [[Messaging_2.0]]&lt;br /&gt;
* [[Event_2]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Coding guidelines|Events]]&lt;br /&gt;
[[Category:Grades]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Logging_API&amp;diff=46137</id>
		<title>Logging API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Logging_API&amp;diff=46137"/>
		<updated>2014-08-11T18:01:55Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Point people to the correct pages&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{obsolete}}&lt;br /&gt;
See [[Logging 2]] and [[Event 2]] for more up-to-date information. --[[User:Eric Merrill|Eric Merrill]] 17:00, 11 August 2014 (UTC)&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The Logging API allows you to add new entries to the Moodle log and define how they get displayed in reports. Logging is an extremely important and often neglected aspect of Moodle Plugin development. All important actions such as viewing, deleting, editing etc should be logged. &lt;br /&gt;
&lt;br /&gt;
==File locations==&lt;br /&gt;
&lt;br /&gt;
The Log API is all in lib/datalib.php and is automatically included for you during the page setup.&lt;br /&gt;
&lt;br /&gt;
==Functions and Examples==&lt;br /&gt;
&lt;br /&gt;
Following are the functions that constitute the basic log API for Moodle.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 add_to_log($courseid, $module, $action, $url=&#039;&#039;, $info=&#039;&#039;, $cm=0, $user=0)&lt;br /&gt;
 user_accesstime_log($courseid=0)&lt;br /&gt;
 get_logs($select, array $params=null, $order=&#039;l.time DESC&#039;, $limitfrom=&#039;&#039;, $limitnum=&#039;&#039;, &amp;amp;$totalcount)&lt;br /&gt;
 get_logs_usercourse($userid, $courseid, $coursestart)&lt;br /&gt;
 get_logs_userday($userid, $courseid, $daystart)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The basic working of these functions can be categorized in two categories:-&lt;br /&gt;
# Adding data to logs&lt;br /&gt;
# Fetching data from logs&lt;br /&gt;
Let us take a deeper look into both of these:-&lt;br /&gt;
&lt;br /&gt;
===Adding data to Logs===&lt;br /&gt;
In Moodle basically we have two functions that take care of adding data to the logs table :-&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 add_to_log($courseid, $module, $action, $url=&#039;&#039;, $info=&#039;&#039;, $cm=0, $user=0)&lt;br /&gt;
 user_accesstime_log($courseid=0)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== add_to_log() ====&lt;br /&gt;
This function is basic core function which you should use to add all your logs to the Moodle Log table. While using this function to add data to logs, please remember that this function is more &amp;quot;Action&amp;quot; oriented rather than being based on &amp;quot;webserver hits&amp;quot;, i.e the events should be logged with enough information that this data can be effectively used to regenerate the entire flow of events and action associated with any specific user.&lt;br /&gt;
This is a simple example:-&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 add_to_log($course-&amp;gt;id, &#039;role&#039;, &#039;assign&#039;, &#039;admin/roles/assign.php?contextid=&#039;.$context-&amp;gt;id.&#039;&amp;amp;roleid=&#039;.$roleid, $rolename, &#039;&#039;, $USER-&amp;gt;id);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== user_accesstime_log() ====&lt;br /&gt;
user_accesstime_log() is used to record the last access time for courses and site. This is basically called when a user vists the site or a course page.&lt;br /&gt;
A simple example can be :-&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $courseid = $course-&amp;gt;id;&lt;br /&gt;
 user_accesstime_log($courseid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Fetching Logs===&lt;br /&gt;
we have three functions that can take care of all your needs to interact with the existing log data in the database. Following function can be used to effectively retrieve log data as per your needs:-&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 get_logs($select, array $params=null, $order=&#039;l.time DESC&#039;, $limitfrom=&#039;&#039;, $limitnum=&#039;&#039;, &amp;amp;$totalcount)&lt;br /&gt;
 get_logs_usercourse($userid, $courseid, $coursestart)&lt;br /&gt;
 get_logs_userday($userid, $courseid, $daystart)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====get_logs()====&lt;br /&gt;
This is a generic function to fetch data based on a given SQL condition.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $params = array();&lt;br /&gt;
 $selector = &amp;quot;l.course = :courseid&amp;quot;;&lt;br /&gt;
 $params[&#039;courseid&#039;] = $course-&amp;gt;id;&lt;br /&gt;
 $logs = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_logs_usercourse()====&lt;br /&gt;
get_logs_usercourse() returns logs data for a given specific course and user.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $coursestart = usergetmidnight($course-&amp;gt;startdate);&lt;br /&gt;
 $logs = get_logs_usercourse($user-&amp;gt;id, $courseselect, $coursestart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====get_logs_userday()====&lt;br /&gt;
This function can return logs specific to a given user and course for a given date.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $daystart = usergetmidnight(time());&lt;br /&gt;
 $logs = get_logs_userday($user-&amp;gt;id, $courseselect, $daystart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Mod/*/db/log.php Files==&lt;br /&gt;
These files specify what information should be associated with a log entry. To understand the working of these files better its necessary to understand the structure of database table &#039;prefix_log_display&#039;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Info&lt;br /&gt;
|-&lt;br /&gt;
| id&lt;br /&gt;
| bigint(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| The unique ID for this comment.&lt;br /&gt;
|-&lt;br /&gt;
| module&lt;br /&gt;
| varchar(20)&lt;br /&gt;
|&lt;br /&gt;
| who wrote this comment&lt;br /&gt;
|-&lt;br /&gt;
| action&lt;br /&gt;
| varchar(40)&lt;br /&gt;
|&lt;br /&gt;
| The action associated. Ex- view, delete, update etc&lt;br /&gt;
|-&lt;br /&gt;
| mtable&lt;br /&gt;
| varchar(30)&lt;br /&gt;
|&lt;br /&gt;
| The name of the database table from which info will be fetched to associate with the log entry&lt;br /&gt;
|-&lt;br /&gt;
| field&lt;br /&gt;
| varchar(200)&lt;br /&gt;
|&lt;br /&gt;
| Which filed needs to be fetched from mtable&lt;br /&gt;
|-&lt;br /&gt;
| component&lt;br /&gt;
| varchar(100)&lt;br /&gt;
|&lt;br /&gt;
| The component ([https://docs.moodle.org/dev/Frankenstyle Frankenstyle name]) associated with the entry&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a log needs to be displayed to a front end user, this table helps us determine what information needs to be displayed to the user along with the log entry, so that they can make perfect sense out of the report. Whenever we have to display a log entry for a given module and action say $module and $action, the information that&#039;s shown along with the log is the value of the &#039;field&#039; column fetched from &#039;mtable&#039; corresponding to the given $module and $action.&lt;br /&gt;
Now Mod/*/db/log.php files are used to create this association by adding entries to the log_display table during the install or upgrade of a module. &lt;br /&gt;
===Example===&lt;br /&gt;
This a simple example of what contents of log.php files can be.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $logs = array(&lt;br /&gt;
    array(&#039;module&#039;=&amp;gt;&#039;page&#039;, &#039;action&#039;=&amp;gt;&#039;view&#039;, &#039;mtable&#039;=&amp;gt;&#039;page&#039;, &#039;field&#039;=&amp;gt;&#039;name&#039;),&lt;br /&gt;
    array(&#039;module&#039;=&amp;gt;&#039;page&#039;, &#039;action&#039;=&amp;gt;&#039;view all&#039;, &#039;mtable&#039;=&amp;gt;&#039;page&#039;, &#039;field&#039;=&amp;gt;&#039;name&#039;),&lt;br /&gt;
    array(&#039;module&#039;=&amp;gt;&#039;page&#039;, &#039;action&#039;=&amp;gt;&#039;update&#039;, &#039;mtable&#039;=&amp;gt;&#039;page&#039;, &#039;field&#039;=&amp;gt;&#039;name&#039;),&lt;br /&gt;
    array(&#039;module&#039;=&amp;gt;&#039;page&#039;, &#039;action&#039;=&amp;gt;&#039;add&#039;, &#039;mtable&#039;=&amp;gt;&#039;page&#039;, &#039;field&#039;=&amp;gt;&#039;name&#039;),&lt;br /&gt;
 );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See Also==&lt;br /&gt;
&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [https://docs.moodle.org/en/Logs Logs Reports]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=SQL_coding_style&amp;diff=45290</id>
		<title>SQL coding style</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=SQL_coding_style&amp;diff=45290"/>
		<updated>2014-06-11T15:32:25Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Updating to show table aliases info and link to Database page.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes recommended coding style for complex database queries. &lt;br /&gt;
&lt;br /&gt;
Full SQL queries are used in $DB-&amp;gt;get_records_sql(), $DB-&amp;gt;get_recordset_sql() or $DB-&amp;gt;execute(). SQL fragments may be used in DML method with _select() suffix.&lt;br /&gt;
&lt;br /&gt;
== General rules==&lt;br /&gt;
* Use parameter placeholders!&lt;br /&gt;
* All SQL keywords are in UPPER CASE.&lt;br /&gt;
* All SQL queries and fragments should be enclosed in double quotes.&lt;br /&gt;
* Complex SQL queries should be on multiple lines.&lt;br /&gt;
* Multiline SQL queries should be right aligned on SELECT, FROM, JOIN, WHERE, GROUPY BY and HAVING.&lt;br /&gt;
* Use JOIN instead of INNER JOIN.&lt;br /&gt;
* Do not use right joins.&lt;br /&gt;
* Always use AS keyword for column aliases.&lt;br /&gt;
* Never use AS keyword for table aliases.&lt;br /&gt;
&lt;br /&gt;
==Double quotes==&lt;br /&gt;
&lt;br /&gt;
All sql queries and fragments should be enclosed in double quotes, do not concat SQL from multiple parts if possible. The single quotes are used for sql strings, it also helps with visual highlighting and SQL code completion in some IDEs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$records = $DB-&amp;gt;get_records_select(&#039;some_table&#039;, &amp;quot;id &amp;gt; ?&amp;quot;, array(111));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Parameter placeholders==&lt;br /&gt;
&lt;br /&gt;
All variable query parameters must be specified via placeholders. It is possible to use three different types of placeholders: :named, ? and $1. It is recommended to use named parameters if there is more than one parameter.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT *&lt;br /&gt;
          FROM {some_table}&lt;br /&gt;
         WHERE id &amp;gt; :above&amp;quot;;&lt;br /&gt;
$records = $DB-&amp;gt;get_records_sql($sql, array(&#039;above&#039;=&amp;gt;111));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Indentation==&lt;br /&gt;
&lt;br /&gt;
[[File:sql_indentation.png]]&lt;br /&gt;
&lt;br /&gt;
==Subqueries==&lt;br /&gt;
&lt;br /&gt;
There are no strict rules for subquery indentation, the deciding factor is good readability - see MDLSITE-1914.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Data manipulation API]]&lt;br /&gt;
* [[Database]]&lt;br /&gt;
* [[Coding style]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Coding guidelines|Coding style]]&lt;br /&gt;
[[Category:XMLDB]]&lt;br /&gt;
[[Category:DB]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Cache_API_-_Quick_reference&amp;diff=44840</id>
		<title>Cache API - Quick reference</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Cache_API_-_Quick_reference&amp;diff=44840"/>
		<updated>2014-05-18T01:57:44Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Create a definition */ Updating definition with current options.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This is a quick reference lookup for the Cache API.&lt;br /&gt;
More detail can be found on the [[Cache API]] page as well as a friendly explanation of the API.&lt;br /&gt;
&lt;br /&gt;
==Using a cache object==&lt;br /&gt;
===Getting a cache instance for a definition===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Most basic&lt;br /&gt;
$cache = cache::make(&#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Using identifiers&lt;br /&gt;
$cache = cache::make(&#039;component&#039;, &#039;area&#039;, array(&#039;dbfamily&#039; =&amp;gt; &#039;pgsql&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Getting an ad-hoc cache instance===&lt;br /&gt;
Using cache definitions is the recommended method. Ad-hoc caches should only be used where you have a rarely used cache, or insignificant cache. Typical use-case can be when you are refactoring some local static variables into MODE_REQUEST caches.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Application cache&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Session cache&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_SESSION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Request cache&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_REQUEST, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Using identifiers&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;,&lt;br /&gt;
    array(&#039;dbfamily&#039; =&amp;gt; &#039;pgsql&#039;));&lt;br /&gt;
&lt;br /&gt;
// Using persistence so that the cache instance is stored for future use/request&lt;br /&gt;
$cache = cache::make_with_params(cache::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;, array(),&lt;br /&gt;
    array(&#039;persistent&#039; =&amp;gt; true));&lt;br /&gt;
&lt;br /&gt;
// Using a request cache to replace static variable&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_REQUEST, &#039;component&#039;, &#039;area&#039;, array(),&lt;br /&gt;
    array(&#039;simplekeys&#039; =&amp;gt; true, &#039;simpledata&#039; =&amp;gt; true));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Get a key===&lt;br /&gt;
If you have many keys to retrieve you should use [[#Get many keys at once|get_many]].&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Get a cache instance&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Key can be an int or string&lt;br /&gt;
$data = $cache-&amp;gt;get(&#039;key&#039;);&lt;br /&gt;
&lt;br /&gt;
// Data returned will be what ever was stored, or false if it was not in the cache.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Get many keys at once===&lt;br /&gt;
Not all cache stores will support fetching many keys at once, some stores will take the array of keys and process them one by one.&lt;br /&gt;
If you have many keys to fetch it is recommended to use this still as cache stores that do support this will likely perform better.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Set some data so I can show results&lt;br /&gt;
$cache-&amp;gt;set(&#039;key1&#039;, &#039;data1&#039;);&lt;br /&gt;
$cache-&amp;gt;set(&#039;key3&#039;, &#039;data3&#039;);&lt;br /&gt;
&lt;br /&gt;
// Keys can be an int or string&lt;br /&gt;
$keys = array(&lt;br /&gt;
    &#039;key1&#039;,&lt;br /&gt;
    &#039;key2&#039;,&lt;br /&gt;
    &#039;key3&#039;&lt;br /&gt;
);&lt;br /&gt;
$results = $cache-&amp;gt;get_many($keys);&lt;br /&gt;
&lt;br /&gt;
print_r($results);&lt;br /&gt;
&lt;br /&gt;
// Will print the following:&lt;br /&gt;
// array(&lt;br /&gt;
//     &#039;key1&#039; =&amp;gt; &#039;data1&#039;,&lt;br /&gt;
//     &#039;key2&#039; =&amp;gt; false,&lt;br /&gt;
//     &#039;key3&#039; =&amp;gt; &#039;data3&#039;&lt;br /&gt;
// )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Store a key===&lt;br /&gt;
If you have many items to store you should use [[#Store many keys at once|set_many]].&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Get a cache instance&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Key can be an int or string&lt;br /&gt;
// Data can be anything&lt;br /&gt;
$result = $cache-&amp;gt;set(&#039;key&#039;, &#039;data&#039;);&lt;br /&gt;
&lt;br /&gt;
// Result will be true on success, false otherwise.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Store many keys at once===&lt;br /&gt;
Note not all stores will support setting several items in a single transaction, stores that don&#039;t will process each item of the array separately.&lt;br /&gt;
It is still recommended to use this method if you have many items to set as those stores that do support it will likely perform better.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Get a cache instance&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Prepare an associative array of key =&amp;gt; value pairs.&lt;br /&gt;
// Key can be an int or string&lt;br /&gt;
// Data can be anything&lt;br /&gt;
$data = array(&lt;br /&gt;
    &#039;key1&#039; =&amp;gt; &#039;data1&#039;,&lt;br /&gt;
    &#039;key3&#039; =&amp;gt; &#039;data3&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
// Use set_many&lt;br /&gt;
$result = $cache-&amp;gt;set_many($data);&lt;br /&gt;
&lt;br /&gt;
// Result will be an int, the number of items successfully set.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Delete a key===&lt;br /&gt;
If you have several keys you want to delete you should use [[#Delete many keys at once|delete_many]]. If you want to delete everything you should use [[#Delete all keys|purge]].&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Get a cache instance&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Key can be an int or string&lt;br /&gt;
// Data can be anything&lt;br /&gt;
$result = $cache-&amp;gt;set(&#039;key&#039;, &#039;data&#039;);&lt;br /&gt;
&lt;br /&gt;
// Result will be true on success, false otherwise.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
===Delete many keys at once===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Get a cache instance&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
// Key can be an int or string&lt;br /&gt;
// Data can be anything&lt;br /&gt;
$result = $cache-&amp;gt;delete_many(&#039;key&#039;, &#039;data&#039;);&lt;br /&gt;
&lt;br /&gt;
// $result will contain the number of items successfully deleted.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Delete all keys===&lt;br /&gt;
It is not recommended to purge unless absolutely required, this will cause the store (plugin instance) being used by your cache to be purged. Not all stores can tell which keys belong to your cache and in that circumstance all keys in the store are deleted, not just the keys belonging to your cache, also the keys belonging to other caches using that same store.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Get a cache instance&lt;br /&gt;
$cache = cache::make(cache_store::MODE_APPLICATION, &#039;component&#039;, &#039;area&#039;);&lt;br /&gt;
&lt;br /&gt;
$result = $cache-&amp;gt;purge();&lt;br /&gt;
// $result will contain the number of items successfully deleted.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Create a definition===&lt;br /&gt;
Basic definition with just the required mode:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitions = array(&lt;br /&gt;
    // The name of the cache area is the key. The component/plugin will be picked up from the file location.&lt;br /&gt;
    &#039;area&#039; =&amp;gt; array(&lt;br /&gt;
        // [int] Required; Sets the mode for the definition. Must be one of cache_store::MODE_*&lt;br /&gt;
        &#039;mode&#039; =&amp;gt; cache_store::MODE_*,&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Advanced definition:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitions = array(&lt;br /&gt;
    // The name of the cache area is the key. The component/plugin will be picked up from the file location.&lt;br /&gt;
    &#039;area&#039; =&amp;gt; array(&lt;br /&gt;
        // [int] Required; Sets the mode for the definition. Must be one of cache_store::MODE_*&lt;br /&gt;
        &#039;mode&#039; =&amp;gt; cache_store::MODE_*,&lt;br /&gt;
&lt;br /&gt;
        // All of the following options are default&lt;br /&gt;
&lt;br /&gt;
        // [bool] Set to true if your cache will only use simple keys for its items.&lt;br /&gt;
        // Simple keys consist of digits, underscores and the 26 chars of the english language. a-zA-Z0-9_&lt;br /&gt;
        // If true the keys won&#039;t be hashed before being passed to the cache store for gets/sets/deletes. It will be&lt;br /&gt;
        // better for performance and possible only becase we know the keys are safe.&lt;br /&gt;
        &#039;simplekeys&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [bool] If set to true we know that the data is scalar or array of scalar.&lt;br /&gt;
        &#039;simpledata&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [array] An array of identifiers that must be provided to the cache when it is created.&lt;br /&gt;
        &#039;requireidentifiers&#039; =&amp;gt; array(&#039;ident1&#039;, &#039;ident2&#039;),&lt;br /&gt;
&lt;br /&gt;
        // [bool] If set to true then only stores that can guarantee data will remain available once set will be used.&lt;br /&gt;
        &#039;requiredataguarantee&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [bool] If set to true then only stores that support multiple identifiers will be used.&lt;br /&gt;
        &#039;requiremultipleidentifiers&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [bool] If set to true then a lock will be gained before reading from the cache store. It is recommended not to use&lt;br /&gt;
        // this setting unless 100% absolutely positively required. Remember 99.9% of caches will NOT need this setting.&lt;br /&gt;
        // This setting will only be used for application caches presently.&lt;br /&gt;
        &#039;requirelockingread&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [bool] If set to true then a lock will be gained before writing to the cache store. As above this is not recommended&lt;br /&gt;
        // unless truly needed. Please think about the order of your code and deal with race conditions there first.&lt;br /&gt;
        // This setting will only be used for application caches presently.&lt;br /&gt;
        &#039;requirelockingwrite&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [int] If set this will be used as the maximum number of entries within the cache store for this definition.&lt;br /&gt;
        // Its important to note that cache stores don&#039;t actually have to acknowledge this setting or maintain it as a hard limit.&lt;br /&gt;
        &#039;maxsize&#039; =&amp;gt; null,&lt;br /&gt;
&lt;br /&gt;
        // [string] A class to use as the loader for this cache. This is an advanced setting and will allow the developer of the&lt;br /&gt;
        // definition to take 100% control of the caching solution.&lt;br /&gt;
        // Any class used here must inherit the cache_loader interface and must extend default cache loader for the mode they are using.&lt;br /&gt;
        &#039;overrideclass&#039; =&amp;gt; null,&lt;br /&gt;
&lt;br /&gt;
        // [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.&lt;br /&gt;
        &#039;overrideclassfile&#039; =&amp;gt; null,&lt;br /&gt;
&lt;br /&gt;
        // [string] A class to use as the data loader for this definition.&lt;br /&gt;
        // Any class used here must inherit the cache_data_loader interface.&lt;br /&gt;
        &#039;datasource&#039; =&amp;gt; null,&lt;br /&gt;
&lt;br /&gt;
        // [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.&lt;br /&gt;
        &#039;datasourcefile&#039; =&amp;gt; null,&lt;br /&gt;
&lt;br /&gt;
        // [bool] This setting does two important things. First it tells the cache API to only instantiate the cache structure for&lt;br /&gt;
        // this definition once, further requests will be given the original instance.&lt;br /&gt;
        // Second the cache loader will keep an array of the items set and retrieved to the cache during the request.&lt;br /&gt;
        // This has several advantages including better performance without needing to start passing the cache instance between&lt;br /&gt;
        // function calls, the downside is that the cache instance + the items used stay within memory.&lt;br /&gt;
        // Consider using this setting when you know that there are going to be many calls to the cache for the same information&lt;br /&gt;
        // or when you are converting existing code to the cache and need to access the cache within functions but don&#039;t want&lt;br /&gt;
        // to add it as an argument to the function.&lt;br /&gt;
        &#039;staticacceleration&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [int] This supplements the above setting by limiting the number of items in the caches persistent array of items.&lt;br /&gt;
        // Tweaking this setting lower will allow you to minimise the memory implications above while hopefully still managing to&lt;br /&gt;
        // offset calls to the cache store.&lt;br /&gt;
        &#039;staticaccelerationsize&#039; =&amp;gt; null,&lt;br /&gt;
&lt;br /&gt;
        // [int] A time to live for the data (in seconds). It is strongly recommended that you don&#039;t make use of this and&lt;br /&gt;
        // instead try to create an event driven invalidation system.&lt;br /&gt;
        // Not all cache stores will support this natively and there are undesired performance impacts if the cache store does not.&lt;br /&gt;
        &#039;ttl&#039; =&amp;gt; 0,&lt;br /&gt;
&lt;br /&gt;
        // [bool] If set to true only the mapped cache store(s) will be used and the default mode store will not. This is a super&lt;br /&gt;
        // advanced setting and should not be used unless absolutely required. It allows you to avoid the default stores for one&lt;br /&gt;
        // reason or another.&lt;br /&gt;
        &#039;mappingsonly&#039; =&amp;gt; false,&lt;br /&gt;
&lt;br /&gt;
        // [array] An array of events that should cause this cache to invalidate some or all of the items within it.&lt;br /&gt;
        &#039;invalidationevents&#039; =&amp;gt; array(&#039;event1&#039;, &#039;event2&#039;),&lt;br /&gt;
        &lt;br /&gt;
        // [int] The sharing options that are appropriate for this definition. Should be the sum of the possible options.&lt;br /&gt;
        &#039;sharingoptions&#039; =&amp;gt; cache_definition::SHARING_DEFAULT,&lt;br /&gt;
        &lt;br /&gt;
        // [int] The default sharing option to use. It&#039;s highly recommended that you don&#039;t set this unless there is a very&lt;br /&gt;
        // specific reason not to use the system default.&lt;br /&gt;
        &#039;defaultsharing&#039; =&amp;gt; cache_definition::SHARING_DEFAULT,&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A better explanation of the cache definition can be found on the [[Cache API#The definition|Cache API]] page&lt;br /&gt;
&lt;br /&gt;
==Invalidating keys from a cache==&lt;br /&gt;
===Invalidate keys using an event===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
cache_helper::invalidate_by_event(&#039;event1&#039;, array(&#039;key1&#039;, &#039;key2&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Invalidate keys using belonging to a definition===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Identifiers for the definitions&lt;br /&gt;
$identifiers = array(&lt;br /&gt;
    &#039;ident1&#039; =&amp;gt; &#039;something&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
// Keys to invalidate&lt;br /&gt;
$keys = array(&#039;key1&#039;, &#039;key2&#039;);&lt;br /&gt;
&lt;br /&gt;
cache_helper::invalidate_by_definition(&#039;component&#039;, &#039;area&#039;, $identifiers, $keys);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=44839</id>
		<title>Cache API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=44839"/>
		<updated>2014-05-18T01:50:59Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Updating definitions to include simplekeys/simpledata/sharingoptions/defaultsharing. Renaiming persist options to static.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This document details the Cache API aka MUC aka Moodle&#039;s Universal Cache.&lt;br /&gt;
I&#039;ve chosen to use a tutorial/example flow for this document always working with a theoretical module plugin called myplugin.&lt;br /&gt;
There is also a [[Cache API - Quick reference]] if you would rather read that.&lt;br /&gt;
&lt;br /&gt;
==Basic usage==&lt;br /&gt;
It&#039;s very easy to get started with the Cache API. It is designed to be as easy and as quick to use as possible and is predominantely self contained.&lt;br /&gt;
All you need to do is add a definition for your cache and you are ready to start working with the Cache API.&lt;br /&gt;
&lt;br /&gt;
===Creating a definition===&lt;br /&gt;
Cache definitions exist within the db/caches.php file for a component/plugin.&amp;lt;br /&amp;gt;&lt;br /&gt;
In the case of core that is the moodle/lib/db/caches.php file, in the case of a module that would be moodle/mod/myplugin/db/caches.php.&lt;br /&gt;
&lt;br /&gt;
The definition is used API in order to understand a little about the cache and what it is being used for, it also allows the administrator to set things up especially for the definition if they want.&lt;br /&gt;
From a development point of view the definition allows you to tell the API about your cache, what it requires, and any (if any) advanced features you want it to have.&amp;lt;br /&amp;gt;&lt;br /&gt;
The following shows a basic definition containing just the bare minimum:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// moodle/mod/myplugin/db/caches.php&lt;br /&gt;
$definitions = array(&lt;br /&gt;
    &#039;somedata&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;mode&#039; =&amp;gt; cache_store::MODE_APPLICATION&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This informs the API that the myplugin module has a cache called &#039;&#039;somedata&#039;&#039; and that it is an application (globally shared) cache.&amp;lt;br /&amp;gt;&lt;br /&gt;
When creating a definition thats the bare minimum, to provide an area &#039;&#039;(somedata)&#039;&#039; and declare the type of the cache application, session, or request.&amp;lt;br /&amp;gt;&lt;br /&gt;
:An application cache is a shared cache, all users can access it.&amp;lt;br /&amp;gt;&lt;br /&gt;
:Session caches are, well, just stores in the users session.&amp;lt;br /&amp;gt;&lt;br /&gt;
:Request caches you can think of as static caches, only available to the user owning the request, and only alive until the end of the request.&amp;lt;br /&amp;gt;&lt;br /&gt;
There are of course many more options available that allow you to really take the cache by the reigns, you can read about some of the important ones further on, or skip ahead to [[#The definition]] section which details the available options in full.&lt;br /&gt;
&lt;br /&gt;
Please note that for each definition, a language string with the name of the definition is expected.&lt;br /&gt;
&lt;br /&gt;
===Getting a cache object===&lt;br /&gt;
Once your definition has been created you should bump the version number so that Moodle upgrades and processes the definitions file at which point your definition will be useable.&lt;br /&gt;
&lt;br /&gt;
Now within code you can get a cache object to interact with in the following manner.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$cache = cache::make(&#039;mod_myplugin&#039;, &#039;mycache&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The cache::make method is a factory method, it will create a cache object to allow you to work with your cache. The cache object will be one of several classes chosen by the API based upon what your definition contains. All of these classes will extend the base cache class, and in nearly all cases you will get one of cache_application, cache_session, or cache_request depending upon the mode you selected.&lt;br /&gt;
&lt;br /&gt;
===Using your cache object===&lt;br /&gt;
Once you have a cache object (will extend the cache class and implements cache_loader) you are ready to start interacting with the cache.&lt;br /&gt;
&lt;br /&gt;
Of course there are three basic basic operations. get, set, and delete.&lt;br /&gt;
&lt;br /&gt;
The first is to send something to the cache.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$result = $cache-&amp;gt;set(&#039;key&#039;, &#039;value&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Easy enough. The key must an int or a string. The value can be absolutely anything your want.&lt;br /&gt;
The result is true if the operation was a success, false otherwise.&lt;br /&gt;
&lt;br /&gt;
The second is to fetch something from the cache.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$data = $cache-&amp;gt;get(&#039;key&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
$data will either be what ever was being stored in the cache, or false if the cache could not find the key.&lt;br /&gt;
&lt;br /&gt;
The third and final operation is delete.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$result = $cache-&amp;gt;delete(&#039;key&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again just like set the result will either be true if the operation was a success, or false otherwise.&lt;br /&gt;
&lt;br /&gt;
You can also set, get, and delete multiple key=&amp;gt;value pairs in a single transaction.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$result = $cache-&amp;gt;set_many(array(&lt;br /&gt;
    &#039;key1&#039; =&amp;gt; &#039;data1&#039;,&lt;br /&gt;
    &#039;key3&#039; =&amp;gt; &#039;data3&#039;&lt;br /&gt;
));&lt;br /&gt;
// $result will be the number of pairs sucessfully set.&lt;br /&gt;
&lt;br /&gt;
$result = $cache-&amp;gt;get_many(array(&#039;key1&#039;, &#039;key2&#039;, &#039;key3&#039;));&lt;br /&gt;
print_r($result);&lt;br /&gt;
// Will print the following:&lt;br /&gt;
// array(&lt;br /&gt;
//     &#039;key1&#039; =&amp;gt; &#039;data1&#039;,&lt;br /&gt;
//     &#039;key2&#039; =&amp;gt; false,&lt;br /&gt;
//     &#039;key3&#039; =&amp;gt; &#039;data3&#039;&lt;br /&gt;
// )&lt;br /&gt;
&lt;br /&gt;
$result = $cache-&amp;gt;delete_many(array(&#039;key1&#039;, &#039;key3&#039;);&lt;br /&gt;
// $result will be the number of records sucessfully deleted.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That covers the basic operation of the Cache API.&amp;lt;br /&amp;gt;&lt;br /&gt;
In many situations there is not going to be any more to it than that.&lt;br /&gt;
&lt;br /&gt;
==Ad-hoc Caches==&lt;br /&gt;
This is the alternative method of using the cache API.&amp;lt;br /&amp;gt;&lt;br /&gt;
It involves creating a cache using just the required params at the time that it is required. It doesn&#039;t require that a definition exists making it quicker and easier to use, however it can only use the default settings and is only recommended for insignificant caches (rarely used during operation, never to be mapped or customised, only existsing in a single place in code).&lt;br /&gt;
&lt;br /&gt;
Once a cache object has been retrieved it operates exactly as the same as a cache that has been created for a definition.&lt;br /&gt;
&lt;br /&gt;
To create an ad-hoc cache you would use the following:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, &#039;mod_myplugin&#039;, &#039;mycache&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Really don&#039;t be lazy, if you don&#039;t have a good reason to use an ad-hoc cache you should be spending a extra 5 minutes creating a definition.&lt;br /&gt;
&lt;br /&gt;
==The definition==&lt;br /&gt;
&lt;br /&gt;
The above section illustrated how to create a basic definition, specifying just the area name (the key) and the mode for the definition. Those being the two required properties for a definition.&amp;lt;br /&amp;gt;&lt;br /&gt;
There are many other options that will let you make the most of the Cache API and will undoubtedly be required when implementing and converting cache solutions to the Cache API.&lt;br /&gt;
&lt;br /&gt;
The following details the options available to a definition and their defaults if not applied:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitions = array(&lt;br /&gt;
    // The name of the cache area is the key. The component/plugin will be picked up from the file location.&lt;br /&gt;
    &#039;area&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;mode&#039; =&amp;gt; cache_store::MODE_*,&lt;br /&gt;
        &#039;simplekeys&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;simpledata&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requireidentifiers&#039; =&amp;gt; array(&#039;ident1&#039;, &#039;ident2&#039;),&lt;br /&gt;
        &#039;requiredataguarantee&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requiremultipleidentifiers&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requirelockingread&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;requirelockingwrite&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;maxsize&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;overrideclass&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;overrideclassfile&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;datasource&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;datasourcefile&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;staticacceleration&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;staticaccelerationsize&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;ttl&#039; =&amp;gt; 0,&lt;br /&gt;
        &#039;mappingsonly&#039; =&amp;gt; false,&lt;br /&gt;
        &#039;invalidationevents&#039; =&amp;gt; array(&#039;event1&#039;, &#039;event2&#039;),&lt;br /&gt;
        &#039;sharingoptions&#039; =&amp;gt; cache_definition::SHARING_DEFAULT,&lt;br /&gt;
        &#039;defaultsharing&#039; =&amp;gt; cache_definition::SHARING_DEFAULT,&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Setting requirements===&lt;br /&gt;
The definition can specify several requirements for the cache.&amp;lt;br /&amp;gt;&lt;br /&gt;
This includes identifiers that must be provided when creating the cache object, that the store guarantees data stored in it will remain there until removed, a store that supports multiple identifiers, and finally read/write locking.&lt;br /&gt;
The options for these are as follows:&lt;br /&gt;
&lt;br /&gt;
; simplekeys : [bool] Set to true if your cache will only use simple keys for its items.&amp;lt;br /&amp;gt;Simple keys consist of digits, underscores and the 26 chars of the english language. a-zA-Z0-9_&amp;lt;br /&amp;gt;If true the keys won&#039;t be hashed before being passed to the cache store for gets/sets/deletes. It will be better for performance and possible only because we know the keys are safe.&lt;br /&gt;
; simpledata : [bool] If set to true we know that the data is scalar or array of scalar.&lt;br /&gt;
; requireidentifiers : [array] An array of identifiers that must be provided to the cache when it is created.&lt;br /&gt;
; requiredataguarantee : [bool] If set to true then only stores that can guarantee data will remain available once set will be used.&lt;br /&gt;
; requiremultipleidentifiers : [bool] If set to true then only stores that support multiple identifiers will be used.&lt;br /&gt;
; requirelockingread : [bool] If set to true then a lock will be gained before reading from the cache store. It is recommended not to use this setting unless 100% absolutely positively required.&amp;lt;br /&amp;gt;Remember 99.9% of caches will NOT need this setting.&amp;lt;br /&amp;gt;This setting will only be used for application caches presently.&lt;br /&gt;
; requirelockingwrite : [bool] If set to true then a lock will be gained before writing to the cache store. As above this is not recommended unless truly needed. Please think about the order of your code and deal with race conditions there first.&amp;lt;br /&amp;gt;This setting will only be used for application caches presently.&lt;br /&gt;
&lt;br /&gt;
===Cache modifiers===&lt;br /&gt;
You are also to modify the way in which the cache is going to operate when working for your definition.&amp;lt;br /&amp;gt;&lt;br /&gt;
By enabling the static option the Cache API will only ever generate a single cache object for your definition on the first request for it, further requests will be returned the original instance.&amp;lt;br /&amp;gt;This greatly speeds up the collecting of a cache object.&amp;lt;br /&amp;gt;&lt;br /&gt;
Enabling persistence also enables a static store within the cache object, anything set to the cache, or retrieved from it will be stored in that static array for the life of the request.&lt;br /&gt;
This makes the persistence options some of the most powerful. If you know you are going to be using you cache over and over again or if you know you will be making lots of requests for the same items then this will provide a great performance boost.&lt;br /&gt;
Of course the static storage of cache objects and of data is costly in terms of memory and should only be used when actually required, as such it is turned off by default.&lt;br /&gt;
As well as persistence you can also set a maximum number of items that the cache should store (not a hard limit, its up to each store) and a time to live (ttl) although both are discouraged as efficient design negates the need for both in most situations.&lt;br /&gt;
&lt;br /&gt;
; staticacceleration : [bool] This setting does two important things. First it tells the cache API to only instantiate the cache structure for this definition once, further requests will be given the original instance.&amp;lt;br /&amp;gt;Second the cache loader will keep an array of the items set and retrieved to the cache during the request.&amp;lt;br /&amp;gt;This has several advantages including better performance without needing to start passing the cache instance between function calls, the downside is that the cache instance + the items used stay within memory.&amp;lt;br /&amp;gt;Consider using this setting when you know that there are going to be many calls to the cache for the same information or when you are converting existing code to the cache and need to access the cache within functions but don&#039;t want to add it as an argument to the function.&lt;br /&gt;
; staticaccelerationsize : [int] This supplements the above setting by limiting the number of items in the caches persistent array of items.&amp;lt;br /&amp;gt;Tweaking this setting lower will allow you to minimise the memory implications above while hopefully still managing to offset calls to the cache store.&lt;br /&gt;
; ttl : [int] A time to live for the data (in seconds). It is strongly recommended that you don&#039;t make use of this and instead try to create an event driven invalidation system.&amp;lt;br /&amp;gt;Not all cache stores will support this natively and there are undesired performance impacts if the cache store does not.&lt;br /&gt;
; maxsize : [int] If set this will be used as the maximum number of entries within the cache store for this definition.&amp;lt;br /&amp;gt;Its important to note that cache stores don&#039;t actually have to acknowledge this setting or maintain it as a hard limit.&lt;br /&gt;
&lt;br /&gt;
===Overriding a cache loader===&lt;br /&gt;
This is a super advanced feature and should not be done. ever. Unless you have an very good reason to do so.&lt;br /&gt;
It allows you to create your own cache loader and have it be used instead of the default cache loader class. The cache object you get back from the make operations will be an instance of this class.&lt;br /&gt;
&lt;br /&gt;
; overrideclass : [string] A class to use as the loader for this cache. This is an advanced setting and will allow the developer of the definition to take 100% control of the caching solution.&amp;lt;br /&amp;gt;Any class used here must inherit the cache_loader interface and must extend default cache loader for the mode they are using.&lt;br /&gt;
; overrideclassfile : [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.&lt;br /&gt;
&lt;br /&gt;
===Specifying a data source===&lt;br /&gt;
This is a great wee feature, especially if your code is object orientated.&amp;lt;br /&amp;gt;&lt;br /&gt;
It allows you to specify a class that must inherit the cache_data_source object and will be used to load any information requested from the cache that is not already being stored.&amp;lt;br /&amp;gt;&lt;br /&gt;
When the requested key cannot be found in the cache the data source will be asked to load it. The data source will then return the information to the cache, the cache will store it, and it will then return it to the user as a request of their get request. Essentially no get request should ever fail if you have a data source specified.&lt;br /&gt;
&lt;br /&gt;
; datasource : [string] A class to use as the data loader for this definition.&amp;lt;br /&amp;gt;Any class used here must inherit the cache_data_loader interface.&lt;br /&gt;
; datasourcefile : [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.&lt;br /&gt;
&lt;br /&gt;
===Misc settings===&lt;br /&gt;
The following are stand along settings that don&#039;t fall into any of the above categories.&lt;br /&gt;
&lt;br /&gt;
; invalidationevents : [array] An array of events that should cause this cache to invalidate some or all of the items within it.&lt;br /&gt;
; mappingsonly : [bool] If set to true only the mapped cache store(s) will be used and the default mode store will not. This is a super advanced setting and should not be used unless absolutely required. It allows you to avoid the default stores for one reason or another.&lt;br /&gt;
; sharingoptions : [int] The sharing options that are appropriate for this definition. Should be the sum of the possible options.&lt;br /&gt;
; defaultsharing : [int] The default sharing option to use. It&#039;s highly recommended that you don&#039;t set this unless there is a very specific reason not to use the system default.&lt;br /&gt;
&lt;br /&gt;
==The loader==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Using a data source==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Developing cache plugins==&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Database&amp;diff=44816</id>
		<title>Database</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Database&amp;diff=44816"/>
		<updated>2014-05-15T16:59:28Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Database structures */ Minor type&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Database structures==&lt;br /&gt;
&lt;br /&gt;
To help you create tables that meet these guidelines, we recommend you use the built in [[XMLDB_defining_an_XML_structure#The_XMLDB_editor|database definition (XMLDB) editor]].&lt;br /&gt;
&lt;br /&gt;
# Every table must have an auto-incrementing id field (INT10) as primary key. (see [[IdColumnReasons]])&lt;br /&gt;
# The main table containing instances of each module must have the same name as the module (eg widget) and contain the following minimum fields:&lt;br /&gt;
#* id - as described above&lt;br /&gt;
#* course - the id of the course that each instance belongs to&lt;br /&gt;
#* name - the full name of each instance of the module&lt;br /&gt;
# Other tables associated with a module that contain information about &#039;things&#039; should be named widget_things (note the plural).&lt;br /&gt;
# Core tables in general should have single word names non-pluralised, and double word names pluralised only for the last word e.g. &#039;course&#039;, &#039;course_categories&#039;. The only exceptions should be for reserved words e.g. &#039;files&#039;. Some tables don&#039;t fit this pattern right now for historical reasons, but this will eventually be changed.&lt;br /&gt;
# Table and column names should avoid using [[XMLDB_reserved_words|reserved words in any database]]. Please check them before creation. Table names may be up to 28 characters long, and Column names up to 30 characters. &lt;br /&gt;
# Column names should be always lowercase, simple and short, following the same rules as for variable names.&lt;br /&gt;
# Where possible, columns that contain a reference to the id field of another table (eg widget) should be called widgetid. (Note that this convention is newish and not followed in some older tables)&lt;br /&gt;
# Boolean fields should be implemented as small integer fields (eg INT4) containing 0 or 1, to allow for later expansion of values if necessary.&lt;br /&gt;
# Most tables should have a timemodified field (INT10) which is updated with a current timestamp obtained with the PHP time() function.&lt;br /&gt;
# Always define a default value for each field (and make it sensible)&lt;br /&gt;
# Each table name should start with the database prefix ($CFG-&amp;gt;prefix). In a lot of cases, this is taken care of for you automatically. Also, under Postgres, the name of every index must start with the prefix too.&lt;br /&gt;
# In order to guarantee [[XMLDB problems#Table and column aliases - the AS keyword|cross-db compatibility]] follow these simple rules about the use of the &#039;&#039;&#039;AS&#039;&#039;&#039; keyword (only if you need table/column aliases, of course):&lt;br /&gt;
#* &#039;&#039;&#039;Don&#039;t use&#039;&#039;&#039; the &#039;&#039;&#039;AS&#039;&#039;&#039; keyword for &#039;&#039;&#039;table aliases&#039;&#039;&#039;.&lt;br /&gt;
#* &#039;&#039;&#039;Don&#039;t use&#039;&#039;&#039; &#039;&#039;&#039;table aliases&#039;&#039;&#039; at all for DELETE statments (Mysql doesn&#039;t like it).&lt;br /&gt;
#* &#039;&#039;&#039;Do use&#039;&#039;&#039; the &#039;&#039;&#039;AS&#039;&#039;&#039; keyword for &#039;&#039;&#039;column aliases&#039;&#039;&#039;.&lt;br /&gt;
# &#039;&#039;&#039;Never&#039;&#039;&#039; create UNIQUE KEYs (constraints) at all. Instead use UNIQUE INDEXes. In the future, if we decide to add referential integrity to Moodle and we need UNIQUE KEYs they will be used, but not now. Please note that the XMLDB editor allows you to specify both XMLDB-only UNIQUE and FOREIGN constraints (and that&#039;s good, in order to have the XML well defined) but only underlying INDEXes will be generated. &lt;br /&gt;
# Those XMLDB-only UNIQUE KEYs (read previous point) only must be defined if such field/fields &#039;&#039;&#039;are going to be the target&#039;&#039;&#039; for some (XMLDB-only too) FOREIGN KEY. Else, create them as simple UNIQUE INDEXes.&lt;br /&gt;
# Tables associated &#039;&#039;&#039;with one block&#039;&#039;&#039; must follow this convention with their names: &#039;&#039;&#039;$CFG-&amp;gt;prefix + &amp;quot;block_&amp;quot; + name_of_the_block + anything_else&#039;&#039;&#039;. For example, assuming that $CFG-&amp;gt;prefix is &#039;mdl_&#039;, all the tables for the block &amp;quot;rss_client&amp;quot; must start by &#039;mdl_block_rss_client&#039; (being possible to add more words at the end, i.e. &#039;mdl_block_rss_client_anothertable&#039;...). This rule will be 100% enforced with Moodle 2.0, giving time to developers until then. See [http://tracker.moodle.org/browse/MDL-6786 Task 6786] for more info about this.&lt;br /&gt;
# &#039;&#039;&#039;Never&#039;&#039;&#039; make database changes in the STABLE branches.  If we did, then users upgrading from one stable version to the next would have duplicate changes occurring, which may cause serious errors.&lt;br /&gt;
# When refering to integer variable in SQL queries, do not surround the value in quotes. For example, get_records_select(&#039;question&#039;, &amp;quot;category=$catid&amp;quot;) is right. get_records_select(&#039;question&#039;, &amp;quot;category=&#039;$catid&#039;&amp;quot;) is wrong. It hides bugs where $catid is undefined. ([http://moodle.org/mod/forum/discuss.php?d=80629 This thread explains].)&lt;br /&gt;
# Never use double quotes for variable values in SQL queries (e.g. &amp;lt;strike&amp;gt;&#039;SELECT * FROM {user} WHERE username = &amp;quot;someuser&amp;quot;&#039;&amp;lt;/strike&amp;gt;). While this is OK for MySQL, which does not respect ANSI standard for databases, Postgresql is treating double quoted variable this as system identifier (e.g. field name).&lt;br /&gt;
# Moodle does not support database &amp;quot;views&amp;quot;, don&#039;t use them. See Petr&#039;s comment on [http://tracker.moodle.org/browse/MDL-25407 Task 25407] for more info about this.&lt;br /&gt;
&lt;br /&gt;
[[Category:Coding guidelines|Database]]&lt;br /&gt;
[[Category:DB|Database]]&lt;br /&gt;
[[Category:XMLDB|Database]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.7_release_notes&amp;diff=44665</id>
		<title>Moodle 2.7 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.7_release_notes&amp;diff=44665"/>
		<updated>2014-05-06T16:00:22Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Platform */ Typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: Expected in May 2014&lt;br /&gt;
 &lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just minimums.  We recommend keeping all your software updated. &lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 2.2 or later (if upgrading from earlier versions, you must upgrade to 2.2.11 as a first step)&lt;br /&gt;
* Minimum Database versions: &lt;br /&gt;
** PostgreSQL 9.1&lt;br /&gt;
** MySQL 5.5.31&lt;br /&gt;
** MariaDB 5.5.31&lt;br /&gt;
** MSSQL 2008, or &lt;br /&gt;
** Oracle 10.2&lt;br /&gt;
* Minimum PHP version: PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/)&lt;br /&gt;
*Ghostscript should be installed for pdf annotation.&lt;br /&gt;
&lt;br /&gt;
==Browser requirements==&lt;br /&gt;
&lt;br /&gt;
* Recent Google Chrome, recent Mozilla Firefox, Safari 6 or later, Internet Explorer 9 or later (IE 10 required for drag and drop of files from outside the browser into Moodle)&lt;br /&gt;
&lt;br /&gt;
==Before you upgrade==&lt;br /&gt;
&lt;br /&gt;
===Questions===&lt;br /&gt;
&lt;br /&gt;
If&lt;br /&gt;
* your site stared off on a version of Moodle 2.0.x or older,&lt;br /&gt;
* and, when you upgraded to Moodle 2.1 or 2.2, you made use of the complex facility to delay part of the quetion engine upgrade as explained in [https://docs.moodle.org/21/en/Upgrading_to_Moodle_2.1#Planning_the_question_engine_upgrade the upgrade documentation for that version],&lt;br /&gt;
* and, you still have not got around to completing that upgrade,&lt;br /&gt;
then you must complete it before upgrading to Moodle 2.7.&lt;br /&gt;
&lt;br /&gt;
As you can tell from that previous paragraph, this almost certainly does not affect you. You can check by looking at the bottom of the the [[:en:Environment]] check page in your site, providing you are running a version later than 2.4.9, 2.5.5 or 2.6.2. If you have a problem, it will tell you there. If there is no mention of questions there, you can forget about this.&lt;br /&gt;
&lt;br /&gt;
===Themes===&lt;br /&gt;
 &lt;br /&gt;
Several core themes have been removed from Moodle 2.7 (see MDL-43784).  If you wish to continue using one of these themes then it&#039;s best to reinstall it explicitly *BEFORE* running the upgrade.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;After burner&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_afterburner plugins db] [https://github.com/moodlehq/moodle-theme_afterburner github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Anomaly&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_anomaly plugins db] [https://github.com/moodlehq/moodle-theme_anomaly github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Arialist&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_arialist plugins db] [https://github.com/moodlehq/moodle-theme_arialist github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Binarius&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_binarius plugins db] [https://github.com/moodlehq/moodle-theme_binarius github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Boxxie&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_boxxie plugins db] [https://github.com/moodlehq/moodle-theme_boxxie github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Brick&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_brick plugins db] [https://github.com/moodlehq/moodle-theme_brick github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Formal_white&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_formal_white plugins db] [https://github.com/andreabix/moodle-theme_formal_white github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Form factor&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_formfactor plugins db] [https://github.com/moodlehq/moodle-theme_formfactor github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Fusion&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_fusion plugins db] [https://github.com/moodlehq/moodle-theme_fusion github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Leatherbound&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_leatherbound plugins db] [https://github.com/moodlehq/moodle-theme_leatherbound github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Magazine&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_magazine plugins db] [https://github.com/moodlehq/moodle-theme_magazine github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Nimble&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_nimble plugins db] [https://github.com/moodlehq/moodle-theme_nimble github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Nonzero&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_nonzero plugins db] [https://github.com/moodlehq/moodle-theme_nonzero github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Overlay&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_overlay plugins db] [https://github.com/moodlehq/moodle-theme_overlay github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Serenity&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_serenity plugins db] [https://github.com/moodlehq/moodle-theme_serenity github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Sky high&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_sky_high plugins db] [https://github.com/moodlehq/moodle-theme_sky_high github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Splash&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_splash plugins db] [https://github.com/moodlehq/moodle-theme_splash github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Standard&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_standard plugins db] [https://github.com/moodlehq/moodle-theme_standard github]&lt;br /&gt;
* &amp;lt;b style=&amp;quot;display:inline-block; min-width: 200px;&amp;quot;&amp;gt;Standard old&amp;lt;/b&amp;gt; [https://moodle.org/plugins/view.php?plugin=theme_standardold plugins db] [https://github.com/moodlehq/moodle-theme_standardold github]&lt;br /&gt;
&lt;br /&gt;
==Headline core features==&lt;br /&gt;
&lt;br /&gt;
===Interface===&lt;br /&gt;
* Atto - our new [https://docs.moodle.org/27/en/Text_editor text editor], tightly integrated in Moodle and focussing on usability and accessibility (TinyMCE still available as an option)&lt;br /&gt;
* MDL-43855 - New equation editor for Atto&lt;br /&gt;
* [https://docs.moodle.org/27/en/Standard_themes Themes clean-up] - Moodle is focussed on Bootstrap and improved responsive design.  Clean is now the default theme and most other old themes have been removed from core (still available from Plugins directory).  Many small improvements have been made all through the interface.&lt;br /&gt;
* More - a completely new theme called More that provides easy configuration though the UI while retaining the power of LESS and Bootstrap.&lt;br /&gt;
* Improved [https://docs.moodle.org/27/en/Conditional_activities_settings conditional activities] - complex boolean combinations now supported, plus plugin conditions and a better interface.  And faster!&lt;br /&gt;
* Reports - User interface for [https://docs.moodle.org/27/en/Logs loglive and log reports] has been improved with more information and filtering support (MDL-43682 and MDL-43681)&lt;br /&gt;
* MDL-43856 - New [https://docs.moodle.org/27/en/MathJax_filter MathJax filter] &lt;br /&gt;
&lt;br /&gt;
===Platform===&lt;br /&gt;
* Logging - a new logging subsystem with plugins allowing Moodle logs to be very detailed and external. [https://docs.moodle.org/27/en/Events_list Many new events] have been added which developers can take advantage of. MDL-37658  These advancements will support better analytics as well as things like TinCan.&lt;br /&gt;
* Tasks - an improved [https://docs.moodle.org/27/en/Scheduled_tasks scheduling system] (like Unix cron) that allows precise scheduling of tasks even on complex clustered servers.&lt;br /&gt;
* Performance - With improvements to logging and scheduled tasks, as well as many other small improvements, overall performance will be improved, particularly on large sites.&lt;br /&gt;
&lt;br /&gt;
===Long-term support (LTS) until June 2017===&lt;br /&gt;
&lt;br /&gt;
Most of our releases receive 1 year of backported general bug fixes and 1.5 years of security and dataloss fixes from Moodle HQ.&lt;br /&gt;
&lt;br /&gt;
Due to popular demand, we are committing to giving Moodle 2.7 extended support for security and dataloss fixes for &#039;&#039;&#039;3 years&#039;&#039;&#039; (that&#039;s an extra 1.5 years support for this version).&lt;br /&gt;
&lt;br /&gt;
If you are stuck on an old version then this might be the perfect time to upgrade!&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
===Quiz &amp;amp; Question bank===&lt;br /&gt;
&lt;br /&gt;
* Quiz reports improved. MDL-41727&lt;br /&gt;
** Responses from all tries are available for analysis when using or &amp;quot;Adaptive&amp;quot;, &amp;quot;Interactive with multiple tries&amp;quot; or similar behaviours.&lt;br /&gt;
** Break-down by question variant, for question types like Calculated, STACK and Variable-numeric, which one question can have different random variants.&lt;br /&gt;
** Progress bar during long calculations to prevent time-outs.&lt;br /&gt;
** Low-level calculation code moved into the question component, where it could potentially be reused by other activities.&lt;br /&gt;
** Much more automated testing of this complex area of code.&lt;br /&gt;
* Some minor improvements to the usability of the question bank - Some of MDL-40987&lt;br /&gt;
** To duplicate a question, you now start by clicking the x2 icon, like for activities. MDL-33653&lt;br /&gt;
** The various different ways to move questions in the question bank have been rationalised. MDL-33839&lt;br /&gt;
** There is now a &#039;Save changes and continue editing&#039; button when editing questions. Useful when you are working on a complex question with the preview open in another window. MDL-33653&lt;br /&gt;
* New plugin point, so that plugins can add columns to the question bank, or new search conditions. MDL-40313 &amp;amp; MDL-40457&lt;br /&gt;
* Essay questions can now require an attachment, with the text optional, rather than the other way around. MDL-39756&lt;br /&gt;
* Random short-answer matching question type brought back from the dead. (This was in stable branches, but worth mentioning again.) MDL-27414&lt;br /&gt;
&lt;br /&gt;
===Assignment===&lt;br /&gt;
&lt;br /&gt;
The old Assignment (2.2) module has been removed from core (MDL-33952). It has been replaced by a stub to support transparently remapping urls and restoring course backups from the old module to the new one. &lt;br /&gt;
&lt;br /&gt;
If you are still using the old assignment module - all instances of the old assignment module will be hidden after upgrading to Moodle 2.7. Once the upgrade tool is run on those assignments they will become visible again. &lt;br /&gt;
&lt;br /&gt;
It is recommended to upgrade, and then convert any remaining assignments because logic has been added to the assignment upgrade code for Moodle 2.7 to transparently map urls from the old assignment module to the new one.&lt;br /&gt;
&lt;br /&gt;
If you really, really need to keep using the old module, you should update the code to Moodle 2.7, and then replace the &amp;quot;mod/assignment&amp;quot; folder with the one from the plugins database before completing the upgrade.&lt;br /&gt;
&lt;br /&gt;
A new capability &#039;&#039;mod/assign:editothersubmission&#039;&#039; can be given to teachers to allow them to edit or delete student submissions.&lt;br /&gt;
&lt;br /&gt;
A checkbox &#039;Notify students&#039; is available to control when to send feedback during the grading process.&lt;br /&gt;
&lt;br /&gt;
Teachers can comment directly on student&#039;s work on online text assignments MDL-34432&lt;br /&gt;
&lt;br /&gt;
===Cron===&lt;br /&gt;
&lt;br /&gt;
Cron has received a major update and now has support for both scheduled and adhoc tasks - MDL-25499.&lt;br /&gt;
The benefits of these changes are:&lt;br /&gt;
* The schedule for every task can be configured by the admin&lt;br /&gt;
* Tasks can run in parallel&lt;br /&gt;
* Cron processes use locking to prevent the same task running at the same time by different processes&lt;br /&gt;
&lt;br /&gt;
A result of this is that cron can be run much more often, which means (for example) forum posts can be sent out sooner. Admins can keep cron running at the same schedule as before, but it is strongly recommended that they increase the frequency of running cron to at least once per minute.&lt;br /&gt;
&lt;br /&gt;
===Authentication===&lt;br /&gt;
Manual account authentication can now have password expiry enabled - MDL-42816&lt;br /&gt;
&lt;br /&gt;
A new setting allows users to log in with both their username and their email address - MDL-41115&lt;br /&gt;
&lt;br /&gt;
== Developer Notes ==&lt;br /&gt;
&lt;br /&gt;
=== API changes ===&lt;br /&gt;
* Reports: Reports that use log table, should be updated to use the new logging frame work. Old reports will continue to work as before as long as legacy logging is enabled in the site. See [[Migrating log access in reports]] for details.&lt;br /&gt;
&lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.7]]&lt;br /&gt;
&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.7]]&lt;br /&gt;
[[es:Notas de Moodle 2.7]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=version.php&amp;diff=44046</id>
		<title>version.php</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=version.php&amp;diff=44046"/>
		<updated>2014-03-03T23:57:13Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Clarify the module-&amp;gt; vs plugin-&amp;gt; changes.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This file is included in the main directory of plugin. It is compulsory for most plugin types. Even when not compulsory it is highly recommended. It is required in the Plugins Directory for Moodle 2 and onwards.&lt;br /&gt;
&lt;br /&gt;
It contains a number of fields, which are used during the install / upgrade process to make sure the plugin is compatible with the current Moodle install, as well as spotting whether an upgrade is needed.&lt;br /&gt;
&lt;br /&gt;
The file is a standard PHP file, starting with an opening &#039;&amp;amp;lt;?php&#039; tag and defining the following variables:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Before Moodle 2.6, for activity modules only, &amp;lt;tt&amp;gt;$module-&amp;gt;&amp;lt;/tt&amp;gt; had to be used instead of &amp;lt;tt&amp;gt;$plugin-&amp;gt;&amp;lt;/tt&amp;gt; in the version.php file. Starting in Moodle 2.6, either may be used. Support for &amp;lt;tt&amp;gt;$module-&amp;gt;&amp;lt;/tt&amp;gt; will be dropped with Moodle 2.10. (MDL-43896)&lt;br /&gt;
&lt;br /&gt;
* $plugin-&amp;gt;version = 2011051000;&lt;br /&gt;
** Required - the version number of your plugin in the form YYYYMMDDxx, so this example is 10th May 2011 (with 00 indicating this is the first version for that day)&lt;br /&gt;
* $plugin-&amp;gt;requires = 2010112400;&lt;br /&gt;
** Optional - minimum version number of Moodle that this plugin requires (Moodle 1.9 = 2007101509; Moodle 2.0 = 2010112400; Moodle 2.1 = 2011070100; Moodle 2.2 = 2011120500; Moodle 2.3 = 2012062500). See [[Releases]] for a full list.&lt;br /&gt;
* $plugin-&amp;gt;cron = 0;&lt;br /&gt;
** Optional - time interval (in seconds) between calls to the plugin&#039;s &#039;cron&#039; function; set to 0 to disable the cron function calls.&lt;br /&gt;
** Cron support is not yet implemented for all plugins.&lt;br /&gt;
* $plugin-&amp;gt;component = &#039;plugintype_pluginname&#039;;&lt;br /&gt;
** Optional - &#039;&#039;frankenstyle&#039;&#039; plugin name, strongly recommended. It is used for installation and upgrade diagnostics.&lt;br /&gt;
* $plugin-&amp;gt;maturity = MATURITY_STABLE;&lt;br /&gt;
** Optional - how stable the plugin is: MATURITY_ALPHA, MATURITY_BETA, MATURITY_RC, MATURITY_STABLE (Moodle 2.0 and above)&lt;br /&gt;
* $plugin-&amp;gt;release = &#039;2.x (Build: 2011051000)&#039;;&lt;br /&gt;
** Optional - Human-readable version name&lt;br /&gt;
* $plugin-&amp;gt;dependencies = array(&#039;mod_forum&#039; =&amp;gt; ANY_VERSION, &#039;mod_data&#039;  =&amp;gt; 2010020300);  &lt;br /&gt;
** Optional - list of other plugins that are required for this plugin to work (Moodle 2.2 and above)&lt;br /&gt;
** In this example, the plugin requires any version of the forum activity and version &#039;20100020300&#039; (or above) of the database activity&lt;br /&gt;
&lt;br /&gt;
Here is a template to copy and paste:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// This file is part of Moodle - http://moodle.org/&lt;br /&gt;
//&lt;br /&gt;
// Moodle is free software: you can redistribute it and/or modify&lt;br /&gt;
// it under the terms of the GNU General Public License as published by&lt;br /&gt;
// the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
// (at your option) any later version.&lt;br /&gt;
//&lt;br /&gt;
// Moodle is distributed in the hope that it will be useful,&lt;br /&gt;
// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
// GNU General Public License for more details.&lt;br /&gt;
//&lt;br /&gt;
// You should have received a copy of the GNU General Public License&lt;br /&gt;
// along with Moodle.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * TODO&lt;br /&gt;
 *&lt;br /&gt;
 * @package   TODO_FRANKENSTYLE&lt;br /&gt;
 * @copyright TODO&lt;br /&gt;
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
$plugin-&amp;gt;version   = TODO;&lt;br /&gt;
$plugin-&amp;gt;requires  = TODO; // See https://docs.moodle.org/dev/Moodle_Versions&lt;br /&gt;
$plugin-&amp;gt;cron      = 0;&lt;br /&gt;
$plugin-&amp;gt;component = &#039;TODO_FRANKENSTYLE&#039;;&lt;br /&gt;
$plugin-&amp;gt;maturity  = MATURITY_STABLE;&lt;br /&gt;
$plugin-&amp;gt;release   = &#039;TODO&#039;;&lt;br /&gt;
&lt;br /&gt;
$plugin-&amp;gt;dependencies = array(&lt;br /&gt;
    &#039;mod_forum&#039; =&amp;gt; ANY_VERSION,&lt;br /&gt;
    &#039;mod_data&#039;  =&amp;gt; TODO&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Moodle versions]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43839</id>
		<title>User talk:Eloy Lafuente (stronk7)/Namespaces</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43839"/>
		<updated>2014-02-13T03:00:13Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Comments */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Votes ==&lt;br /&gt;
&lt;br /&gt;
(put your name/known nick under the preferred options)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* U05: TOVOTE: Allowed syntax of imports:&lt;br /&gt;
** A) One import per line is required. ([https://github.com/moodle/moodle/blob/master/mod/assign/feedback/editpdf/locallib.php#L28 example])&lt;br /&gt;
*** (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** B) Multiple imports (comma separated) per line are required. ([https://github.com/moodle/moodle/blob/master/mod/assign/classes/plugininfo/assignsubmission.php#L26 example])&lt;br /&gt;
*** &lt;br /&gt;
** C) Both A) and B) are ok.&lt;br /&gt;
*** (damyon) (david)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* L02: TOVOTE: Level 2 namespace:&lt;br /&gt;
** A) Must be always a [https://docs.moodle.org/dev/Core_APIs valid Moodle API], with an special (to determine) &amp;quot;componentapi&amp;quot;  subspace available for everything else.&lt;br /&gt;
*** (eloy) (david)&lt;br /&gt;
** B) Can be anything except [https://docs.moodle.org/dev/Core_APIs valid Moodle APIs], that are reserved for classes belonging to such APIs. ([https://github.com/moodle/moodle/tree/master/course/classes/management non-api example]), ([https://github.com/moodle/moodle/tree/master/mod/assign/classes/event api example])&lt;br /&gt;
*** (damyon) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** C) No, my friend, there is not C) here, lol.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* A03) TOVOTE: Always apply for the maximum level of detail deciding the namespace for a class (The &amp;quot;event observers&amp;quot; case, currently in a non-normalized way, look for them in code)&lt;br /&gt;
** A) Yes (a class zzzz known to belong to component xxxx, api yyyyy, should be \xxxx\yyyy\zzzz).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (david) (sam) (emerrill)&lt;br /&gt;
** B) No (we can put it elsewhere, as far as it&#039;s autoloaded, who cares). ([https://github.com/moodle/moodle/blob/master/badges/classes/observer.php non-namespaced observer]), ([https://github.com/moodle/moodle/blob/master/mod/quiz/classes/group_observers.php namespaced observer]).&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*S03: TOVOTE: Global scope class backslashing (from namespaced code):&lt;br /&gt;
**A) is always required. ([https://github.com/moodle/moodle/blob/master/lib/classes/event/user_updated.php#L69 moodle global scope example]), ([https://github.com/moodle/moodle/blob/master/lib/classes/update/checker.php#L784 php global scope example]).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
**B) is forbidden (because access is provided by importing the global scope class). ([https://github.com/moodle/moodle/blob/master/lib/classes/plugininfo/mod.php#L26 moodle global scope example])&lt;br /&gt;
*** (david)&lt;br /&gt;
**C) Both A) and B) are ok.&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
== Comments ==&lt;br /&gt;
&lt;br /&gt;
Damo: U05:  Can&#039;t see why we would need to be so prescriptive.&lt;br /&gt;
:Eloy: well, that what the voting is about&lt;br /&gt;
&lt;br /&gt;
Damo:  L02: Even in a core plugin I can see that there could be complex code that would benefit from 2nd level namespaces - e.g. quiz. To ban them and reserve them only for core apis is again - too prescriptive.&lt;br /&gt;
:Eloy: note that option A provides a way for developers to own an entire sub-namespace (&amp;quot;componentapi&amp;quot;) so then can use it and sublevels freely.&lt;br /&gt;
&lt;br /&gt;
Damo: A03  For stuff where this is known it makes sense to standardise.&lt;br /&gt;
&lt;br /&gt;
Damo: S02: A) Because that&#039;s how I&#039;ve done it and I rule.&lt;br /&gt;
:Eloy, LOL&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[User:David Mudrak|David Mudrak]] ([[User talk:David Mudrak|talk]]) 01:03, 13 February 2014 (WST):&lt;br /&gt;
* UO3C - long names would span over multiple lines anyway&lt;br /&gt;
* L02A - because it really sucks to explain to our poor add-on developers that they have to refactorize their code just because we started to like some keyword, too. It&#039;s enough we already suffer from this heritage in activity modules prefixes.&lt;br /&gt;
:Eric: To me, I would rather have the freedom as a contrib dev. Things frequently break between major versions, so that is always a risk. You could provide a know, safe, namespace, but don&#039;t *require* it&#039;s use. I would rather have the shorter namespace and risk a collision that I need to fix down the road. Here is an ([https://github.com/merrill-oakland/mod_webexactivity/tree/master/classes example]) of what I have being doing at the moment.&lt;br /&gt;
::Eloy: Offtopic, and said with love, it&#039;s the 2nd time I hear from you about that &amp;quot;freedom&amp;quot; opinion. And, while I understand your point... I also thinks it&#039;s a bit nonsense: 1) I want freedom in my addons 2) I want to follow coding-standards 3) As far as coding-standards do not provide me freedom 4) I push to change coding-standards to, officially, get freedom. Again, just arguing by the pleasure of arguing (in my side), but up to some degree I see your point that way. Surely wrongly, lol, but hey, it&#039;s my opinion.&lt;br /&gt;
:::Dan: But the problem is I can&#039;t see everything always naturally fitting into a core api and so that is where I/we want freedom. Rather than forcing it under some core api prefix which is not natural. (GRR. WHY do we insist on using the wiki as a discussion tool. Grr. It sucks.)&lt;br /&gt;
&lt;br /&gt;
:::Eric: Eloy - To me, that logic would only make sense if I am to treat Moodle HQ as a black box that stuff comes out of. There was a discussion going on about this topic, so I&#039;ll put in my opinions, and they should be weighted by people for what they are worth (not much). Also, I am not trying to change existing policy - once a the decision is made, I will abide by it. Freedom vs Prescription isn&#039;t black and white, I tend to lean towards freedom. That doesn&#039;t mean I feel there shouldn&#039;t be rules, and plenty of them. I aspire to one day write more than just some patches and contrib modules, and write honest to goodness core code, so I follow these discussions as close as I can, and sometimes participate - I try and walk the fine line between being involved, and being a nuisance (sometimes I fail at that). And I know you argue with love - no Moodle policy discussion would be complete without strong opinions from Eloy :)&lt;br /&gt;
* A03A - it just makes sense&lt;br /&gt;
* S03B - I \just \can&#039;t help \myself but I soooo \dislike these backslashes when looking \at the code. PLEASE. (edit: after spending more time with this, I can understand why to go the A way - such as PHP bug 60022 or following what other frameworks like Symphony, Doctrine or Drupal do). But it still makes me a sad panda :-/)&lt;br /&gt;
:&amp;lt;div id=&amp;quot;the_easter_egg_revealed&amp;quot;&amp;gt;Eloy: hehe, I was aware of that problem from Drupal (only affects php global scope, not moodle global scope, afaik), and that S03B answer is really an easter egg, you found it!. Also, there are at least two more drawbacks with this option. First, its maintenance (to keep the list of imported classes on sync with code, on deletions and so on). And second, it really does not provide any interesting information to know that, along a class, you are going to use, say, stdClass or FileIterator or whatever. And the list can be huge!&lt;br /&gt;
&lt;br /&gt;
p.s. I know it&#039;s out of scope of this issue, but I see it a shame that (generally) namespaces in PHP do not really help to prevent collisions in 3rd libraries. Particularly (again from our add-on developers&#039; POV) I know a case when two add-ons ship with the same 3rd party lib. Namespaces really do not help much if one would like to keep 3rd party libs untouched.&lt;br /&gt;
&lt;br /&gt;
p.p.s. To be completely honest (and I know this will be completely out of scope), I still do not see namespaces much useful in Moodle codebase at all. If we were pretending that Moodle code can be composed by reusable components found here and there, then our namespace would have to start with vendor name, i.e. \Moodle\... This is not our case. And we have established and working conventions (frankenstyle prefix) that solve the collisions well. I can&#039;t stop thinking that this will just add another layer of code style inconsistency (as I am not naive enough to believe that one day we rewrite everything to use namespaces).&lt;br /&gt;
&lt;br /&gt;
:Eloy: I only can agree 100%. Autoloading was the excuse, since then the tree has been growing without control... time will tell if the fruits are tasty or nasty. I&#039;m sceptic too. But fun is guaranteed!&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43838</id>
		<title>User talk:Eloy Lafuente (stronk7)/Namespaces</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43838"/>
		<updated>2014-02-13T02:59:50Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Comments */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Votes ==&lt;br /&gt;
&lt;br /&gt;
(put your name/known nick under the preferred options)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* U05: TOVOTE: Allowed syntax of imports:&lt;br /&gt;
** A) One import per line is required. ([https://github.com/moodle/moodle/blob/master/mod/assign/feedback/editpdf/locallib.php#L28 example])&lt;br /&gt;
*** (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** B) Multiple imports (comma separated) per line are required. ([https://github.com/moodle/moodle/blob/master/mod/assign/classes/plugininfo/assignsubmission.php#L26 example])&lt;br /&gt;
*** &lt;br /&gt;
** C) Both A) and B) are ok.&lt;br /&gt;
*** (damyon) (david)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* L02: TOVOTE: Level 2 namespace:&lt;br /&gt;
** A) Must be always a [https://docs.moodle.org/dev/Core_APIs valid Moodle API], with an special (to determine) &amp;quot;componentapi&amp;quot;  subspace available for everything else.&lt;br /&gt;
*** (eloy) (david)&lt;br /&gt;
** B) Can be anything except [https://docs.moodle.org/dev/Core_APIs valid Moodle APIs], that are reserved for classes belonging to such APIs. ([https://github.com/moodle/moodle/tree/master/course/classes/management non-api example]), ([https://github.com/moodle/moodle/tree/master/mod/assign/classes/event api example])&lt;br /&gt;
*** (damyon) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** C) No, my friend, there is not C) here, lol.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* A03) TOVOTE: Always apply for the maximum level of detail deciding the namespace for a class (The &amp;quot;event observers&amp;quot; case, currently in a non-normalized way, look for them in code)&lt;br /&gt;
** A) Yes (a class zzzz known to belong to component xxxx, api yyyyy, should be \xxxx\yyyy\zzzz).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (david) (sam) (emerrill)&lt;br /&gt;
** B) No (we can put it elsewhere, as far as it&#039;s autoloaded, who cares). ([https://github.com/moodle/moodle/blob/master/badges/classes/observer.php non-namespaced observer]), ([https://github.com/moodle/moodle/blob/master/mod/quiz/classes/group_observers.php namespaced observer]).&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*S03: TOVOTE: Global scope class backslashing (from namespaced code):&lt;br /&gt;
**A) is always required. ([https://github.com/moodle/moodle/blob/master/lib/classes/event/user_updated.php#L69 moodle global scope example]), ([https://github.com/moodle/moodle/blob/master/lib/classes/update/checker.php#L784 php global scope example]).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
**B) is forbidden (because access is provided by importing the global scope class). ([https://github.com/moodle/moodle/blob/master/lib/classes/plugininfo/mod.php#L26 moodle global scope example])&lt;br /&gt;
*** (david)&lt;br /&gt;
**C) Both A) and B) are ok.&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
== Comments ==&lt;br /&gt;
&lt;br /&gt;
Damo: U05:  Can&#039;t see why we would need to be so prescriptive.&lt;br /&gt;
:Eloy: well, that what the voting is about&lt;br /&gt;
&lt;br /&gt;
Damo:  L02: Even in a core plugin I can see that there could be complex code that would benefit from 2nd level namespaces - e.g. quiz. To ban them and reserve them only for core apis is again - too prescriptive.&lt;br /&gt;
:Eloy: note that option A provides a way for developers to own an entire sub-namespace (&amp;quot;componentapi&amp;quot;) so then can use it and sublevels freely.&lt;br /&gt;
&lt;br /&gt;
Damo: A03  For stuff where this is known it makes sense to standardise.&lt;br /&gt;
&lt;br /&gt;
Damo: S02: A) Because that&#039;s how I&#039;ve done it and I rule.&lt;br /&gt;
:Eloy, LOL&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[User:David Mudrak|David Mudrak]] ([[User talk:David Mudrak|talk]]) 01:03, 13 February 2014 (WST):&lt;br /&gt;
* UO3C - long names would span over multiple lines anyway&lt;br /&gt;
* L02A - because it really sucks to explain to our poor add-on developers that they have to refactorize their code just because we started to like some keyword, too. It&#039;s enough we already suffer from this heritage in activity modules prefixes.&lt;br /&gt;
:Eric: To me, I would rather have the freedom as a contrib dev. Things frequently break between major versions, so that is always a risk. You could provide a know, safe, namespace, but don&#039;t *require* it&#039;s use. I would rather have the shorter namespace and risk a collision that I need to fix down the road. Here is an ([https://github.com/merrill-oakland/mod_webexactivity/tree/master/classes example]) of what I have being doing at the moment.&lt;br /&gt;
::Eloy: Offtopic, and said with love, it&#039;s the 2nd time I hear from you about that &amp;quot;freedom&amp;quot; opinion. And, while I understand your point... I also thinks it&#039;s a bit nonsense: 1) I want freedom in my addons 2) I want to follow coding-standards 3) As far as coding-standards do not provide me freedom 4) I push to change coding-standards to, officially, get freedom. Again, just arguing by the pleasure of arguing (in my side), but up to some degree I see your point that way. Surely wrongly, lol, but hey, it&#039;s my opinion.&lt;br /&gt;
:::Dan: But the problem is I can&#039;t see everything always naturally fitting into a core api and so that is where I/we want freedom. Rather than forcing it under some core api prefix which is not natural. (GRR. WHY do we insist on using the wiki as a discussion tool. Grr. It sucks.)&lt;br /&gt;
:::Eric: Eloy - To me, that logic would only make sense if I am to treat Moodle HQ as a black box that stuff comes out of. There was a discussion going on about this topic, so I&#039;ll put in my opinions, and they should be weighted by people for what they are worth (not much). Also, I am not trying to change existing policy - once a the decision is made, I will abide by it. Freedom vs Prescription isn&#039;t black and white, I tend to lean towards freedom. That doesn&#039;t mean I feel there shouldn&#039;t be rules, and plenty of them. I aspire to one day write more than just some patches and contrib modules, and write honest to goodness core code, so I follow these discussions as close as I can, and sometimes participate - I try and walk the fine line between being involved, and being a nuisance (sometimes I fail at that). And I know you argue with love - no Moodle policy discussion would be complete without strong opinions from Eloy :)&lt;br /&gt;
* A03A - it just makes sense&lt;br /&gt;
* S03B - I \just \can&#039;t help \myself but I soooo \dislike these backslashes when looking \at the code. PLEASE. (edit: after spending more time with this, I can understand why to go the A way - such as PHP bug 60022 or following what other frameworks like Symphony, Doctrine or Drupal do). But it still makes me a sad panda :-/)&lt;br /&gt;
:&amp;lt;div id=&amp;quot;the_easter_egg_revealed&amp;quot;&amp;gt;Eloy: hehe, I was aware of that problem from Drupal (only affects php global scope, not moodle global scope, afaik), and that S03B answer is really an easter egg, you found it!. Also, there are at least two more drawbacks with this option. First, its maintenance (to keep the list of imported classes on sync with code, on deletions and so on). And second, it really does not provide any interesting information to know that, along a class, you are going to use, say, stdClass or FileIterator or whatever. And the list can be huge!&lt;br /&gt;
&lt;br /&gt;
p.s. I know it&#039;s out of scope of this issue, but I see it a shame that (generally) namespaces in PHP do not really help to prevent collisions in 3rd libraries. Particularly (again from our add-on developers&#039; POV) I know a case when two add-ons ship with the same 3rd party lib. Namespaces really do not help much if one would like to keep 3rd party libs untouched.&lt;br /&gt;
&lt;br /&gt;
p.p.s. To be completely honest (and I know this will be completely out of scope), I still do not see namespaces much useful in Moodle codebase at all. If we were pretending that Moodle code can be composed by reusable components found here and there, then our namespace would have to start with vendor name, i.e. \Moodle\... This is not our case. And we have established and working conventions (frankenstyle prefix) that solve the collisions well. I can&#039;t stop thinking that this will just add another layer of code style inconsistency (as I am not naive enough to believe that one day we rewrite everything to use namespaces).&lt;br /&gt;
&lt;br /&gt;
:Eloy: I only can agree 100%. Autoloading was the excuse, since then the tree has been growing without control... time will tell if the fruits are tasty or nasty. I&#039;m sceptic too. But fun is guaranteed!&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43829</id>
		<title>User talk:Eloy Lafuente (stronk7)/Namespaces</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43829"/>
		<updated>2014-02-12T20:23:04Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Comments */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Votes ==&lt;br /&gt;
&lt;br /&gt;
(put your name/known nick under the preferred options)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* U05: TOVOTE: Allowed syntax of imports:&lt;br /&gt;
** A) One import per line is required. ([https://github.com/moodle/moodle/blob/master/mod/assign/feedback/editpdf/locallib.php#L28 example])&lt;br /&gt;
*** (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** B) Multiple imports (comma separated) per line are required. ([https://github.com/moodle/moodle/blob/master/mod/assign/classes/plugininfo/assignsubmission.php#L26 example])&lt;br /&gt;
*** &lt;br /&gt;
** C) Both A) and B) are ok.&lt;br /&gt;
*** (damyon) (david)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* L02: TOVOTE: Level 2 namespace:&lt;br /&gt;
** A) Must be always a [https://docs.moodle.org/dev/Core_APIs valid Moodle API], with an special (to determine) &amp;quot;componentapi&amp;quot;  subspace available for everything else.&lt;br /&gt;
*** (eloy) (david)&lt;br /&gt;
** B) Can be anything except [https://docs.moodle.org/dev/Core_APIs valid Moodle APIs], that are reserved for classes belonging to such APIs. ([https://github.com/moodle/moodle/tree/master/course/classes/management non-api example]), ([https://github.com/moodle/moodle/tree/master/mod/assign/classes/event api example])&lt;br /&gt;
*** (damyon) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** C) No, my friend, there is not C) here, lol.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* A03) TOVOTE: Always apply for the maximum level of detail deciding the namespace for a class (The &amp;quot;event observers&amp;quot; case, currently in a non-normalized way, look for them in code)&lt;br /&gt;
** A) Yes (a class zzzz known to belong to component xxxx, api yyyyy, should be \xxxx\yyyy\zzzz).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (david) (sam) (emerrill)&lt;br /&gt;
** B) No (we can put it elsewhere, as far as it&#039;s autoloaded, who cares). ([https://github.com/moodle/moodle/blob/master/badges/classes/observer.php non-namespaced observer]), ([https://github.com/moodle/moodle/blob/master/mod/quiz/classes/group_observers.php namespaced observer]).&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*S03: TOVOTE: Global scope class backslashing (from namespaced code):&lt;br /&gt;
**A) is always required. ([https://github.com/moodle/moodle/blob/master/lib/classes/event/user_updated.php#L69 moodle global scope example]), ([https://github.com/moodle/moodle/blob/master/lib/classes/update/checker.php#L784 php global scope example]).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
**B) is forbidden (because access is provided by importing the global scope class). ([https://github.com/moodle/moodle/blob/master/lib/classes/plugininfo/mod.php#L26 moodle global scope example])&lt;br /&gt;
*** (david)&lt;br /&gt;
**C) Both A) and B) are ok.&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
== Comments ==&lt;br /&gt;
&lt;br /&gt;
Damo: U05:  Can&#039;t see why we would need to be so prescriptive.&lt;br /&gt;
:Eloy: well, that what the voting is about&lt;br /&gt;
&lt;br /&gt;
Damo:  L02: Even in a core plugin I can see that there could be complex code that would benefit from 2nd level namespaces - e.g. quiz. To ban them and reserve them only for core apis is again - too prescriptive.&lt;br /&gt;
:Eloy: note that option A provides a way for developers to own an entire sub-namespace (&amp;quot;componentapi&amp;quot;) so then can use it and sublevels freely.&lt;br /&gt;
&lt;br /&gt;
Damo: A03  For stuff where this is known it makes sense to standardise.&lt;br /&gt;
&lt;br /&gt;
Damo: S02: A) Because that&#039;s how I&#039;ve done it and I rule.&lt;br /&gt;
:Eloy, LOL&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[User:David Mudrak|David Mudrak]] ([[User talk:David Mudrak|talk]]) 01:03, 13 February 2014 (WST):&lt;br /&gt;
* UO3C - long names would span over multiple lines anyway&lt;br /&gt;
* L02A - because it really sucks to explain to our poor add-on developers that they have to refactorize their code just because we started to like some keyword, too. It&#039;s enough we already suffer from this heritage in activity modules prefixes.&lt;br /&gt;
:Eric: To me, I would rather have the freedom as a contrib dev. Things frequently break between major versions, so that is always a risk. You could provide a know, safe, namespace, but don&#039;t *require* it&#039;s use. I would rather have the shorter namespace and risk a collision that I need to fix down the road. Here is an ([https://github.com/merrill-oakland/mod_webexactivity/tree/master/classes example]) of what I have being doing at the moment.&lt;br /&gt;
* A03A - it just makes sense&lt;br /&gt;
* S03B - I \just \can&#039;t help \myself but I soooo \dislike these backslashes when looking \at the code. PLEASE. (edit: after spending more time with this, I can understand why to go the A way - such as PHP bug 60022 or following what other frameworks like Symphony, Doctrine or Drupal do). But it still makes me a sad panda :-/)&lt;br /&gt;
&lt;br /&gt;
p.s. I know it&#039;s out of scope of this issue, but I see it a shame that (generally) namespaces in PHP do not really help to prevent collisions in 3rd libraries. Particularly (again from our add-on developers&#039; POV) I know a case when two add-ons ship with the same 3rd party lib. Namespaces really do not help much if one would like to keep 3rd party libs untouched.&lt;br /&gt;
&lt;br /&gt;
p.p.s. To be completely honest (and I know this will be completely out of scope), I still do not see namespaces much useful in Moodle codebase at all. If we were pretending that Moodle code can be composed by reusable components found here and there, then our namespace would have to start with vendor name, i.e. \Moodle\... This is not our case. And we have established and working conventions (frankenstyle prefix) that solve the collisions well. I can&#039;t stop thinking that this will just add another layer of code style inconsistency (as I am not naive enough to believe that one day we rewrite everything to use namespaces).&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43828</id>
		<title>User talk:Eloy Lafuente (stronk7)/Namespaces</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43828"/>
		<updated>2014-02-12T20:19:50Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Comments */ My thoughts about L02&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Votes ==&lt;br /&gt;
&lt;br /&gt;
(put your name/known nick under the preferred options)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* U05: TOVOTE: Allowed syntax of imports:&lt;br /&gt;
** A) One import per line is required. ([https://github.com/moodle/moodle/blob/master/mod/assign/feedback/editpdf/locallib.php#L28 example])&lt;br /&gt;
*** (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** B) Multiple imports (comma separated) per line are required. ([https://github.com/moodle/moodle/blob/master/mod/assign/classes/plugininfo/assignsubmission.php#L26 example])&lt;br /&gt;
*** &lt;br /&gt;
** C) Both A) and B) are ok.&lt;br /&gt;
*** (damyon) (david)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* L02: TOVOTE: Level 2 namespace:&lt;br /&gt;
** A) Must be always a [https://docs.moodle.org/dev/Core_APIs valid Moodle API], with an special (to determine) &amp;quot;componentapi&amp;quot;  subspace available for everything else.&lt;br /&gt;
*** (eloy) (david)&lt;br /&gt;
** B) Can be anything except [https://docs.moodle.org/dev/Core_APIs valid Moodle APIs], that are reserved for classes belonging to such APIs. ([https://github.com/moodle/moodle/tree/master/course/classes/management non-api example]), ([https://github.com/moodle/moodle/tree/master/mod/assign/classes/event api example])&lt;br /&gt;
*** (damyon) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** C) No, my friend, there is not C) here, lol.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* A03) TOVOTE: Always apply for the maximum level of detail deciding the namespace for a class (The &amp;quot;event observers&amp;quot; case, currently in a non-normalized way, look for them in code)&lt;br /&gt;
** A) Yes (a class zzzz known to belong to component xxxx, api yyyyy, should be \xxxx\yyyy\zzzz).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (david) (sam) (emerrill)&lt;br /&gt;
** B) No (we can put it elsewhere, as far as it&#039;s autoloaded, who cares). ([https://github.com/moodle/moodle/blob/master/badges/classes/observer.php non-namespaced observer]), ([https://github.com/moodle/moodle/blob/master/mod/quiz/classes/group_observers.php namespaced observer]).&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*S03: TOVOTE: Global scope class backslashing (from namespaced code):&lt;br /&gt;
**A) is always required. ([https://github.com/moodle/moodle/blob/master/lib/classes/event/user_updated.php#L69 moodle global scope example]), ([https://github.com/moodle/moodle/blob/master/lib/classes/update/checker.php#L784 php global scope example]).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
**B) is forbidden (because access is provided by importing the global scope class). ([https://github.com/moodle/moodle/blob/master/lib/classes/plugininfo/mod.php#L26 moodle global scope example])&lt;br /&gt;
*** (david)&lt;br /&gt;
**C) Both A) and B) are ok.&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
== Comments ==&lt;br /&gt;
&lt;br /&gt;
Damo: U05:  Can&#039;t see why we would need to be so prescriptive.&lt;br /&gt;
:Eloy: well, that what the voting is about&lt;br /&gt;
&lt;br /&gt;
Damo:  L02: Even in a core plugin I can see that there could be complex code that would benefit from 2nd level namespaces - e.g. quiz. To ban them and reserve them only for core apis is again - too prescriptive.&lt;br /&gt;
:Eloy: note that option A provides a way for developers to own an entire sub-namespace (&amp;quot;componentapi&amp;quot;) so then can use it and sublevels freely.&lt;br /&gt;
&lt;br /&gt;
Damo: A03  For stuff where this is known it makes sense to standardise.&lt;br /&gt;
&lt;br /&gt;
Damo: S02: A) Because that&#039;s how I&#039;ve done it and I rule.&lt;br /&gt;
:Eloy, LOL&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[User:David Mudrak|David Mudrak]] ([[User talk:David Mudrak|talk]]) 01:03, 13 February 2014 (WST):&lt;br /&gt;
* UO3C - long names would span over multiple lines anyway&lt;br /&gt;
* L02A - because it really sucks to explain to our poor add-on developers that they have to refactorize their code just because we started to like some keyword, too. It&#039;s enough we already suffer from this heritage in activity modules prefixes.&lt;br /&gt;
:Eric: To me, I would rather have the freedom as a contrib dev. Things frequently break between major versions, so that is always a risk. You could provide a know, safe, namespace, but don&#039;t *require* it&#039;s use. I would rather have the shorter namespace and risk a collision that I need to fix down the road.&lt;br /&gt;
* A03A - it just makes sense&lt;br /&gt;
* S03B - I \just \can&#039;t help \myself but I soooo \dislike these backslashes when looking \at the code. PLEASE. (edit: after spending more time with this, I can understand why to go the A way - such as PHP bug 60022 or following what other frameworks like Symphony, Doctrine or Drupal do). But it still makes me a sad panda :-/)&lt;br /&gt;
&lt;br /&gt;
p.s. I know it&#039;s out of scope of this issue, but I see it a shame that (generally) namespaces in PHP do not really help to prevent collisions in 3rd libraries. Particularly (again from our add-on developers&#039; POV) I know a case when two add-ons ship with the same 3rd party lib. Namespaces really do not help much if one would like to keep 3rd party libs untouched.&lt;br /&gt;
&lt;br /&gt;
p.p.s. To be completely honest (and I know this will be completely out of scope), I still do not see namespaces much useful in Moodle codebase at all. If we were pretending that Moodle code can be composed by reusable components found here and there, then our namespace would have to start with vendor name, i.e. \Moodle\... This is not our case. And we have established and working conventions (frankenstyle prefix) that solve the collisions well. I can&#039;t stop thinking that this will just add another layer of code style inconsistency (as I am not naive enough to believe that one day we rewrite everything to use namespaces).&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43827</id>
		<title>User talk:Eloy Lafuente (stronk7)/Namespaces</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User_talk:Eloy_Lafuente_(stronk7)/Namespaces&amp;diff=43827"/>
		<updated>2014-02-12T20:17:12Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Votes */ My votes. Feel free to ignore if you just want HQ/core voters&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Votes ==&lt;br /&gt;
&lt;br /&gt;
(put your name/known nick under the preferred options)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* U05: TOVOTE: Allowed syntax of imports:&lt;br /&gt;
** A) One import per line is required. ([https://github.com/moodle/moodle/blob/master/mod/assign/feedback/editpdf/locallib.php#L28 example])&lt;br /&gt;
*** (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** B) Multiple imports (comma separated) per line are required. ([https://github.com/moodle/moodle/blob/master/mod/assign/classes/plugininfo/assignsubmission.php#L26 example])&lt;br /&gt;
*** &lt;br /&gt;
** C) Both A) and B) are ok.&lt;br /&gt;
*** (damyon) (david)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* L02: TOVOTE: Level 2 namespace:&lt;br /&gt;
** A) Must be always a [https://docs.moodle.org/dev/Core_APIs valid Moodle API], with an special (to determine) &amp;quot;componentapi&amp;quot;  subspace available for everything else.&lt;br /&gt;
*** (eloy) (david)&lt;br /&gt;
** B) Can be anything except [https://docs.moodle.org/dev/Core_APIs valid Moodle APIs], that are reserved for classes belonging to such APIs. ([https://github.com/moodle/moodle/tree/master/course/classes/management non-api example]), ([https://github.com/moodle/moodle/tree/master/mod/assign/classes/event api example])&lt;br /&gt;
*** (damyon) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
** C) No, my friend, there is not C) here, lol.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* A03) TOVOTE: Always apply for the maximum level of detail deciding the namespace for a class (The &amp;quot;event observers&amp;quot; case, currently in a non-normalized way, look for them in code)&lt;br /&gt;
** A) Yes (a class zzzz known to belong to component xxxx, api yyyyy, should be \xxxx\yyyy\zzzz).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (david) (sam) (emerrill)&lt;br /&gt;
** B) No (we can put it elsewhere, as far as it&#039;s autoloaded, who cares). ([https://github.com/moodle/moodle/blob/master/badges/classes/observer.php non-namespaced observer]), ([https://github.com/moodle/moodle/blob/master/mod/quiz/classes/group_observers.php namespaced observer]).&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*S03: TOVOTE: Global scope class backslashing (from namespaced code):&lt;br /&gt;
**A) is always required. ([https://github.com/moodle/moodle/blob/master/lib/classes/event/user_updated.php#L69 moodle global scope example]), ([https://github.com/moodle/moodle/blob/master/lib/classes/update/checker.php#L784 php global scope example]).&lt;br /&gt;
*** (damyon) (eloy) (tim) (dan) (sam) (emerrill)&lt;br /&gt;
**B) is forbidden (because access is provided by importing the global scope class). ([https://github.com/moodle/moodle/blob/master/lib/classes/plugininfo/mod.php#L26 moodle global scope example])&lt;br /&gt;
*** (david)&lt;br /&gt;
**C) Both A) and B) are ok.&lt;br /&gt;
***&lt;br /&gt;
&lt;br /&gt;
== Comments ==&lt;br /&gt;
&lt;br /&gt;
Damo: U05:  Can&#039;t see why we would need to be so prescriptive.&lt;br /&gt;
:Eloy: well, that what the voting is about&lt;br /&gt;
&lt;br /&gt;
Damo:  L02: Even in a core plugin I can see that there could be complex code that would benefit from 2nd level namespaces - e.g. quiz. To ban them and reserve them only for core apis is again - too prescriptive.&lt;br /&gt;
:Eloy: note that option A provides a way for developers to own an entire sub-namespace (&amp;quot;componentapi&amp;quot;) so then can use it and sublevels freely.&lt;br /&gt;
&lt;br /&gt;
Damo: A03  For stuff where this is known it makes sense to standardise.&lt;br /&gt;
&lt;br /&gt;
Damo: S02: A) Because that&#039;s how I&#039;ve done it and I rule.&lt;br /&gt;
:Eloy, LOL&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[User:David Mudrak|David Mudrak]] ([[User talk:David Mudrak|talk]]) 01:03, 13 February 2014 (WST):&lt;br /&gt;
* UO3C - long names would span over multiple lines anyway&lt;br /&gt;
* L02A - because it really sucks to explain to our poor add-on developers that they have to refactorize their code just because we started to like some keyword, too. It&#039;s enough we already suffer from this heritage in activity modules prefixes.&lt;br /&gt;
* A03A - it just makes sense&lt;br /&gt;
* S03B - I \just \can&#039;t help \myself but I soooo \dislike these backslashes when looking \at the code. PLEASE. (edit: after spending more time with this, I can understand why to go the A way - such as PHP bug 60022 or following what other frameworks like Symphony, Doctrine or Drupal do). But it still makes me a sad panda :-/)&lt;br /&gt;
&lt;br /&gt;
p.s. I know it&#039;s out of scope of this issue, but I see it a shame that (generally) namespaces in PHP do not really help to prevent collisions in 3rd libraries. Particularly (again from our add-on developers&#039; POV) I know a case when two add-ons ship with the same 3rd party lib. Namespaces really do not help much if one would like to keep 3rd party libs untouched.&lt;br /&gt;
&lt;br /&gt;
p.p.s. To be completely honest (and I know this will be completely out of scope), I still do not see namespaces much useful in Moodle codebase at all. If we were pretending that Moodle code can be composed by reusable components found here and there, then our namespace would have to start with vendor name, i.e. \Moodle\... This is not our case. And we have established and working conventions (frankenstyle prefix) that solve the collisions well. I can&#039;t stop thinking that this will just add another layer of code style inconsistency (as I am not naive enough to believe that one day we rewrite everything to use namespaces).&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Events_API&amp;diff=43734</id>
		<title>Events API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Events_API&amp;diff=43734"/>
		<updated>2014-02-06T15:40:00Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Information contained in events */ Reflect changes in MDL-43661, level became edulevel&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Events 2&lt;br /&gt;
|state = Implementation in progress&lt;br /&gt;
|tracker = MDL-39797 , MDL-39952, MDL-39846&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=229425&lt;br /&gt;
|assignee = Backend Team&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= What are events? =&lt;br /&gt;
&lt;br /&gt;
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 [[:en:Cron|cron]] process or administration actions [[:en:Administration via command line|undertaken via the command line]].&lt;br /&gt;
&lt;br /&gt;
When an action takes place, an event is created by a [[Core APIs|core API]] or [[Plugins|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.&lt;br /&gt;
&lt;br /&gt;
Event observers can not modify event data or interrupt the dispatching of events, it is a one way communication channel.&lt;br /&gt;
&lt;br /&gt;
= Why is a new events system needed? =&lt;br /&gt;
&lt;br /&gt;
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 will be useful to other parts of Moodle that observe event information.&lt;br /&gt;
&lt;br /&gt;
* The events need to be more strictly defined if we want to use them for new logging and other advanced use cases. They need to contain a lot more information in a standardised way (such as most fields from current log table and log_actions table).&lt;br /&gt;
* Complex data types were allowed in old events which was causing major problems when serialising/storing/unserializing the data.&lt;br /&gt;
* The logging and events contain similar information and are tiggered at the same places, new events would remove this code duplication. All events should be loggable and all current log entries should be triggered as events.&lt;br /&gt;
* The logging system will become an event observer, listening to all events and directing them to logging storage plugins in a controllable way.&lt;br /&gt;
* It will be possible to subscribe to &#039;*&#039; event, which would allow a system to potentially observe, and selectively deal with, all events. Current handlers do not get event name which makes this problematic.&lt;br /&gt;
* Current event handlers may trigger exceptions during site upgrade which would lead to fatal upgrade problems. The new design eliminates this.&lt;br /&gt;
* Failure in handlers blocked dispatching of subsequent events. Instead problems in new observers would be only logged and execution would continue normally.&lt;br /&gt;
* Current execution of external handlers during DB transactions blocks other handlers. This would be eliminated by in-memory buffer for external events.&lt;br /&gt;
* It would good to have observer priority.&lt;br /&gt;
* Nested events are not dispatched sequentially, it would change the order of events received in lower priority handlers.&lt;br /&gt;
&lt;br /&gt;
= Performance =&lt;br /&gt;
Some basic profiling has been conducted.&lt;br /&gt;
&lt;br /&gt;
There is a general plan to complete pre- and post-implementation testing as development happens. The new system will be imemented in parallel with the old one which should help with comparison of new and old logging and event triggering performance on each page.&lt;br /&gt;
&lt;br /&gt;
Our aim is to trigger more events and log more information, which is going to impact 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.&lt;br /&gt;
&lt;br /&gt;
More details will be added to this section soon.&lt;br /&gt;
&lt;br /&gt;
= Events API =&lt;br /&gt;
&lt;br /&gt;
Each plugin will define the events that it can report (trigger) by extending an abstract base class, once for each possible event. This approach will have several benefits.&lt;br /&gt;
; Events will be active objects&lt;br /&gt;
: When they are triggered and possibly after they are reinstantiated (say, when they are retrieved from a log), an event object will be able to provide callback functions for various purposes (such as capability checks).&lt;br /&gt;
; Automatic inclusion&lt;br /&gt;
: Event class definitions will be automatically included when needed, without having to maintain lists of known event types. New event definitions can be added without the need to upgrade, only purging of MUC cache is required after adding new observer.&lt;br /&gt;
; Maintainability&lt;br /&gt;
: It will be easy to add new events and migrate existing events. Code review will be simplified because there will be less duplication of code when triggering events and all event related information/code will be concentrated in one file in fixed locations.&lt;br /&gt;
; Self documenting&lt;br /&gt;
: The behaviour of events will be combined with the definition of events in one place (file). It will be easy for event observer writers to know what events a plugin can trigger. This includes support for autocompletion and code inspection in modern IDEs.&lt;br /&gt;
; Quick, self-validating data structure&lt;br /&gt;
: 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 common typos.&lt;br /&gt;
&lt;br /&gt;
== Backwards compatibility and migration ==&lt;br /&gt;
&lt;br /&gt;
Events:&lt;br /&gt;
* Moodle core and standard plugins will replace all calls to the events_trigger() function with new events classes.&lt;br /&gt;
* For events that already exist in Moodle 2.5 the additional legacy information should be added to the event data (in properties &#039;legacyeventname&#039; and &#039;legacyeventdata&#039;.&lt;br /&gt;
* The function events_trigger() will continue working as before, but it will be called automatically after a new event is processed using the &#039;legacyeventname&#039; and &#039;legacyeventdata&#039;.&lt;br /&gt;
* The legacy events handling code will be maintained  separately and will continue being supported in Moodle 2.x. New legacy events will not be added.&lt;br /&gt;
* Existing legacy event handlers will be migrated to new event handlers accepting new event class instances.&lt;br /&gt;
* More subsystems may be migrated to events-handlers, ex.: gradebook history.&lt;br /&gt;
&lt;br /&gt;
Logging:&lt;br /&gt;
* Function add_to_log() and all logging internals will continue working as before.&lt;br /&gt;
* Existing add_to_log() parameters will be migrated inside new events method get_legacy_log_data() and core_event_base::trigger() will call add_to_log() automatically (this will depend on $CFG-&amp;gt;loglifetime setting for performance reasons).&lt;br /&gt;
&lt;br /&gt;
== Event dispatching and observers ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Event observers ===&lt;br /&gt;
&lt;br /&gt;
The 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;&lt;br /&gt;
* eventname - event class name or &amp;quot;*&amp;quot; indicating all events. All events must use namespace, ex.: &#039;&#039;\plugintype_pluginname\event\something_happened&#039;&#039;.&lt;br /&gt;
* callback - PHP callable type.&lt;br /&gt;
* includefile - optional. File to be included before calling the observer. Path relative to dirroot.&lt;br /&gt;
* priority - optional. Defaults to 0. Observers with higher priority are notified first.&lt;br /&gt;
* internal - optional. Defaults to true. Non-internal observers are not called during database transactions, but instead after a successful commit of the transaction.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
$observers = array(&lt;br /&gt;
&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039;   =&amp;gt; &#039;\core\event\sample_executed&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; &#039;core_event_sample_observer::observe_one&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039;   =&amp;gt; &#039;\core\event\sample_executed&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; &#039;core_event_sample_observer::external_observer&#039;,&lt;br /&gt;
        &#039;priority&#039;    =&amp;gt; 200,&lt;br /&gt;
        &#039;internal&#039;    =&amp;gt; false,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039;   =&amp;gt; &#039;*&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; &#039;core_event_sample_observer::observe_all&#039;,&lt;br /&gt;
        &#039;includefile&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;internal&#039;    =&amp;gt; true,&lt;br /&gt;
        &#039;priority&#039;    =&amp;gt; 9999,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Event dispatching ===&lt;br /&gt;
&lt;br /&gt;
A list of available observers is constructed on the fly directly from all available events.php files. Previously handlers were installed only during installation and upgrade. There is no 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.&lt;br /&gt;
&lt;br /&gt;
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. Current handlers must not throw any exceptions at any time.&lt;br /&gt;
&lt;br /&gt;
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 are notified.&lt;br /&gt;
&lt;br /&gt;
=== Differences from old event handling ===&lt;br /&gt;
&lt;br /&gt;
# New events contain a lot more structured information.&lt;br /&gt;
# New event data must not contain any PHP classes.&lt;br /&gt;
# There is separate context cache which may be used when deleting data or for observer performance improvements.&lt;br /&gt;
# No database access in new event dispatching code.&lt;br /&gt;
# There is no support for cron execution - this eliminates performance problems, simplifies events dispatching and prevents abuse of cron events.&lt;br /&gt;
# Events triggered in observers are processed in a different order.&lt;br /&gt;
# External events are buffered when a transaction is in progress, instead of being sent to the cron queue.&lt;br /&gt;
# It is possible to define multiple observers for one event in one events.php file.&lt;br /&gt;
# It is possible to subscribe an observer to all events.&lt;br /&gt;
# The new event manager is using frankenstyle autoloading - smaller memory footprint when events are not used on the current page.&lt;br /&gt;
&lt;br /&gt;
== Triggering events ==&lt;br /&gt;
&lt;br /&gt;
* All event descriptions are objects extending the \core\event\base class.&lt;br /&gt;
* Events are triggered by creating a new instance of the class event and executing $event-&amp;gt;trigger().&lt;br /&gt;
* Each event class name is a unique identifier of the event.&lt;br /&gt;
* Class names and namespace follow the identifier scheme \&#039;&#039;&#039;frankenstyle_component&#039;&#039;&#039;\event\&#039;&#039;&#039;some_object_action&#039;&#039;&#039;. Core events have prefix &#039;core_&#039;.&lt;br /&gt;
* Plugins define each event class in a separate file. File name and location must match the class name, for example: &#039;&#039;&#039;plugindir&#039;&#039;&#039;/classes/event/&#039;&#039;&#039;something_happened&#039;&#039;&#039;.php&lt;br /&gt;
* The event identifier suffix has the form &#039;&#039;some_object_action&#039;&#039;  (&#039;&#039;&#039;something_happened&#039;&#039;&#039; in the example above) and should follow our standard naming convention. The last word after underscore is automatically parsed as action, the rest of words is object.&lt;br /&gt;
&lt;br /&gt;
Decision:[[#Verb_list| Recommended verb list]]&lt;br /&gt;
&lt;br /&gt;
Examples: \core\event\course_completed, \mod_assign\event\submission_commented, \mod_forum\event\post_shared, \mod_forum\event\post_responded, etc.&lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
An example of triggering an event:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$event = \mod_myplugin\event\something_happened::create(array(&#039;context&#039; =&amp;gt; $context, &#039;objectid&#039; =&amp;gt; YYY, &#039;other&#039; =&amp;gt; ZZZ));&lt;br /&gt;
// ... code that may add some record snapshots&lt;br /&gt;
$event-&amp;gt;trigger();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Use of php autoloading ===&lt;br /&gt;
&lt;br /&gt;
All new OOP APIs in Moodle 2.6 are going to use class auto loading - see [[Automatic class loading]]. New events use PHP strictly defined namespaces which concentrate all events classes in classes/event/ subdirectory.&lt;br /&gt;
&lt;br /&gt;
=== Why separate classes? ===&lt;br /&gt;
There were two alternatives proposed on how to define the event structure. The first is a separate class for each event (extending the base class), the other being each event is based on a generic event instance of the base class.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Decision: Use separate class for each event.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Each plugin creates its own event class for each event&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pros&lt;br /&gt;
* Maintainability - It is much easier to review, debug, integrate.&lt;br /&gt;
* Self documenting, behaviour is combined with definition.&lt;br /&gt;
* It is extremely flexible for plugin developers and core devs too.&lt;br /&gt;
* Automatically lists events without being installed - PHPDocs as events documentation.&lt;br /&gt;
* It is included only when needed using autoloading.&lt;br /&gt;
* Self-validating data structure (by PHP).&lt;br /&gt;
* Some developers will find it easier to copy whole class files as templates.&lt;br /&gt;
Cons&lt;br /&gt;
* 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).&lt;br /&gt;
* 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).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Each plugin defines events in a list based on a generic object&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pros&lt;br /&gt;
* Easier for some developers (this can be eliminated with developer documentation).&lt;br /&gt;
* Some developers think it gives more control of event structure in core (we can easily solve that with private access and validation in the base class).&lt;br /&gt;
Cons&lt;br /&gt;
* It is not flexible enough. PHP code gives developers more freedom.&lt;br /&gt;
* It would not be possible to implement any performance hacks in custom methods. All data would have to be calculated even if it is not used.&lt;br /&gt;
* It would be necessary to define access control callbacks in other code.&lt;br /&gt;
* It would be harder and slower to integrate legacy logging.&lt;br /&gt;
* It would be harder and slower to implement support for legacy events.&lt;br /&gt;
* Event observers could not use event class names as reliable identifiers.&lt;br /&gt;
* Event object and action could not be parsed from class name, it would have to be stored in event properties every time you trigger event.&lt;br /&gt;
* Requires upgrade/install to register an event in DB table with MUC cache. Events could not be triggered earlier.&lt;br /&gt;
* The localised descriptions and names would have to be stored as properties, it would not be possible to store them in any definition.&lt;br /&gt;
* The implementation of an events infrastructure would be significantly more complex and error prone.&lt;br /&gt;
&lt;br /&gt;
== Information contained in events ==&lt;br /&gt;
&lt;br /&gt;
Events have to contain as much information as they can, but this should not affect the performance. That&#039;s why part of the information is available in properties, and the rest via methods. This allows for delaying the computation of the data at the time it is really needed, if it ever is.&lt;br /&gt;
&lt;br /&gt;
=== Properties ===&lt;br /&gt;
&lt;br /&gt;
List of properties that the developer has to pass to the event upon creation, or automatically generated when possible and cost free. Some of those properties not mandatory.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property name&lt;br /&gt;
! Title&lt;br /&gt;
! Type&lt;br /&gt;
! Comment&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;eventname&#039;&#039;&lt;br /&gt;
| Event name&lt;br /&gt;
| &#039;&#039;static, automatic from class name&#039;&#039;&lt;br /&gt;
| Automatically computed by copying class name&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;component&#039;&#039;&lt;br /&gt;
| Component&lt;br /&gt;
| &#039;&#039;static, automatic from top namespace&#039;&#039;&lt;br /&gt;
| Component declaring the event, automatically computed from class name.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;action&#039;&#039;&lt;br /&gt;
| Action&lt;br /&gt;
| &#039;&#039;static, automatic from last word in class name&#039;&#039;&lt;br /&gt;
| Can be automatically computed from class name.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;target&#039;&#039;&lt;br /&gt;
| target of action&lt;br /&gt;
| &#039;&#039;static, automatic from class name&#039;&#039;&lt;br /&gt;
| Target on which the action is taken, can be automatically computed from class name.&lt;br /&gt;
|-&lt;br /&gt;
| objecttable&lt;br /&gt;
| Database table name&lt;br /&gt;
|&lt;br /&gt;
| optional database table name where is/was the object stored. Never use a relationship table here.&lt;br /&gt;
|-&lt;br /&gt;
| objectid&lt;br /&gt;
| Object ID&lt;br /&gt;
|&lt;br /&gt;
| optional id of the object record from objecttable&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&#039;&#039;crud&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
| Transaction type&lt;br /&gt;
| &#039;&#039;static mandatory&#039;&#039;&lt;br /&gt;
| One of [crud] letters. Statically declared in the event class method init().&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&#039;&#039;level&#039;&#039;&#039;&#039;&#039; / &#039;&#039;&#039;&#039;&#039;edulevel&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
| Level&lt;br /&gt;
| &#039;&#039;static mandatory&#039;&#039;&lt;br /&gt;
| Level of educational value of the event. Statically declared in the event class method init(). Changed from &#039;&#039;&#039;&#039;&#039;level&#039;&#039;&#039;&#039;&#039; to &#039;&#039;&#039;&#039;&#039;edulevel&#039;&#039;&#039;&#039;&#039; in Moodle 2.7 (See below for more details)&lt;br /&gt;
|-&lt;br /&gt;
| contextid&lt;br /&gt;
| Context ID&lt;br /&gt;
| mandatory&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| contextlevel&lt;br /&gt;
| Context level&lt;br /&gt;
| automatic from context&lt;br /&gt;
| This tells you if it was a course, activity, course category, etc.&lt;br /&gt;
|-&lt;br /&gt;
| contextinstanceid&lt;br /&gt;
| Context instanceid&lt;br /&gt;
| automatic from context&lt;br /&gt;
| Based on context level this may be course id , course module id, course category, etc.&lt;br /&gt;
|-&lt;br /&gt;
| userid&lt;br /&gt;
| User ID&lt;br /&gt;
| defaults to current user&lt;br /&gt;
| User ID, or 0 when not logged in, or -1 when other (System, CLI, Cron, ...)&lt;br /&gt;
|-&lt;br /&gt;
| courseid&lt;br /&gt;
| Affected course&lt;br /&gt;
| defaults to course context from context&lt;br /&gt;
| This is used only for contexts at and bellow course level - this can be used to filter events by course (includes all course activities)&lt;br /&gt;
|-&lt;br /&gt;
| relateduserid&lt;br /&gt;
| Affected user&lt;br /&gt;
|&lt;br /&gt;
| Is this action related to some user? This could be used for some personal timeline view.&lt;br /&gt;
|-&lt;br /&gt;
| other&lt;br /&gt;
| All other data&lt;br /&gt;
|&lt;br /&gt;
| Any other fields needed for event description - scalars or arrays, must be serialisable using json_encode()&lt;br /&gt;
|-&lt;br /&gt;
| timecreated&lt;br /&gt;
| Time of the event&lt;br /&gt;
| automatic&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;static&#039;&#039;: It is the same for all event instances of this class.&lt;br /&gt;
* &#039;&#039;&#039;mandatory&#039;&#039;&#039;: Is required in order to trigger the event.&lt;br /&gt;
&lt;br /&gt;
==== Level property ====&lt;br /&gt;
&lt;br /&gt;
The edulevel property helps defining the educational value of the event. It is intentional that the list is limited to only 3 different constants, 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.&lt;br /&gt;
&lt;br /&gt;
Remember that this is event based. If the user that has triggered the event is not really &amp;quot;participating&amp;quot; because he is an admin, or a manager, then it is the job of the report to filter those. The event itself has a static educational level.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Teaching&#039;&#039;&#039; (LEVEL_TEACHING: 1)&lt;br /&gt;
&lt;br /&gt;
Any event/action that is performed by someone (typically a teacher) and has a teaching value (anything that is affecting the learning experience/environment of the students). This should not be combined with &amp;quot;Participating&amp;quot; events.&lt;br /&gt;
&lt;br /&gt;
Valid events:&lt;br /&gt;
&lt;br /&gt;
* A teacher grading a student&lt;br /&gt;
* A teacher modifying the course settings&lt;br /&gt;
* A teacher adding a new section to the course page&lt;br /&gt;
* A teacher modifying a module settings&lt;br /&gt;
* A teacher adding a page to course&lt;br /&gt;
* A teacher leaving a feedback&lt;br /&gt;
&lt;br /&gt;
INVALID events:&lt;br /&gt;
&lt;br /&gt;
* A teacher posting in a forum (it might affect the learning experience, but not necessarily, so the teacher is just participating)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Participating&#039;&#039;&#039; (LEVEL_PARTICIPATING: 2)&lt;br /&gt;
&lt;br /&gt;
Any event/action that is performed by a user, that is related (or could be related) to his learning experience.&lt;br /&gt;
&lt;br /&gt;
Valid events:&lt;br /&gt;
&lt;br /&gt;
* A user posting to a forum&lt;br /&gt;
* A user submitting an assignment&lt;br /&gt;
* A user blogging&lt;br /&gt;
* A user reading someone&#039;s blog&lt;br /&gt;
* A user posting a comment&lt;br /&gt;
* A user chatting on a chat activity&lt;br /&gt;
* A user viewing the course page&lt;br /&gt;
* A user deletes a blog post&lt;br /&gt;
&lt;br /&gt;
INVALID events:&lt;br /&gt;
&lt;br /&gt;
* A user updating his profile&lt;br /&gt;
* A user visiting someone&#039;s profile&lt;br /&gt;
* A user viewing his /my/ page&lt;br /&gt;
* A user sending a message to another one&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Other&#039;&#039;&#039; (LEVEL_OTHER: 0)&lt;br /&gt;
&lt;br /&gt;
Any other action, whether they are related to the site administration, or are specific to user. They do not have any educational value.&lt;br /&gt;
&lt;br /&gt;
=== Methods ===&lt;br /&gt;
&lt;br /&gt;
The computation of this data is not required by default, but can be accessed by any event observer if need be.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Method&lt;br /&gt;
! Comment&lt;br /&gt;
|-&lt;br /&gt;
| get_name()&lt;br /&gt;
| Returns localised name of the event, it is the same for all instances.&lt;br /&gt;
|-&lt;br /&gt;
| get_description()&lt;br /&gt;
| Returns localised description of one particular event.&lt;br /&gt;
|-&lt;br /&gt;
| can_view($user)&lt;br /&gt;
| Can the specified user view the event?&lt;br /&gt;
|-&lt;br /&gt;
| get_url()&lt;br /&gt;
| Returns Moodle URL where the event can be observed afterwards.&lt;br /&gt;
|-&lt;br /&gt;
| get_legacy_eventname()&lt;br /&gt;
| Information necessary for event BC.&lt;br /&gt;
|-&lt;br /&gt;
| get_legacy_eventdata()&lt;br /&gt;
| Information necessary for event BC.&lt;br /&gt;
|-&lt;br /&gt;
| get_legacy_logdata()&lt;br /&gt;
| Information necessary for logging BC.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Record caching===&lt;br /&gt;
&lt;br /&gt;
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. The snapshot is meant to be a full database record, as it will be automatically fetched from get_record_snapshot() if not set previously and assuming the property &#039;&#039;objecttable&#039;&#039; is set. Please be aware that the snapshots are not stored in the event, and cannot be restored.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $event = \core\event\role_assigned::create(&lt;br /&gt;
        array(&#039;context&#039;=&amp;gt;$context, &#039;objectid&#039;=&amp;gt;$ra-&amp;gt;roleid, &#039;relateduserid&#039;=&amp;gt;$ra-&amp;gt;userid,&lt;br /&gt;
            &#039;other&#039;=&amp;gt;array(&#039;id&#039;=&amp;gt;$ra-&amp;gt;id, &#039;component&#039;=&amp;gt;$ra-&amp;gt;component, &#039;itemid&#039;=&amp;gt;$ra-&amp;gt;itemid)));&lt;br /&gt;
    $event-&amp;gt;add_record_snapshot(&#039;role_assignments&#039;, $ra);&lt;br /&gt;
    $event-&amp;gt;trigger();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $event = \core\event\role_unassigned::create(&lt;br /&gt;
        array(&#039;context&#039;=&amp;gt;$context, &#039;objectid&#039;=&amp;gt;$ra-&amp;gt;roleid, &#039;relateduserid&#039;=&amp;gt;$ra-&amp;gt;userid,&lt;br /&gt;
            &#039;other&#039;=&amp;gt;array(&#039;id&#039;=&amp;gt;$ra-&amp;gt;id, &#039;component&#039;=&amp;gt;$ra-&amp;gt;component, &#039;itemid&#039;=&amp;gt;$ra-&amp;gt;itemid)));&lt;br /&gt;
    $event-&amp;gt;add_record_snapshot(&#039;role_assignments&#039;, $ra);&lt;br /&gt;
    $event-&amp;gt;trigger();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The related methods are:&lt;br /&gt;
* public function add_record_snapshot($tablename, $record)&lt;br /&gt;
* public function get_record_snapshot($tablename, $id)&lt;br /&gt;
&lt;br /&gt;
=== Rejected properties and methods ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property&lt;br /&gt;
! Title&lt;br /&gt;
! Why&lt;br /&gt;
|-&lt;br /&gt;
| URL&lt;br /&gt;
| relevant page URL&lt;br /&gt;
| These URLs can be constructed on the fly from other data, external log plugins may use get_url() method.&lt;br /&gt;
|-&lt;br /&gt;
| version&lt;br /&gt;
| Event specification number&lt;br /&gt;
| The event specification should never change&lt;br /&gt;
|-&lt;br /&gt;
| type&lt;br /&gt;
| Type of event (action, error, ...)&lt;br /&gt;
| Event are not intended for error logging or debugging.&lt;br /&gt;
|-&lt;br /&gt;
| actor&lt;br /&gt;
| Whether current execution is cron, cli, user, ...&lt;br /&gt;
| Impossible to track down at a low level&lt;br /&gt;
|-&lt;br /&gt;
| severity&lt;br /&gt;
| Severity following [http://tools.ietf.org/html/rfc5424#section-6.2.1 logging standards]&lt;br /&gt;
| Our logging does not match this, as we will not (at present) log errors&lt;br /&gt;
|-&lt;br /&gt;
| coursecatname&lt;br /&gt;
| Category name&lt;br /&gt;
| Might be costly to retrieve for little gain&lt;br /&gt;
|-&lt;br /&gt;
| coursename&lt;br /&gt;
| Course name&lt;br /&gt;
| Might be costly to retrieve for little gain&lt;br /&gt;
|-&lt;br /&gt;
| cmname&lt;br /&gt;
| Course module name&lt;br /&gt;
| Might be costly to retrieve for little gain&lt;br /&gt;
|-&lt;br /&gt;
| categoryid &lt;br /&gt;
| Course category id&lt;br /&gt;
| Categories are a tree structure, we can not identify them by one integer. It would have to be a path.&lt;br /&gt;
|-&lt;br /&gt;
| cmid &lt;br /&gt;
| Course module id&lt;br /&gt;
| Can be derived from contextlevel and contextinstanceid&lt;br /&gt;
|-&lt;br /&gt;
| associatedobject&lt;br /&gt;
| Associated object&lt;br /&gt;
| Object associated to the main object. Ie: The user to whom a message is sent.&lt;br /&gt;
|-&lt;br /&gt;
| associatedobjectid&lt;br /&gt;
| Associated object ID&lt;br /&gt;
| Identifier of the associated object&lt;br /&gt;
|-&lt;br /&gt;
| realuserid&lt;br /&gt;
| Real User ID&lt;br /&gt;
| Will be tracked by log plugins only - user who &amp;quot;logged in as&amp;quot;, stores the real user ID&lt;br /&gt;
|-&lt;br /&gt;
| origin&lt;br /&gt;
| Origin of the event&lt;br /&gt;
| Will be tracked by log plugins only - CLI, cron, Webservice, ... (optionally with IP address)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Method&lt;br /&gt;
! Comment&lt;br /&gt;
! Why&lt;br /&gt;
|-&lt;br /&gt;
| get_all_affected_users()&lt;br /&gt;
| Returns all the users affected by this event&lt;br /&gt;
| It is expensive to fetch all users and it changes in time, so it would be unreliable too. For now we store only one user who is related to each event.&lt;br /&gt;
|-&lt;br /&gt;
| get_objecturl()&lt;br /&gt;
| Returns the URL to view the object&lt;br /&gt;
| There is usually only one URL where event changes may be observed. The URL may depend on current user capabilities too.&lt;br /&gt;
|-&lt;br /&gt;
| get_associatedobjecturl()&lt;br /&gt;
| Returns the URL to view the associated object&lt;br /&gt;
| No associated user property is present.&lt;br /&gt;
|-&lt;br /&gt;
| get_currenturl()&lt;br /&gt;
| Returns the current URL, uses $PAGE.&lt;br /&gt;
| This information may be added by logger, current page info is not part of events data.&lt;br /&gt;
|-&lt;br /&gt;
| get_useripaddress()&lt;br /&gt;
| Returns the User IP address&lt;br /&gt;
| Page access method is not part of events API, this should be implemented as logdata properties in loggers.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Events naming convention ==&lt;br /&gt;
&lt;br /&gt;
Clear event names help developers when reading what events are triggered, and defining the events properties when defining the event class.&lt;br /&gt;
&lt;br /&gt;
 Decision: \&amp;lt;component&amp;gt;\event\&amp;lt;some_object&amp;gt;_&amp;lt;verb&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Structure of existing events ===&lt;br /&gt;
&lt;br /&gt;
List of existing events called in 2.5, along with their future name and the decomposition of the new name into &#039;&#039;action&#039;&#039; and &#039;&#039;object&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 2.5 name&lt;br /&gt;
! New name&lt;br /&gt;
! Subject&lt;br /&gt;
! Action&lt;br /&gt;
! Object&lt;br /&gt;
! &amp;lt;relationship&amp;gt;&lt;br /&gt;
! Object 2&lt;br /&gt;
! Comment&lt;br /&gt;
|-&lt;br /&gt;
| activity_completion_changed&lt;br /&gt;
| &#039;&#039;&#039;core_event_activity_completion_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| changed&lt;br /&gt;
| completion&lt;br /&gt;
| of&lt;br /&gt;
| activity&lt;br /&gt;
|-&lt;br /&gt;
| assessable_content_uploaded&lt;br /&gt;
| &#039;&#039;&#039;mod_*_event_assessablecontent_uploaded&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| uploaded&lt;br /&gt;
| assessablecontent&lt;br /&gt;
|-&lt;br /&gt;
| assessable_files_done&lt;br /&gt;
| &#039;&#039;&#039;mod_*_event_assessablecontent_processed&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| processed&lt;br /&gt;
| assessable content&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| assessable_file_uploaded&lt;br /&gt;
|&lt;br /&gt;
| Someone&lt;br /&gt;
| uploaded&lt;br /&gt;
| assessable_file&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;To be deprecated MDL-35197&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| assessable_submitted&lt;br /&gt;
| &#039;&#039;&#039;mod_*_event_assessablecontent_submitted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| submitted&lt;br /&gt;
| assessable content&lt;br /&gt;
|- &lt;br /&gt;
| blog_entry_added&lt;br /&gt;
| &#039;&#039;&#039;mod_blog_event_entry_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| added&lt;br /&gt;
| entry&lt;br /&gt;
|-&lt;br /&gt;
| blog_entry_deleted&lt;br /&gt;
| &#039;&#039;&#039;mod_blog_event_entry_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| entry&lt;br /&gt;
|-&lt;br /&gt;
| blog_entry_edited&lt;br /&gt;
| &#039;&#039;&#039;mod_blog_event_entry_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| entry&lt;br /&gt;
|-&lt;br /&gt;
| cohort_added&lt;br /&gt;
| &#039;&#039;&#039;core_event_cohort_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| created&lt;br /&gt;
| cohort&lt;br /&gt;
|-&lt;br /&gt;
| cohort_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_cohort_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| cohort&lt;br /&gt;
|-&lt;br /&gt;
| cohort_updated&lt;br /&gt;
| &#039;&#039;&#039;core_event_cohort_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| cohort&lt;br /&gt;
|-&lt;br /&gt;
| cohort_member_added&lt;br /&gt;
| &#039;&#039;&#039;core_event_cohort_member_added&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| added&lt;br /&gt;
| member&lt;br /&gt;
| to&lt;br /&gt;
| cohort&lt;br /&gt;
|-&lt;br /&gt;
| cohort_member_removed&lt;br /&gt;
| &#039;&#039;&#039;core_event_cohort_member_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| member&lt;br /&gt;
| from&lt;br /&gt;
| cohort&lt;br /&gt;
|-&lt;br /&gt;
| course_category_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_coursecat_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| coursecat&lt;br /&gt;
|-&lt;br /&gt;
| course_completed&lt;br /&gt;
| &#039;&#039;&#039;core_event_course_completed&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| completed&lt;br /&gt;
| course&lt;br /&gt;
|-&lt;br /&gt;
| course_content_removed&lt;br /&gt;
| &#039;&#039;&#039;core_event_course_purged&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| purged&lt;br /&gt;
| course&lt;br /&gt;
|-&lt;br /&gt;
| course_created&lt;br /&gt;
| &#039;&#039;&#039;core_event_course_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| created&lt;br /&gt;
| course&lt;br /&gt;
|-&lt;br /&gt;
| course_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_course_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| course&lt;br /&gt;
|-&lt;br /&gt;
| course_restored&lt;br /&gt;
| &#039;&#039;&#039;core_event_course_restored&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| restored&lt;br /&gt;
| course&lt;br /&gt;
|-&lt;br /&gt;
| course_updated&lt;br /&gt;
| &#039;&#039;&#039;core_event_course_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| course&lt;br /&gt;
|-&lt;br /&gt;
| groups_group_created&lt;br /&gt;
| &#039;&#039;&#039;core_event_group_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| created&lt;br /&gt;
| group&lt;br /&gt;
|-&lt;br /&gt;
| groups_group_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_group_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| group&lt;br /&gt;
|-&lt;br /&gt;
| groups_grouping_created&lt;br /&gt;
| &#039;&#039;&#039;core_event_grouping_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| created&lt;br /&gt;
| grouping&lt;br /&gt;
|-&lt;br /&gt;
| groups_grouping_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_grouping_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| grouping&lt;br /&gt;
|-&lt;br /&gt;
| groups_groupings_deleted&lt;br /&gt;
|&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| groupings&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;To deprecate&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| groups_groupings_groups_removed&lt;br /&gt;
|&lt;br /&gt;
| Someone&lt;br /&gt;
| removed&lt;br /&gt;
| groups&lt;br /&gt;
| from&lt;br /&gt;
| groupings&lt;br /&gt;
| &#039;&#039;To deprecate&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| groups_grouping_updated&lt;br /&gt;
| &#039;&#039;&#039;core_event_grouping_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| grouping&lt;br /&gt;
|-&lt;br /&gt;
| groups_groups_deleted&lt;br /&gt;
| &lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| groups&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;To deprecate&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| groups_group_updated&lt;br /&gt;
| &#039;&#039;&#039;core_event_group_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| group&lt;br /&gt;
|-&lt;br /&gt;
| groups_member_added&lt;br /&gt;
| &#039;&#039;&#039;core_event_group_member_added&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| added&lt;br /&gt;
| member&lt;br /&gt;
| to&lt;br /&gt;
| group&lt;br /&gt;
|-&lt;br /&gt;
| groups_member_removed&lt;br /&gt;
| &#039;&#039;&#039;core_event_group_member_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| member&lt;br /&gt;
| from&lt;br /&gt;
| group&lt;br /&gt;
|-&lt;br /&gt;
| groups_members_removed&lt;br /&gt;
|&lt;br /&gt;
| Someone&lt;br /&gt;
| removed&lt;br /&gt;
| members&lt;br /&gt;
| from&lt;br /&gt;
| group&lt;br /&gt;
| &#039;&#039;To deprecate&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| lti_unknown_service_api_call&lt;br /&gt;
| &#039;&#039;&#039;mod_lti_event_unknownservice_called&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| called&lt;br /&gt;
| unknown service&lt;br /&gt;
|-&lt;br /&gt;
| mod_created&lt;br /&gt;
| &#039;&#039;&#039;core_event_module_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| created&lt;br /&gt;
| module&lt;br /&gt;
|-&lt;br /&gt;
| mod_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_module_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| module&lt;br /&gt;
|-&lt;br /&gt;
| portfolio_send&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;This is a hack...&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| role_assigned&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_role_added&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| added&lt;br /&gt;
| role&lt;br /&gt;
| to&lt;br /&gt;
| user&lt;br /&gt;
| &#039;&#039;Or ..._role_assigned&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| role_unassigned&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_role_removed&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| removed&lt;br /&gt;
| role&lt;br /&gt;
| from&lt;br /&gt;
| user&lt;br /&gt;
| &#039;&#039;Or ..._role_unassigned&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| quiz_attempt_started&lt;br /&gt;
| &#039;&#039;&#039;mod_quiz_event_attempt_started&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| started&lt;br /&gt;
| attempt&lt;br /&gt;
| of&lt;br /&gt;
| quiz&lt;br /&gt;
|-&lt;br /&gt;
| test_cron&lt;br /&gt;
|-&lt;br /&gt;
| test_instant&lt;br /&gt;
|-&lt;br /&gt;
| user_created&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| created&lt;br /&gt;
| user&lt;br /&gt;
|-&lt;br /&gt;
| user_deleted&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| deleted&lt;br /&gt;
| user&lt;br /&gt;
|-&lt;br /&gt;
| user_enrolled&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_enrolment_created&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| added&lt;br /&gt;
| enrolment&lt;br /&gt;
| of&lt;br /&gt;
| user&lt;br /&gt;
|-&lt;br /&gt;
| user_enrol_modified&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_enrolment_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| enrolment&lt;br /&gt;
| of&lt;br /&gt;
| user&lt;br /&gt;
|-&lt;br /&gt;
| user_unenrolled&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_enrolment_deleted&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| removed&lt;br /&gt;
| enrolment&lt;br /&gt;
| of&lt;br /&gt;
| user&lt;br /&gt;
|-&lt;br /&gt;
| user_logout&lt;br /&gt;
| &#039;&#039;&#039;core_event_loggedout&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| loggedout&lt;br /&gt;
|-&lt;br /&gt;
| user_updated&lt;br /&gt;
| &#039;&#039;&#039;core_event_user_updated&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| updated&lt;br /&gt;
| user&lt;br /&gt;
|-&lt;br /&gt;
| workshop_viewed&lt;br /&gt;
| &#039;&#039;&#039;mod_workshop_event_viewed&#039;&#039;&#039;&lt;br /&gt;
| Someone&lt;br /&gt;
| viewed&lt;br /&gt;
| workshop&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Verb list ===&lt;br /&gt;
All events must use a verb from this list. New verbs should be added to this list if required.&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!verb&lt;br /&gt;
!Explanation&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|accepted&lt;br /&gt;
|Example: Accepting a statement when submitting an assignment.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|added&lt;br /&gt;
|	Used to represent &amp;quot;something that already exists is now part of/bound to another entity&amp;quot;. Examples: &amp;quot;Admin added role to user X&amp;quot;, &amp;quot;Admin added user X to group A&amp;quot;. Wrong example: &amp;quot;User added course in category&amp;quot; because it is a &#039;move&#039; 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.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|answered&lt;br /&gt;
| Indicates the actor responded to a Question&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|assessed&lt;br /&gt;
| Some submitted material has been assessed&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|attempted&lt;br /&gt;
|&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|awarded&lt;br /&gt;
|	ex:-teacher awarded student a badge.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|backedup&lt;br /&gt;
|	When a backup has been performed.&lt;br /&gt;
|Moodle	&lt;br /&gt;
|-&lt;br /&gt;
|called&lt;br /&gt;
|When a call to something is made like an API @see unknown_service_api_called.php&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|commented&lt;br /&gt;
|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.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|completed&lt;br /&gt;
|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 &#039;incomplete&#039; an activity, one would void the statement which completes the activity.&amp;quot;&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|created&lt;br /&gt;
|	Used to represent &amp;quot;something new has been created&amp;quot;.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|deleted&lt;br /&gt;
|	Used to indicate the object in context was deleted.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|duplicated&lt;br /&gt;
|For something that has been copied.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|evaluated&lt;br /&gt;
| Material has been evaluated.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|failed&lt;br /&gt;
|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 &#039;failed&#039;. The opposite of &#039;passed&#039;.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|graded&lt;br /&gt;
|Used to represent an activity was graded.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|imported&lt;br /&gt;
|The act of moving an object into another location or system.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|loggedin/loggedout&lt;br /&gt;
|	For login and logout.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|loggedinas&lt;br /&gt;
|	 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.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|locked&lt;br /&gt;
|&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|moved&lt;br /&gt;
|	Used to indicate the object in context was moved.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|passed&lt;br /&gt;
|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 &#039;passed&#039;. The opposite of &#039;failed&#039;.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|previewed&lt;br /&gt;
|Something has been previewed.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|reassessed&lt;br /&gt;
| Submitted material has been assessed again.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|reevaluated&lt;br /&gt;
| Material has been evaluated again.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|submitted&lt;br /&gt;
|This is very close to &amp;quot;Attempted&amp;quot;. Depends on context which one should be used. For example:- &amp;quot;Admin submitted a form. Student attempted a quiz.&amp;quot;  is correct, however some cases might not be as clear as the previous example. We can say both &amp;quot;Student submitted an assignment&amp;quot; or &amp;quot;student attempted an assignment&amp;quot;. We need to make the difference clear.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|suspended&lt;br /&gt;
| Suspend something. (example a user)&lt;br /&gt;
| Tincan (However the context is different)&lt;br /&gt;
|-&lt;br /&gt;
|switched&lt;br /&gt;
|Something has been switched. For example:- The workshop phase has been switched to assessment&amp;quot;&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|viewed&lt;br /&gt;
|Something has been viewed. For example:- &amp;quot;Student viewed chapter 1 of book 1.&amp;quot;&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|registered&lt;br /&gt;
|Indicates the actor registered for a learning activity&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|removed&lt;br /&gt;
|By opposition to &amp;quot;Added&amp;quot;. This does not mean that the object has been deleted, but removed from the entity, or not bound to it any more.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|restored&lt;br /&gt;
|	When restoring a backup. Rolling back to a previous state.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|reset&lt;br /&gt;
|	Sets one or more properties back to the default value.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|revealed&lt;br /&gt;
|Example: Identities revealed after blind marking.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|unlocked&lt;br /&gt;
|&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|upgraded&lt;br /&gt;
|Something was upgraded, some module probably&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|updated&lt;br /&gt;
|Used to indicate the object in context was updated. Simple example is &amp;quot;Admin updated course xyz&amp;quot;.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Rules ===&lt;br /&gt;
&lt;br /&gt;
==== Use singular ====&lt;br /&gt;
&lt;br /&gt;
Plurals must be used on objects when it&#039;s a &#039;&#039;One to Many&#039;&#039; relationship. Ex: bulk import, mass deletion, ... In any other case, use the singular.&lt;br /&gt;
&lt;br /&gt;
==== Ends with a verb ====&lt;br /&gt;
&lt;br /&gt;
The last word (after the last underscore) must be a verb.&lt;br /&gt;
&lt;br /&gt;
== Shared events ==&lt;br /&gt;
&lt;br /&gt;
 Decision: Not supported at this stage.&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.5 we have a good example of a shared event: &#039;assessable_content_uploaded&#039; which is triggered in &#039;&#039;forum&#039;&#039;, &#039;assignment&#039;&#039; and &#039;&#039;workshop&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== One to many ==&lt;br /&gt;
&lt;br /&gt;
 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.&lt;br /&gt;
&lt;br /&gt;
In 2.5, some events are triggered when an action happens on multiple objects. We have to decide whether we want to keep supporting &#039;&#039;One to Many&#039;&#039; events or not.&lt;br /&gt;
&lt;br /&gt;
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...&lt;br /&gt;
&lt;br /&gt;
=== Accuracy ===&lt;br /&gt;
&lt;br /&gt;
When uploading a bunch of users using the CSV upload feature, if only one event is triggered, it means that the observers of &#039;&#039;user_created&#039;&#039; won&#039;t be triggered. And so some functionality can be lost as, as a plugin developer, I expect this &#039;&#039;user_created&#039;&#039; to be triggered regardless of the way they have been uploaded. Of course, the developer could observe the event &#039;&#039;bulk_user_imported&#039;&#039;, but that means that he could miss some relevant observers.&lt;br /&gt;
&lt;br /&gt;
This applies to existing events.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
&lt;br /&gt;
Triggering one event is cheaper then repeating the same events x number of times...&lt;br /&gt;
&lt;br /&gt;
=== Information tracking ===&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;bulk&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
=== Double event ===&lt;br /&gt;
&lt;br /&gt;
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 &#039;user_bulk_upload_started&#039; when the action starts.&lt;br /&gt;
&lt;br /&gt;
== Unit Testing ==&lt;br /&gt;
&lt;br /&gt;
With unit testing for this system we want to assert the following:&lt;br /&gt;
&lt;br /&gt;
* That event strict validation and custom validation works.&lt;br /&gt;
* Missing event data is auto filled with accurate data.&lt;br /&gt;
* Typos in properties passed to ::create() are captured (if we decide to validate).&lt;br /&gt;
* The legacy methods return the expected values (use assertEventLegacyData() and assertEventLegacyData())&lt;br /&gt;
* The class properties are correctly overridden (crud, level, action, object, ...).&lt;br /&gt;
* The properties automatically generated (component, name, ...) are correct.&lt;br /&gt;
* Events are dispatched to the corresponding observers.&lt;br /&gt;
* Events are dispatched to the corresponding legacy handlers.&lt;br /&gt;
* Events are dispatched to the * observers.&lt;br /&gt;
* Events perform an add_to_log() if it has legacy log data.&lt;br /&gt;
* &#039;Events restore&#039; restored the whole event data, and does not miss any information.&lt;br /&gt;
* &#039;Events restore&#039; does not generate any extra information.&lt;br /&gt;
* Event methods should check context object or avoid using it, as context might not be valid at time of event restore (use assertEventContextNotUsed())&lt;br /&gt;
&lt;br /&gt;
= Example events =&lt;br /&gt;
&lt;br /&gt;
== Assignment ==&lt;br /&gt;
&lt;br /&gt;
Assumption: Course contains groups with students in each group.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1.&#039;&#039;&#039; Teacher creates an assignment with group mode set to &#039;Separate groups&#039; and Feedback type set to comments and files.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has created assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;2.&#039;&#039;&#039; A student views the assignment.&lt;br /&gt;
*Event: User &#039;Student&#039; has viewed assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;3.&#039;&#039;&#039; A member from one of the groups submits an assignment&lt;br /&gt;
*Event: User &#039;Student&#039; has added a submission for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to the teacher and all students in that group.&lt;br /&gt;
&#039;&#039;&#039;4.&#039;&#039;&#039; User &#039;Adrian&#039; adds some changes to the assignment and updates it.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has updated the submission for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to the teacher and all students in that group.&lt;br /&gt;
&#039;&#039;&#039;5.&#039;&#039;&#039; Teacher views the assignment.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;6.&#039;&#039;&#039; Teacher clicks on &#039;View/grade all submissions&#039;&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed the assignment &#039;B&#039; grade area in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;7.&#039;&#039;&#039; Teacher clicks to grade the student&#039;s submission.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed the submission for user &#039;student&#039; for assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;8.&#039;&#039;&#039; Teacher marks the assignment with the setting &#039;Apply grades and feedback to entire group&#039; set to &#039;Yes&#039; leaving a comment and a file.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has marked assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has left a comment for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has uploaded a feedback file for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has uploaded a file to the course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Student&#039; notifying them their submission for assignment &#039;B&#039; has been marked. - This is done for all users in the group.&lt;br /&gt;
&#039;&#039;&#039;9.&#039;&#039;&#039; User &#039;Adrian&#039; views the feedback.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has viewed assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;10.&#039;&#039;&#039; User &#039;Adrian&#039; opens the feedback file.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has viewed the file &#039;A&#039; for assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;11.&#039;&#039;&#039; User &#039;Adrian&#039; adds some changes to the assignment insulting the teachers marking and updates it.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has updated the submission for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Teacher&#039; notifying them that user &#039;Adrian&#039; has updated the submission for assignment &#039;B&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Student&#039; notifying them their submission for assignment &#039;B&#039; has been updated. - This is done for all users in the group.&lt;br /&gt;
&#039;&#039;&#039;12.&#039;&#039;&#039; The teacher clicks directly on the link in the email to be taken to the grading page.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed the submission for user &#039;student&#039; for assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;13.&#039;&#039;&#039; The teacher is upset due to the harsh comments and decides to mark Adrian down, but not the rest of the group by setting &#039;Apply grades and feedback to entire group&#039; set to &#039;No&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has marked assignment &#039;B&#039; for user &#039;Adrian&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has left a comment for assignment &#039;B&#039; for user &#039;Adrian&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has uploaded a feedback file for assignment &#039;B&#039; for user &#039;Adrian&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Adrian&#039; notifying them their submission for assignment &#039;B&#039; has been marked.&lt;br /&gt;
&lt;br /&gt;
= FAQs =&lt;br /&gt;
&lt;br /&gt;
; Why not use 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.&lt;br /&gt;
&lt;br /&gt;
= Development stages =&lt;br /&gt;
&lt;br /&gt;
== Stage 1 ==&lt;br /&gt;
&lt;br /&gt;
* Finish class loader spec and implement basic Frankenstyle class loader.&lt;br /&gt;
* Describe new observer definition - just few new flags in current db/events.php&lt;br /&gt;
* Describe new event dispatcher.&lt;br /&gt;
* Describe core_event_base class.&lt;br /&gt;
* Current (= legacy) events triggering:&lt;br /&gt;
** Re-factor current event handling code to new self-contained class - do not change functionality, keep events_trigger().&lt;br /&gt;
** Create new event handler management class that deals with installation and upgrades of both legacy and new handlers.&lt;br /&gt;
* New events:&lt;br /&gt;
** Create new core_event_base class.&lt;br /&gt;
** Create new self-contained event dispatcher class with &#039;*&#039; handler support.&lt;br /&gt;
** In function core_event_base::trigger() check if the  event has property &#039;legacyeventname&#039; execute events_trigger($this-&amp;gt;legacyeventname, $this-&amp;gt;legacyeventdata) after triggering new event.&lt;br /&gt;
** Write unit tests for all new events code.&lt;br /&gt;
* No changes to be made to the current logging system yet.&lt;br /&gt;
&lt;br /&gt;
After completing this stage everything should continue to work as it did before and we can start parallel work on further stages.&lt;br /&gt;
&lt;br /&gt;
== Stage 2 (requires completion of Stage 1) ==&lt;br /&gt;
&lt;br /&gt;
* Create event classes and replace existing calls to events_trigger() and with new event classes containing legacy information properties.&lt;br /&gt;
* Add more events throughout the standard Moodle package in places where we have add_to_log(). Implement some_event::get_legacy_log_data() which returns original parameters of add_to_log() and remove it. Old add_to_log() function is called in core_event_base::trigger() automatically with original parameters.&lt;br /&gt;
* Add even more new events all over the place.&lt;br /&gt;
&lt;br /&gt;
The difficult part is defining the new event classes properly because we must not change them after the 2.6 release.&lt;br /&gt;
&lt;br /&gt;
== Stage 3 (requires partial completion of Stage 2) ==&lt;br /&gt;
&lt;br /&gt;
* Migrate current legacy event handlers to new handlers with one event class instance parameter, ex.: enrol plugins.&lt;br /&gt;
&lt;br /&gt;
== Stage 4 (requires partial completion of Stage 2) ==&lt;br /&gt;
&lt;br /&gt;
* Implement an event logging handler.&lt;br /&gt;
* Implement logging storage plugins.&lt;br /&gt;
* Define logging apis.&lt;br /&gt;
* Create new reports.&lt;br /&gt;
* Switch to new logging everywhere after Stage 2 has been completed and new reports are usable.&lt;br /&gt;
&lt;br /&gt;
See [[Logging 2]]&lt;br /&gt;
&lt;br /&gt;
== Stage 5  ==&lt;br /&gt;
&lt;br /&gt;
* Decide how much backwards compatibility we want for old log tables. Most probably they will get only legacy log data.&lt;br /&gt;
* Implement some BC solution for old code that reads log tables directly.&lt;br /&gt;
&lt;br /&gt;
See [[Logging 2]]&lt;br /&gt;
&lt;br /&gt;
== Stage 6 (requires completion of Stage 4 and 5) ==&lt;br /&gt;
&lt;br /&gt;
Moodle 2.8dev? This is the ultimate end of old logging via the &#039;&#039;log&#039;&#039; table.&lt;br /&gt;
&lt;br /&gt;
* Deprecate the add_to_log() function with a debug message and do nothing inside.&lt;br /&gt;
* Remove all legacy logging from event classes.&lt;br /&gt;
&lt;br /&gt;
See [[Logging 2]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Developer_meeting_January_2014&amp;diff=43595</id>
		<title>Developer meeting January 2014</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Developer_meeting_January_2014&amp;diff=43595"/>
		<updated>2014-01-21T13:16:52Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Developer meetings]] &amp;gt; January 2014 meeting notes&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Time&lt;br /&gt;
| [http://timeanddate.com/worldclock/fixedtime.html?year=2014&amp;amp;month=1&amp;amp;day=21&amp;amp;hour=13&amp;amp;min=0&amp;amp;sec=0 13:00 UTC on Tuesday, 21 January 2014]&lt;br /&gt;
|-&lt;br /&gt;
| Meeting room&lt;br /&gt;
| [http://www.youtube.com/user/moodlehq Moodle HQ YouTube channel] (changed since last meeting)&lt;br /&gt;
|-&lt;br /&gt;
| Chat&lt;br /&gt;
| [https://moodle.org/local/chatlogs/info.php Regular Dev chat]&lt;br /&gt;
|-&lt;br /&gt;
| Twitter&lt;br /&gt;
| [https://twitter.com/search?q=%23moodledev #moodledev]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
In this meeting we will focus on Moodle development happening outside Moodle HQ. &lt;br /&gt;
&lt;br /&gt;
The meeting will be streamed live on YouTube with chat through the regular Dev chat room and comments on Twitter.&lt;br /&gt;
&lt;br /&gt;
== Agenda ==&lt;br /&gt;
* Quiz editing changes (Tim Hunt, Colin Chamber, Mahmoud Kassaei, OU)&lt;br /&gt;
* Possible future changes to Conditional Activities (Sam Marshall, OU)&lt;br /&gt;
* Moodle 2.7 progress&lt;br /&gt;
** Prototypes site (Martin Dougiamas)&lt;br /&gt;
** Clean as default theme (Jérôme Mouneyrac)&lt;br /&gt;
*** Should we move most themes to Plugins Directory?&lt;br /&gt;
*** Bootstrap 3?&lt;br /&gt;
** Editor replacement (Damyon Wiese)&lt;br /&gt;
** Old Assignment removal (Damyon Wiese)&lt;br /&gt;
** Outcomes (Andrew Davis)&lt;br /&gt;
** New logging (Rajesh Taneja)&lt;br /&gt;
* Moodle Mobile update (Juan Leyva)&lt;br /&gt;
* Integration update  (Dan Poltawski)&lt;br /&gt;
** Pre-checking of issues by CiBot&lt;br /&gt;
** Testing requirements&lt;br /&gt;
** Behat improvements&lt;br /&gt;
** Misc&lt;br /&gt;
* Long term support (Martin Dougiamas)&lt;br /&gt;
* A better video conferencing solution for Gen Dev meetings? (Michael de Raadt)&lt;br /&gt;
&lt;br /&gt;
Questions&lt;br /&gt;
&lt;br /&gt;
* What are the plans in 2.7 for Gradebook improvements?&lt;br /&gt;
* Will the base theme no longer receive updates or be actively worked on?&lt;br /&gt;
* Anymore changes to the event system? We want to start planning/scoping out analytics/alerts using the new event system in 2.6, but don&#039;t want to progress if more changes are expected.&lt;br /&gt;
* Global Search ?&lt;br /&gt;
&lt;br /&gt;
If you have something you&#039;d like to add to the agenda, please [https://docs.moodle.org/dev/index.php?title=Developer_meeting_January_2014&amp;amp;action=edit edit this page] or contact [http://moodle.org/user/profile.php?id=381842 Michael d].&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42725</id>
		<title>Moodle 2.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42725"/>
		<updated>2013-10-22T18:19:00Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Other highlights */ MDL-41940 Add new option to disable new files in legacy course files&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
=UNDER CONSTRUCTION=&lt;br /&gt;
&lt;br /&gt;
Release date: November 2013 (Not yet released)&lt;br /&gt;
&lt;br /&gt;
See [https://docs.moodle.org/26/en/New_features New features] for a user-friendly tour with screenshots.&lt;br /&gt;
 &lt;br /&gt;
===Requirements===&lt;br /&gt;
&lt;br /&gt;
These are just minimums.  We recommend keeping all your software updated. &lt;br /&gt;
&lt;br /&gt;
* Recommended minimum browser: recent Google Chrome, recent Mozilla Firefox, Safari 6, Internet Explorer 9 (IE 10 required for drag and drop of files from outside the browser into Moodle)&lt;br /&gt;
* Moodle upgrade:  Moodle 2.2 or later (if upgrading from earlier versions, you must upgrade to 2.2.11 as a first step)&lt;br /&gt;
* Minimum DB versions: PostgreSQL 8.3, MySQL 5.1.33, MariaDB 5.2, MSSQL 2005 or Oracle 10.2&lt;br /&gt;
* Minimum PHP version: PHP 5.3.3 (always use latest PHP 5.3.x, 5.4.x or 5.5.x on Windows - http://windows.php.net/download/)&lt;br /&gt;
* New PHP extensions: zlib extension now recommended&lt;br /&gt;
&lt;br /&gt;
===Known problems===&lt;br /&gt;
&lt;br /&gt;
* IE8 and Safari 5 are not fully supported any more, they should still work but they are not tested regularly and there might be some known problems. Like most of the world&#039;s web sites and browser producers, we encourage you to keep your browsers current to improve security and functionality while saving us valuable time.  ([http://googleappsupdates.blogspot.ca/2012/09/supporting-modern-browsers-internet.html For example see what Google is doing])&lt;br /&gt;
* IE6 and IE7 are not recommended for Moodle 2.6 at all.  You would have big problems trying to use those old browsers in today&#039;s internet.&lt;br /&gt;
* Support for Oracle database is limited, there are known performance and compatibility problems, many add-ons are not compatible. Most of these problems are caused by the fact that Oracle databases do not implement necessary industry SQL standards and contain several legacy limitations.  If you are using Oracle or planning to then we highly encourage you to think about using one of the open source databases we support.&lt;br /&gt;
&lt;br /&gt;
===Major new features===&lt;br /&gt;
&lt;br /&gt;
* MDL-31776 - Alternate name fields&lt;br /&gt;
* MDL-31830 - Improved category and course management interface&lt;br /&gt;
* MDL-13114 - Bulk course creation tool&lt;br /&gt;
* MDL-40121 - New Single Activity Course Format (and removed SCORM course format MDL-40122)&lt;br /&gt;
* MDL-30740 - Microsoft Skydrive repository&lt;br /&gt;
* MDL-17081 - Roles import and export&lt;br /&gt;
* MDL-40493 - Users may select preferred text editor&lt;br /&gt;
* MDL-37565 - Toolbar switching in TinyMCE editor (one or multiple lines)&lt;br /&gt;
* MDL-41866, MDL-18375 - Support for multiple calendars&lt;br /&gt;
* MDL-23692 - Simplified recovery of forgotten username and password reset&lt;br /&gt;
&lt;br /&gt;
====Assignment activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-38359 - New marker allocation and grading workflow settings&lt;br /&gt;
* MDL-37621 - Admins can set assignment setting defaults&lt;br /&gt;
* MDL-42023 - Edit PDF plugin&lt;br /&gt;
* MDL-37148 - Lots more webservices&lt;br /&gt;
&lt;br /&gt;
====Forum activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-29663 - Forum read tracking options now Off, Optional, and Forced.&lt;br /&gt;
* MDL-41933 - Option to set default read tracking option.&lt;br /&gt;
* MDL-4908  - Per-forum digest settings&lt;br /&gt;
&lt;br /&gt;
====Quiz and question bank====&lt;br /&gt;
&lt;br /&gt;
* MDL-32188 - Big improvements to how certainty-based marking (CBM) works. There is now much better feedback for students about how they have done, and what they need to do to improve in future.&lt;br /&gt;
* MDL-9873  - Question text is now a required field when creating and editing questions.&lt;br /&gt;
* MDL-39155 - Option for what size user picture to show during quiz attempts.&lt;br /&gt;
&lt;br /&gt;
====SCORM activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-28579 / MDL-41580 - Allow use of file aliases and direct linking to imsmanifest.xml inside an extracted zip in the file system repository.&lt;br /&gt;
* MDL-39910 - Improved SCORM player with responsive design elements and better support for mobile devices.&lt;br /&gt;
* MDL-39926 - New objectives report&lt;br /&gt;
* MDL-41290 - Improved user level reporting with ability to export data.&lt;br /&gt;
* MDL-41434 - When updating a SCORM package we no longer delete and re-create the scorm_scoes table - we now use a sortorder field.&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
&lt;br /&gt;
* MDL-38189 - Restoring of large courses possible&lt;br /&gt;
* MDL-40415 - OPcache extension fully supported and recommended&lt;br /&gt;
* MDL-31501 - New session drivers supporting files, database and memcached storage&lt;br /&gt;
* MDL-40545 - New $CFG-&amp;gt;localcachedir setting (intended for clustered servers)&lt;br /&gt;
* MDL-40563 - Improved theme resource caching (local cache compatible)&lt;br /&gt;
* MDL-40546 - Improved javascript  caching (local cache compatible)&lt;br /&gt;
* MDL-41019 - Language caching improvements (local cache compatible)&lt;br /&gt;
* MDL-41017 - HTMLPurifier caching improvements (local cache compatible)&lt;br /&gt;
* MDL-39474 - Developer debug checks improvements&lt;br /&gt;
* MDL-38570 - Automatic temp directory cleanup&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-40770 - New TinyMCE editor icons&lt;br /&gt;
* MDL-39814 - Improved edit icons for usability on all screens&lt;br /&gt;
* MDL-11270 - Significantly improved MS SQL Server compatibility&lt;br /&gt;
* MDL-39985 - Full MariaDB support&lt;br /&gt;
* MDL-19390 - Email notification for new users added manually&lt;br /&gt;
* MDL-33955 - Support for open_basedir restriction&lt;br /&gt;
* MDL-41245, MDL-41437,  MDL-41086 - Multiple installation and upgrade fixes and improvements&lt;br /&gt;
* MDL-42078 - Standardised plugin uninstallation and management.&lt;br /&gt;
* MDL-37717 - Teachers are warned before suspending own enrolment in course&lt;br /&gt;
* MDL-38155 - User enrolment may be suspended via CSV upload&lt;br /&gt;
* MDL-16073 - New test pages for external database authentication and enrolment plugins&lt;br /&gt;
* MDL-41838 - Backup and restore .mbz files now supports a new .tar.gz internal format (helps with very large courses)&lt;br /&gt;
* MDL-41940 - Option to prevent users from adding new files and directories to legacy course files&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
&lt;br /&gt;
* To be published after release.&lt;br /&gt;
&lt;br /&gt;
===For developers: API changes===&lt;br /&gt;
&lt;br /&gt;
* MDL-39854 - Automatic class loader&lt;br /&gt;
* MDL-39797 - New events infrastructure&lt;br /&gt;
* MDL-41267 - Support for sub-plugins in admin tool plugins&lt;br /&gt;
* MDL-26943 - Support for sub-plugins in local plugins&lt;br /&gt;
* MDL-20045 - Unofficial support for custom context levels&lt;br /&gt;
* MDL-40359 - 3rd party libraries updated to latest versions&lt;br /&gt;
* MDL-40305, MDL-40940 - PHPUnit testcase autoloader&lt;br /&gt;
* MDL-23493 - Support for including a font through theme CSS&lt;br /&gt;
* MDL-40248 - Better support for subplugins in Activity chooser&lt;br /&gt;
* MDL-41953 - Plugin name restrictions were relaxed, multiple trailing numbers are allowed&lt;br /&gt;
* MDL-42040 - New API for registration of shutdown handlers&lt;br /&gt;
* MDL-42148 - New admin page listing all third party libraries, thirdpartylibs.xml now supported in plugins&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Upgrade notes for developers====&lt;br /&gt;
&lt;br /&gt;
;Assignment: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/assign/upgrade.txt;hb=master&lt;br /&gt;
;Backup: http://git.moodle.org/gw?p=moodle.git;a=blob;f=backup/upgrade.txt;hb=master&lt;br /&gt;
;Cache: http://git.moodle.org/gw?p=moodle.git;a=blob;f=cache/upgrade.txt;hb=master&lt;br /&gt;
;Calendar: http://git.moodle.org/gw?p=moodle.git;a=blob;f=calendar/upgrade.txt;hb=master&lt;br /&gt;
;Core: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/upgrade.txt;hb=master&lt;br /&gt;
;Course formats: http://git.moodle.org/gw?p=moodle.git;a=blob;f=course/format/upgrade.txt;hb=master&lt;br /&gt;
;Enrolment plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=enrol/upgrade.txt;hb=master&lt;br /&gt;
;Forum: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/forum/upgrade.txt;hb=master&lt;br /&gt;
;Question types: http://git.moodle.org/gw?p=moodle.git;a=blob;f=question/type/upgrade.txt;hb=master&lt;br /&gt;
;Repositories: http://git.moodle.org/gw?p=moodle.git;a=blob;f=repository/upgrade.txt;hb=master&lt;br /&gt;
;Themes: http://git.moodle.org/gw?p=moodle.git;a=blob;f=theme/upgrade.txt;hb=master&lt;br /&gt;
;TinyMCE plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/editor/tinymce/upgrade.txt;hb=master&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
==See also==&lt;br /&gt;
* [https://docs.moodle.org/26/en/Category:New_features User documentation of new features in Moodle 2.6]&lt;br /&gt;
* [https://docs.moodle.org/26/en/Upgrading_to_Moodle_2.6 Upgrading to Moodle 2.6] - information for admins who are upgrading from earlier versions&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.6]]&lt;br /&gt;
 &lt;br /&gt;
[[es:Notas de Moodle 2.6]]&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.6]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42724</id>
		<title>Moodle 2.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42724"/>
		<updated>2013-10-22T18:17:48Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Quiz and question bank */ Updating question text required description&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
=UNDER CONSTRUCTION=&lt;br /&gt;
&lt;br /&gt;
Release date: November 2013 (Not yet released)&lt;br /&gt;
&lt;br /&gt;
See [https://docs.moodle.org/26/en/New_features New features] for a user-friendly tour with screenshots.&lt;br /&gt;
 &lt;br /&gt;
===Requirements===&lt;br /&gt;
&lt;br /&gt;
These are just minimums.  We recommend keeping all your software updated. &lt;br /&gt;
&lt;br /&gt;
* Recommended minimum browser: recent Google Chrome, recent Mozilla Firefox, Safari 6, Internet Explorer 9 (IE 10 required for drag and drop of files from outside the browser into Moodle)&lt;br /&gt;
* Moodle upgrade:  Moodle 2.2 or later (if upgrading from earlier versions, you must upgrade to 2.2.11 as a first step)&lt;br /&gt;
* Minimum DB versions: PostgreSQL 8.3, MySQL 5.1.33, MariaDB 5.2, MSSQL 2005 or Oracle 10.2&lt;br /&gt;
* Minimum PHP version: PHP 5.3.3 (always use latest PHP 5.3.x, 5.4.x or 5.5.x on Windows - http://windows.php.net/download/)&lt;br /&gt;
* New PHP extensions: zlib extension now recommended&lt;br /&gt;
&lt;br /&gt;
===Known problems===&lt;br /&gt;
&lt;br /&gt;
* IE8 and Safari 5 are not fully supported any more, they should still work but they are not tested regularly and there might be some known problems. Like most of the world&#039;s web sites and browser producers, we encourage you to keep your browsers current to improve security and functionality while saving us valuable time.  ([http://googleappsupdates.blogspot.ca/2012/09/supporting-modern-browsers-internet.html For example see what Google is doing])&lt;br /&gt;
* IE6 and IE7 are not recommended for Moodle 2.6 at all.  You would have big problems trying to use those old browsers in today&#039;s internet.&lt;br /&gt;
* Support for Oracle database is limited, there are known performance and compatibility problems, many add-ons are not compatible. Most of these problems are caused by the fact that Oracle databases do not implement necessary industry SQL standards and contain several legacy limitations.  If you are using Oracle or planning to then we highly encourage you to think about using one of the open source databases we support.&lt;br /&gt;
&lt;br /&gt;
===Major new features===&lt;br /&gt;
&lt;br /&gt;
* MDL-31776 - Alternate name fields&lt;br /&gt;
* MDL-31830 - Improved category and course management interface&lt;br /&gt;
* MDL-13114 - Bulk course creation tool&lt;br /&gt;
* MDL-40121 - New Single Activity Course Format (and removed SCORM course format MDL-40122)&lt;br /&gt;
* MDL-30740 - Microsoft Skydrive repository&lt;br /&gt;
* MDL-17081 - Roles import and export&lt;br /&gt;
* MDL-40493 - Users may select preferred text editor&lt;br /&gt;
* MDL-37565 - Toolbar switching in TinyMCE editor (one or multiple lines)&lt;br /&gt;
* MDL-41866, MDL-18375 - Support for multiple calendars&lt;br /&gt;
* MDL-23692 - Simplified recovery of forgotten username and password reset&lt;br /&gt;
&lt;br /&gt;
====Assignment activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-38359 - New marker allocation and grading workflow settings&lt;br /&gt;
* MDL-37621 - Admins can set assignment setting defaults&lt;br /&gt;
* MDL-42023 - Edit PDF plugin&lt;br /&gt;
* MDL-37148 - Lots more webservices&lt;br /&gt;
&lt;br /&gt;
====Forum activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-29663 - Forum read tracking options now Off, Optional, and Forced.&lt;br /&gt;
* MDL-41933 - Option to set default read tracking option.&lt;br /&gt;
* MDL-4908  - Per-forum digest settings&lt;br /&gt;
&lt;br /&gt;
====Quiz and question bank====&lt;br /&gt;
&lt;br /&gt;
* MDL-32188 - Big improvements to how certainty-based marking (CBM) works. There is now much better feedback for students about how they have done, and what they need to do to improve in future.&lt;br /&gt;
* MDL-9873  - Question text is now a required field when creating and editing questions.&lt;br /&gt;
* MDL-39155 - Option for what size user picture to show during quiz attempts.&lt;br /&gt;
&lt;br /&gt;
====SCORM activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-28579 / MDL-41580 - Allow use of file aliases and direct linking to imsmanifest.xml inside an extracted zip in the file system repository.&lt;br /&gt;
* MDL-39910 - Improved SCORM player with responsive design elements and better support for mobile devices.&lt;br /&gt;
* MDL-39926 - New objectives report&lt;br /&gt;
* MDL-41290 - Improved user level reporting with ability to export data.&lt;br /&gt;
* MDL-41434 - When updating a SCORM package we no longer delete and re-create the scorm_scoes table - we now use a sortorder field.&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
&lt;br /&gt;
* MDL-38189 - Restoring of large courses possible&lt;br /&gt;
* MDL-40415 - OPcache extension fully supported and recommended&lt;br /&gt;
* MDL-31501 - New session drivers supporting files, database and memcached storage&lt;br /&gt;
* MDL-40545 - New $CFG-&amp;gt;localcachedir setting (intended for clustered servers)&lt;br /&gt;
* MDL-40563 - Improved theme resource caching (local cache compatible)&lt;br /&gt;
* MDL-40546 - Improved javascript  caching (local cache compatible)&lt;br /&gt;
* MDL-41019 - Language caching improvements (local cache compatible)&lt;br /&gt;
* MDL-41017 - HTMLPurifier caching improvements (local cache compatible)&lt;br /&gt;
* MDL-39474 - Developer debug checks improvements&lt;br /&gt;
* MDL-38570 - Automatic temp directory cleanup&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-40770 - New TinyMCE editor icons&lt;br /&gt;
* MDL-39814 - Improved edit icons for usability on all screens&lt;br /&gt;
* MDL-11270 - Significantly improved MS SQL Server compatibility&lt;br /&gt;
* MDL-39985 - Full MariaDB support&lt;br /&gt;
* MDL-19390 - Email notification for new users added manually&lt;br /&gt;
* MDL-33955 - Support for open_basedir restriction&lt;br /&gt;
* MDL-41245, MDL-41437,  MDL-41086 - Multiple installation and upgrade fixes and improvements&lt;br /&gt;
* MDL-42078 - Standardised plugin uninstallation and management.&lt;br /&gt;
* MDL-37717 - Teachers are warned before suspending own enrolment in course&lt;br /&gt;
* MDL-38155 - User enrolment may be suspended via CSV upload&lt;br /&gt;
* MDL-16073 - New test pages for external database authentication and enrolment plugins&lt;br /&gt;
* MDL-41838 - Backup and restore .mbz files now supports a new .tar.gz internal format (helps with very large courses)&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
&lt;br /&gt;
* To be published after release.&lt;br /&gt;
&lt;br /&gt;
===For developers: API changes===&lt;br /&gt;
&lt;br /&gt;
* MDL-39854 - Automatic class loader&lt;br /&gt;
* MDL-39797 - New events infrastructure&lt;br /&gt;
* MDL-41267 - Support for sub-plugins in admin tool plugins&lt;br /&gt;
* MDL-26943 - Support for sub-plugins in local plugins&lt;br /&gt;
* MDL-20045 - Unofficial support for custom context levels&lt;br /&gt;
* MDL-40359 - 3rd party libraries updated to latest versions&lt;br /&gt;
* MDL-40305, MDL-40940 - PHPUnit testcase autoloader&lt;br /&gt;
* MDL-23493 - Support for including a font through theme CSS&lt;br /&gt;
* MDL-40248 - Better support for subplugins in Activity chooser&lt;br /&gt;
* MDL-41953 - Plugin name restrictions were relaxed, multiple trailing numbers are allowed&lt;br /&gt;
* MDL-42040 - New API for registration of shutdown handlers&lt;br /&gt;
* MDL-42148 - New admin page listing all third party libraries, thirdpartylibs.xml now supported in plugins&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Upgrade notes for developers====&lt;br /&gt;
&lt;br /&gt;
;Assignment: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/assign/upgrade.txt;hb=master&lt;br /&gt;
;Backup: http://git.moodle.org/gw?p=moodle.git;a=blob;f=backup/upgrade.txt;hb=master&lt;br /&gt;
;Cache: http://git.moodle.org/gw?p=moodle.git;a=blob;f=cache/upgrade.txt;hb=master&lt;br /&gt;
;Calendar: http://git.moodle.org/gw?p=moodle.git;a=blob;f=calendar/upgrade.txt;hb=master&lt;br /&gt;
;Core: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/upgrade.txt;hb=master&lt;br /&gt;
;Course formats: http://git.moodle.org/gw?p=moodle.git;a=blob;f=course/format/upgrade.txt;hb=master&lt;br /&gt;
;Enrolment plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=enrol/upgrade.txt;hb=master&lt;br /&gt;
;Forum: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/forum/upgrade.txt;hb=master&lt;br /&gt;
;Question types: http://git.moodle.org/gw?p=moodle.git;a=blob;f=question/type/upgrade.txt;hb=master&lt;br /&gt;
;Repositories: http://git.moodle.org/gw?p=moodle.git;a=blob;f=repository/upgrade.txt;hb=master&lt;br /&gt;
;Themes: http://git.moodle.org/gw?p=moodle.git;a=blob;f=theme/upgrade.txt;hb=master&lt;br /&gt;
;TinyMCE plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/editor/tinymce/upgrade.txt;hb=master&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
==See also==&lt;br /&gt;
* [https://docs.moodle.org/26/en/Category:New_features User documentation of new features in Moodle 2.6]&lt;br /&gt;
* [https://docs.moodle.org/26/en/Upgrading_to_Moodle_2.6 Upgrading to Moodle 2.6] - information for admins who are upgrading from earlier versions&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.6]]&lt;br /&gt;
 &lt;br /&gt;
[[es:Notas de Moodle 2.6]]&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.6]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42534</id>
		<title>Moodle 2.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42534"/>
		<updated>2013-10-11T13:21:53Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Forum activity */ Fixed typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
=UNDER CONSTRUCTION=&lt;br /&gt;
&lt;br /&gt;
Release date: November 2013 (Not yet released)&lt;br /&gt;
 &lt;br /&gt;
===Requirements===&lt;br /&gt;
&lt;br /&gt;
These are just minimums.  We recommend keeping all your software updated. &lt;br /&gt;
&lt;br /&gt;
* Recommended minimum browser: recent Google Chrome, recent Mozilla Firefox, Safari 6, Internet Explorer 9 (IE 10 required for drag and drop of files from outside the browser into Moodle)&lt;br /&gt;
* Moodle upgrade:  Moodle 2.2 or later (if upgrading from earlier versions, you must upgrade to 2.2.11 as a first step)&lt;br /&gt;
* Minimum DB versions: PostgreSQL 8.3, MySQL 5.1.33, MariaDB 5.2, MSSQL 2005 or Oracle 10.2&lt;br /&gt;
* Minimum PHP version: PHP 5.3.3 (always use latest PHP 5.3.x, 5.4.x or 5.5.x on Windows - http://windows.php.net/download/)&lt;br /&gt;
* New PHP extensions: zlib extension now recommended&lt;br /&gt;
&lt;br /&gt;
NOTE: IE8 and Safari 5 are not fully supported any more, they should still work but they are not tested regularly and there might be some known problems, IE6 and IE7 are not compatible with Moodle. Like most of the world&#039;s web sites and browser producers, we encourage you to keep your browsers current to improve security and functionality while saving us valuable time.  ([http://googleappsupdates.blogspot.ca/2012/09/supporting-modern-browsers-internet.html For example see what Google is doing])&lt;br /&gt;
&lt;br /&gt;
===Major new features===&lt;br /&gt;
&lt;br /&gt;
* MDL-31776 - Alternate name fields&lt;br /&gt;
* MDL-13114 - Bulk course creation tool&lt;br /&gt;
* MDL-40121 - New Single Activity Course Format (and removed SCORM course format MDL-40122)&lt;br /&gt;
* MDL-30740 - Microsoft Skydrive repository&lt;br /&gt;
* MDL-17081 - Roles import and export&lt;br /&gt;
* MDL-41098 - New Atto simple text editor suitable for iOS devices&lt;br /&gt;
* MDL-37565 - Toolbar switching in TinyMCE editor (one or multiple lines)&lt;br /&gt;
* MDL-41866 - Multiple calendar support (MDL-18375 was the original issue but it was decided we would split this into two separate tasks, hence the newly created epic).&lt;br /&gt;
&lt;br /&gt;
====Assignment activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-38359 - New marker allocation and grading workflow settings&lt;br /&gt;
* MDL-37621 - Admins can set assignment setting defaults&lt;br /&gt;
* MDL-42023 - Edit PDF plugin&lt;br /&gt;
* MDL-37148 - Lots more webservices&lt;br /&gt;
&lt;br /&gt;
====Forum activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-29663 - Forum read tracking options now Off, Optional, and Forced.&lt;br /&gt;
* MDL-41933 - Option to set default read tracking option.&lt;br /&gt;
&lt;br /&gt;
====SCORM activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-28579 / MDL-41580 - Allow use of file aliases and direct linking to imsmanifest.xml inside an extracted zip in the file system repository.&lt;br /&gt;
* MDL-39910 - Improved SCORM player with responsive design elements and better support for mobile devices.&lt;br /&gt;
* MDL-39926 - New objectives report&lt;br /&gt;
* MDL-41290 - Improved user level reporting with ability to export data.&lt;br /&gt;
* MDL-41434 - When updating a SCORM package we no longer delete and re-create the scorm_scoes table - we now use a sortorder field.&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
&lt;br /&gt;
* MDL-40415 - OPcache extension fully supported and recommended&lt;br /&gt;
* MDL-31501 - New session drivers supporting files, database and memcached storage&lt;br /&gt;
* MDL-40545 - New $CFG-&amp;gt;localcachedir setting (intended for clustered servers)&lt;br /&gt;
* MDL-40563 - Improved theme resource caching (local cache compatible)&lt;br /&gt;
* MDL-40546 - Improved javascript  caching (local cache compatible)&lt;br /&gt;
* MDL-41019 - Language caching improvements (local cache compatible)&lt;br /&gt;
* MDL-41017 - htmlpurifier caching improvements (local cache compatible)&lt;br /&gt;
* MDL-39474 - Developer debug checks improvements&lt;br /&gt;
* MDL-38570 - automatic temp directory cleanup&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-40770 - New TinyMCE editor icons&lt;br /&gt;
* MDL-11270 - Significantly improved MS SQL Server compatibility&lt;br /&gt;
* MDL-39985 - Full MariaDB support&lt;br /&gt;
* MDL-19390 - Email notification for new users added manually&lt;br /&gt;
* MDL-33955 - Support for open_basedir restriction&lt;br /&gt;
* MDL-9873 - Question text now required&lt;br /&gt;
* MDL-41245, MDL-41437,  MDL-41086 - Multiple installation and upgrade fixes and improvements&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
&lt;br /&gt;
* To be published after release.&lt;br /&gt;
&lt;br /&gt;
===For developers: API changes===&lt;br /&gt;
&lt;br /&gt;
* MDL-39854 - Automatic class loader&lt;br /&gt;
* MDL-39797 - New events infrastructure&lt;br /&gt;
* MDL-41267 - Support for sub-plugins in admin tool plugins&lt;br /&gt;
* MDL-26943 - Support for sub-plugins in local plugins&lt;br /&gt;
* MDL-20045 - Unofficial support for custom context levels&lt;br /&gt;
* MDL-40359 - 3rd party libraries updated to latest versions&lt;br /&gt;
* MDL-40305, MDL-40940 - PHPUnit testcase autoloader&lt;br /&gt;
* MDL-23493 - Support for including a font through theme CSS&lt;br /&gt;
* MDL-35434 - File picker available in themes settings&lt;br /&gt;
* MDL-40248 - better support for subplugins in Activity chooser&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Upgrade notes for developers====&lt;br /&gt;
&lt;br /&gt;
;Assignment: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/assign/upgrade.txt;hb=master&lt;br /&gt;
;Backup: http://git.moodle.org/gw?p=moodle.git;a=blob;f=backup/upgrade.txt;hb=master&lt;br /&gt;
;Cache: http://git.moodle.org/gw?p=moodle.git;a=blob;f=cache/upgrade.txt;hb=master&lt;br /&gt;
;Calendar: http://git.moodle.org/gw?p=moodle.git;a=blob;f=calendar/upgrade.txt;hb=master&lt;br /&gt;
;Core: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/upgrade.txt;hb=master&lt;br /&gt;
;Course formats: http://git.moodle.org/gw?p=moodle.git;a=blob;f=course/format/upgrade.txt;hb=master&lt;br /&gt;
;Enrolment plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=enrol/upgrade.txt;hb=master&lt;br /&gt;
;Forum: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/forum/upgrade.txt;hb=master&lt;br /&gt;
;Question types: http://git.moodle.org/gw?p=moodle.git;a=blob;f=question/type/upgrade.txt;hb=master&lt;br /&gt;
;Repositories: http://git.moodle.org/gw?p=moodle.git;a=blob;f=repository/upgrade.txt;hb=master&lt;br /&gt;
;Themes: http://git.moodle.org/gw?p=moodle.git;a=blob;f=theme/upgrade.txt;hb=master&lt;br /&gt;
;TinyMCE plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/editor/tinymce/upgrade.txt;hb=master&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.6]]&lt;br /&gt;
 &lt;br /&gt;
[[es:Notas de Moodle 2.6]]&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.6]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42533</id>
		<title>Moodle 2.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.6_release_notes&amp;diff=42533"/>
		<updated>2013-10-11T13:21:10Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* UNDER CONSTRUCTION */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
=UNDER CONSTRUCTION=&lt;br /&gt;
&lt;br /&gt;
Release date: November 2013 (Not yet released)&lt;br /&gt;
 &lt;br /&gt;
===Requirements===&lt;br /&gt;
&lt;br /&gt;
These are just minimums.  We recommend keeping all your software updated. &lt;br /&gt;
&lt;br /&gt;
* Recommended minimum browser: recent Google Chrome, recent Mozilla Firefox, Safari 6, Internet Explorer 9 (IE 10 required for drag and drop of files from outside the browser into Moodle)&lt;br /&gt;
* Moodle upgrade:  Moodle 2.2 or later (if upgrading from earlier versions, you must upgrade to 2.2.11 as a first step)&lt;br /&gt;
* Minimum DB versions: PostgreSQL 8.3, MySQL 5.1.33, MariaDB 5.2, MSSQL 2005 or Oracle 10.2&lt;br /&gt;
* Minimum PHP version: PHP 5.3.3 (always use latest PHP 5.3.x, 5.4.x or 5.5.x on Windows - http://windows.php.net/download/)&lt;br /&gt;
* New PHP extensions: zlib extension now recommended&lt;br /&gt;
&lt;br /&gt;
NOTE: IE8 and Safari 5 are not fully supported any more, they should still work but they are not tested regularly and there might be some known problems, IE6 and IE7 are not compatible with Moodle. Like most of the world&#039;s web sites and browser producers, we encourage you to keep your browsers current to improve security and functionality while saving us valuable time.  ([http://googleappsupdates.blogspot.ca/2012/09/supporting-modern-browsers-internet.html For example see what Google is doing])&lt;br /&gt;
&lt;br /&gt;
===Major new features===&lt;br /&gt;
&lt;br /&gt;
* MDL-31776 - Alternate name fields&lt;br /&gt;
* MDL-13114 - Bulk course creation tool&lt;br /&gt;
* MDL-40121 - New Single Activity Course Format (and removed SCORM course format MDL-40122)&lt;br /&gt;
* MDL-30740 - Microsoft Skydrive repository&lt;br /&gt;
* MDL-17081 - Roles import and export&lt;br /&gt;
* MDL-41098 - New Atto simple text editor suitable for iOS devices&lt;br /&gt;
* MDL-37565 - Toolbar switching in TinyMCE editor (one or multiple lines)&lt;br /&gt;
* MDL-41866 - Multiple calendar support (MDL-18375 was the original issue but it was decided we would split this into two separate tasks, hence the newly created epic).&lt;br /&gt;
&lt;br /&gt;
====Assignment activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-38359 - New marker allocation and grading workflow settings&lt;br /&gt;
* MDL-37621 - Admins can set assignment setting defaults&lt;br /&gt;
* MDL-42023 - Edit PDF plugin&lt;br /&gt;
* MDL-37148 - Lots more webservices&lt;br /&gt;
&lt;br /&gt;
====Forum activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-29663 - Forum read tracking options now Off, On, and Forced.&lt;br /&gt;
* MDL-41933 - Option to set default read tracking option.&lt;br /&gt;
&lt;br /&gt;
====SCORM activity====&lt;br /&gt;
&lt;br /&gt;
* MDL-28579 / MDL-41580 - Allow use of file aliases and direct linking to imsmanifest.xml inside an extracted zip in the file system repository.&lt;br /&gt;
* MDL-39910 - Improved SCORM player with responsive design elements and better support for mobile devices.&lt;br /&gt;
* MDL-39926 - New objectives report&lt;br /&gt;
* MDL-41290 - Improved user level reporting with ability to export data.&lt;br /&gt;
* MDL-41434 - When updating a SCORM package we no longer delete and re-create the scorm_scoes table - we now use a sortorder field.&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
&lt;br /&gt;
* MDL-40415 - OPcache extension fully supported and recommended&lt;br /&gt;
* MDL-31501 - New session drivers supporting files, database and memcached storage&lt;br /&gt;
* MDL-40545 - New $CFG-&amp;gt;localcachedir setting (intended for clustered servers)&lt;br /&gt;
* MDL-40563 - Improved theme resource caching (local cache compatible)&lt;br /&gt;
* MDL-40546 - Improved javascript  caching (local cache compatible)&lt;br /&gt;
* MDL-41019 - Language caching improvements (local cache compatible)&lt;br /&gt;
* MDL-41017 - htmlpurifier caching improvements (local cache compatible)&lt;br /&gt;
* MDL-39474 - Developer debug checks improvements&lt;br /&gt;
* MDL-38570 - automatic temp directory cleanup&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-40770 - New TinyMCE editor icons&lt;br /&gt;
* MDL-11270 - Significantly improved MS SQL Server compatibility&lt;br /&gt;
* MDL-39985 - Full MariaDB support&lt;br /&gt;
* MDL-19390 - Email notification for new users added manually&lt;br /&gt;
* MDL-33955 - Support for open_basedir restriction&lt;br /&gt;
* MDL-9873 - Question text now required&lt;br /&gt;
* MDL-41245, MDL-41437,  MDL-41086 - Multiple installation and upgrade fixes and improvements&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
&lt;br /&gt;
* To be published after release.&lt;br /&gt;
&lt;br /&gt;
===For developers: API changes===&lt;br /&gt;
&lt;br /&gt;
* MDL-39854 - Automatic class loader&lt;br /&gt;
* MDL-39797 - New events infrastructure&lt;br /&gt;
* MDL-41267 - Support for sub-plugins in admin tool plugins&lt;br /&gt;
* MDL-26943 - Support for sub-plugins in local plugins&lt;br /&gt;
* MDL-20045 - Unofficial support for custom context levels&lt;br /&gt;
* MDL-40359 - 3rd party libraries updated to latest versions&lt;br /&gt;
* MDL-40305, MDL-40940 - PHPUnit testcase autoloader&lt;br /&gt;
* MDL-23493 - Support for including a font through theme CSS&lt;br /&gt;
* MDL-35434 - File picker available in themes settings&lt;br /&gt;
* MDL-40248 - better support for subplugins in Activity chooser&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Upgrade notes for developers====&lt;br /&gt;
&lt;br /&gt;
;Assignment: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/assign/upgrade.txt;hb=master&lt;br /&gt;
;Backup: http://git.moodle.org/gw?p=moodle.git;a=blob;f=backup/upgrade.txt;hb=master&lt;br /&gt;
;Cache: http://git.moodle.org/gw?p=moodle.git;a=blob;f=cache/upgrade.txt;hb=master&lt;br /&gt;
;Calendar: http://git.moodle.org/gw?p=moodle.git;a=blob;f=calendar/upgrade.txt;hb=master&lt;br /&gt;
;Core: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/upgrade.txt;hb=master&lt;br /&gt;
;Course formats: http://git.moodle.org/gw?p=moodle.git;a=blob;f=course/format/upgrade.txt;hb=master&lt;br /&gt;
;Enrolment plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=enrol/upgrade.txt;hb=master&lt;br /&gt;
;Forum: http://git.moodle.org/gw?p=moodle.git;a=blob;f=mod/forum/upgrade.txt;hb=master&lt;br /&gt;
;Question types: http://git.moodle.org/gw?p=moodle.git;a=blob;f=question/type/upgrade.txt;hb=master&lt;br /&gt;
;Repositories: http://git.moodle.org/gw?p=moodle.git;a=blob;f=repository/upgrade.txt;hb=master&lt;br /&gt;
;Themes: http://git.moodle.org/gw?p=moodle.git;a=blob;f=theme/upgrade.txt;hb=master&lt;br /&gt;
;TinyMCE plugins: http://git.moodle.org/gw?p=moodle.git;a=blob;f=lib/editor/tinymce/upgrade.txt;hb=master&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.6]]&lt;br /&gt;
 &lt;br /&gt;
[[es:Notas de Moodle 2.6]]&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.6]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Eric_Merrill&amp;diff=42323</id>
		<title>User:Eric Merrill</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Eric_Merrill&amp;diff=42323"/>
		<updated>2013-09-12T22:15:30Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Setting up a bare page for myself&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Eric Merrill is a Learning Management Programmer at [http://www.oakland.edu Oakland University].&lt;br /&gt;
&lt;br /&gt;
Eric writes and maintains the [https://moodle.org/plugins/view.php?plugin=enrol_lmb Banner/Luminis Message Broker] enrollment plugin.&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Insecure_configuration_management&amp;diff=42322</id>
		<title>Security:Insecure configuration management</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Insecure_configuration_management&amp;diff=42322"/>
		<updated>2013-09-12T22:00:33Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: Update to represent more recent version of Moodle (and using git).&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
&lt;br /&gt;
Evil Hacker somehow gets access to your server some time and installs some nasty code. For example, they could add some code to the login page that records every username and password entered, and sends it back to evel-hacker.com.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, you have no procedures in place for detecting that this is happening.&lt;br /&gt;
&lt;br /&gt;
Another problem is not updating to the latest Moodle release, which means that you will be running a version of Moodle with know security holes.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
&lt;br /&gt;
This is not really a problem that can be solved from within Moodle code. However, any Moodle code that does install other PHP code (for example admin/langimport.php) must be written with extreme care.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
&lt;br /&gt;
* If you are writing code like admin/langimport.php, make sure you know what you are doing.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
&lt;br /&gt;
* Keep up-to-date with the latest Moodle release from whichever branch you are using.&lt;br /&gt;
** Register your Moodle site, so you get notified of security problems before the general public.&lt;br /&gt;
* Think about how you deploy the Moodle code to your server. For example, if you [[:en:Git_for_Administrators|use git]], then &#039;git status&#039; will tell you which files have been edited.&lt;br /&gt;
** Alternatively, if you upload the Moodle code manually, delete all the old code except config.php before you upload a new version.&lt;br /&gt;
* Be very careful who can access your servers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing&amp;diff=39721</id>
		<title>Acceptance testing</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing&amp;diff=39721"/>
		<updated>2013-05-15T13:32:06Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: /* Advanced usage */ Adding instructions for different browsers&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
This page describes how we describe Moodle&#039;s functionalities and how we automatically test all of them.&lt;br /&gt;
&lt;br /&gt;
Behat is a behavioural driven development (BDD) tool written in PHP, it can parse a human-readable list of sentences (called steps) and execute actions in a browser using Selenium or other tools to simulate users interactions.&lt;br /&gt;
&lt;br /&gt;
For technical info: https://docs.moodle.org/dev/Behat_integration&lt;br /&gt;
&lt;br /&gt;
=== How it works ===&lt;br /&gt;
Behat parses and executes features files which describes Moodle&#039;s features (for example &#039;&#039;Post in a forum&#039;&#039;), each feature file is composed by many scenarios (for example &#039;&#039;Add a post to a discussion&#039;&#039; or &#039;&#039;Create a new discussion&#039;&#039;), and finally each scenario is composed by steps (for example  &#039;&#039;I press &amp;quot;Post to Forum&amp;quot;&#039;&#039; or &#039;&#039;I should see &amp;quot;My post title&amp;quot;&#039;&#039;). When the feature file is executed, every step internally is translated into an PHP method and is executed.&lt;br /&gt;
&lt;br /&gt;
This features are executed nightly in the HQ servers with all the supported databases (MySQL, PostgreSQL, MSSQL and Oracle) and with different browsers (Firefox, Internet Explorer, Safari and Chrome) to avoid regressions and test new functionalities.&lt;br /&gt;
&lt;br /&gt;
=== Examples ===&lt;br /&gt;
&lt;br /&gt;
* There is a closed list of steps to use in the features, a feature written with the basic (or low-level) steps looks like this:&lt;br /&gt;
  @auth&lt;br /&gt;
  &#039;&#039;&#039;Feature&#039;&#039;&#039;: Login&lt;br /&gt;
    In order to login&lt;br /&gt;
    As a moodle user&lt;br /&gt;
    I need to be able to validate the username and password against moodle&lt;br /&gt;
    &lt;br /&gt;
    &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Login as an existing user&lt;br /&gt;
      Given I am on &amp;quot;login/index.php&amp;quot;&lt;br /&gt;
      When I fill in &amp;quot;username&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
      And I fill in &amp;quot;password&amp;quot; with &amp;quot;moodle&amp;quot;&lt;br /&gt;
      And I press &amp;quot;loginbtn&amp;quot;&lt;br /&gt;
      Then I should see &amp;quot;Moodle 101: Course Name&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Login as an unexisting user&lt;br /&gt;
      Given I am on &amp;quot;login/index.php&amp;quot;&lt;br /&gt;
      When I fill in &amp;quot;username&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
      And I fill in &amp;quot;password&amp;quot; with &amp;quot;moodle&amp;quot;&lt;br /&gt;
      And I press &amp;quot;loginbtn&amp;quot;&lt;br /&gt;
      Then I should see &amp;quot;Moodle 101: Course Name&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that The 3 sentences below &#039;&#039;Feature: Login&#039;&#039; are only information about what we want to test.&lt;br /&gt;
&lt;br /&gt;
These are simple scenarios, but most of Moodle&#039;s functionalities would require a huge list of this steps to test a scenario, imagine a &#039;&#039;Add a post to a discussion&#039;&#039; scenario; you need to login, create a course, create a user and enrol it in the course... Most of this steps is not what we intend to test in a &#039;&#039;Post in a forum&#039;&#039; feature, Moodle provides extra steps to quickly set up the context required to test a Moodle feature, for example:&lt;br /&gt;
&lt;br /&gt;
  @mod @mod_forum&lt;br /&gt;
  &#039;&#039;&#039;Feature&#039;&#039;&#039;: Add forum activities and discussions&lt;br /&gt;
    In order to discuss topics with other users&lt;br /&gt;
    As a moodle teacher&lt;br /&gt;
    I need to add forum activities to moodle courses&lt;br /&gt;
    &lt;br /&gt;
    &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Add a forum and a discussion&lt;br /&gt;
      &#039;&#039;&#039;Given&#039;&#039;&#039; the following &amp;quot;users&amp;quot; exists:&lt;br /&gt;
        | username | firstname | lastname | email |&lt;br /&gt;
        | teacher1 | Teacher | 1 | teacher1@asd.com |&lt;br /&gt;
      &#039;&#039;&#039;And&#039;&#039;&#039; the following &amp;quot;courses&amp;quot; exists:&lt;br /&gt;
        | fullname | shortname | category |&lt;br /&gt;
        | Course 1 | C1 | 0 |&lt;br /&gt;
      &#039;&#039;&#039;And&#039;&#039;&#039; the following &amp;quot;course enrolments&amp;quot; exists:&lt;br /&gt;
        | user | course | role |&lt;br /&gt;
        | teacher1 | C1 | editingteacher |&lt;br /&gt;
      &#039;&#039;&#039;And&#039;&#039;&#039; I log in as &amp;quot;teacher1&amp;quot;&lt;br /&gt;
      &#039;&#039;&#039;And&#039;&#039;&#039; I follow &amp;quot;Course 1&amp;quot;&lt;br /&gt;
      &#039;&#039;&#039;And&#039;&#039;&#039; I turn editing mode on&lt;br /&gt;
      &#039;&#039;&#039;And&#039;&#039;&#039; I add a &amp;quot;Forum&amp;quot; to section &amp;quot;1&amp;quot; and I fill the form with:&lt;br /&gt;
        | Forum name | Test forum name |&lt;br /&gt;
        | Forum type | Standard forum for general use |&lt;br /&gt;
        | Description | Test forum description |&lt;br /&gt;
      &#039;&#039;&#039;When&#039;&#039;&#039; I add a new discussion to &amp;quot;Test forum name&amp;quot; forum with:&lt;br /&gt;
        | Subject | Forum post subject |&lt;br /&gt;
        | Message | This is the body |&lt;br /&gt;
      &#039;&#039;&#039;Then&#039;&#039;&#039; I should see &amp;quot;Test forum name&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that:&lt;br /&gt;
&lt;br /&gt;
* Each scenario is executed in an isolated testing environment, so the first step begins with an empty moodle site and what you set up in an scenario (like the &#039;&#039;Test forum name&#039;&#039; forum in the example above) is cleaned up after the scenario execution&lt;br /&gt;
* The prefixes &amp;quot;Given&amp;quot;, &amp;quot;When&amp;quot; and &amp;quot;Then&amp;quot; are only informative and they are used to define the context (Given), specify the action (When) and check the results (Then), using them properly helps to understand what the scenario is testing.&lt;br /&gt;
&lt;br /&gt;
== Quick start ==&lt;br /&gt;
&lt;br /&gt;
This is a quick introduction to write a functional test (acceptance tests) using steps in a development/testing site, please DON&#039;T USE THIS IN A PRODUCTION SITE.&lt;br /&gt;
&lt;br /&gt;
To let you experience the pleasure of watching a feature file doing &amp;quot;your work&amp;quot; automatically in a real browser, this guide includes 2 optional steps to download Selenium and run it in another CLI.&lt;br /&gt;
&lt;br /&gt;
# Open a command line interface&lt;br /&gt;
# &#039;&#039;&#039;cd /to/your/moodle/dirroot&#039;&#039;&#039;&lt;br /&gt;
# Edit config.php adding the following lines before the lib/setup.php include&lt;br /&gt;
#: &amp;lt;code language=&amp;quot;text&amp;quot;&amp;gt;$CFG-&amp;gt;behat_prefix = &#039;b_&#039;;&lt;br /&gt;
$CFG-&amp;gt;behat_dataroot = &#039;/path/to/your/behat/dataroot/directory&#039;;&lt;br /&gt;
$CFG-&amp;gt;behat_switchcompletely = true;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
# &#039;&#039;&#039;curl http://getcomposer.org/installer | php&#039;&#039;&#039; (In case you have problems read https://docs.moodle.org/dev/Acceptance_testing#Installation)&lt;br /&gt;
# &#039;&#039;&#039;php admin/tool/behat/cli/init.php&#039;&#039;&#039;&lt;br /&gt;
# Download selenium-server-standalone-2.NN.N.jar from http://seleniumhq.org/download/, under &amp;quot;Selenium server (formerly the Selenium RC Server)&amp;quot;&lt;br /&gt;
# Open another command line interface and run &#039;&#039;&#039;java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar&#039;&#039;&#039;&lt;br /&gt;
# &#039;&#039;&#039;vendor/bin/behat --config /path/to/your/behat/dataroot/directory/behat/behat.yml&#039;&#039;&#039;&lt;br /&gt;
# You just ran the current Moodle tests, now let&#039;s add your own test, add a blog entry for example&lt;br /&gt;
# Browse to your $CFG-&amp;gt;wwwroot, this is an empty test site and it is reset before each test (called scenario)&lt;br /&gt;
# From this point follow the steps you would follow to add manually a blog entry (login credentials are admin/admin)&lt;br /&gt;
# When you are done go to &#039;Site administration&#039; -&amp;gt; &#039;Development&#039; -&amp;gt; &#039;Acceptance testing&#039;, you will find the list of &amp;quot;actions&amp;quot; that can be run automatically, you can filter them to find what do you need to do (more steps can be added if you need, more info in https://docs.moodle.org/dev/Acceptance_testing#Adding_steps_definitions)&lt;br /&gt;
# To &#039;add a blog entry&#039; we need to:&lt;br /&gt;
## Log in the system as a valid user&lt;br /&gt;
## Expand &#039;My profile&#039; node of the navigation block&lt;br /&gt;
## Expand the &#039;Blogs&#039; node of the navigation block&lt;br /&gt;
## Follow he &#039;Add a new entry&#039; link&lt;br /&gt;
## Fill the moodle form with values for &#039;Entry title&#039; and &#039;Blog entry body&#039;&lt;br /&gt;
## Press the &#039;Save changes&#039; button&lt;br /&gt;
## Verify you see the values you entered in the form and verify you are not in the form page&lt;br /&gt;
# This translated to steps is:&lt;br /&gt;
#: &amp;lt;code language=&amp;quot;text&amp;quot;&amp;gt;Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
And I expand &amp;quot;My profile&amp;quot; node&lt;br /&gt;
And I expand &amp;quot;Blogs&amp;quot; node&lt;br /&gt;
And I follow &amp;quot;Add a new entry&amp;quot;&lt;br /&gt;
And I fill the moodle form with:&lt;br /&gt;
  | Entry title | I&#039;m the name |&lt;br /&gt;
  | Blog entry body | I&#039;m the description |&lt;br /&gt;
When I press &amp;quot;Save changes&amp;quot;&lt;br /&gt;
Then I should see &amp;quot;Blog entries&amp;quot;&lt;br /&gt;
And I should see &amp;quot;I&#039;m the description&amp;quot;&lt;br /&gt;
And I should not see &amp;quot;Required&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
# We need to wrap this steps following the behaviour driven development guidelines (more info in https://docs.moodle.org/dev/Acceptance_testing#Writing_features)&lt;br /&gt;
#: &amp;lt;code language=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
@core @core_blog&lt;br /&gt;
Feature: Add a blog entry&lt;br /&gt;
  In order to let the world know about me&lt;br /&gt;
  As a user&lt;br /&gt;
  I need to write blog entries&lt;br /&gt;
&lt;br /&gt;
  @javascript&lt;br /&gt;
  Scenario: Add a blog entry with valid data&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I expand &amp;quot;My profile&amp;quot; node&lt;br /&gt;
    And I expand &amp;quot;Blogs&amp;quot; node&lt;br /&gt;
    And I follow &amp;quot;Add a new entry&amp;quot;&lt;br /&gt;
    And I fill the moodle form with:&lt;br /&gt;
      | Entry title | I&#039;m the name |&lt;br /&gt;
      | Blog entry body | I&#039;m the description |&lt;br /&gt;
    When I press &amp;quot;Save changes&amp;quot;&lt;br /&gt;
    Then I should see &amp;quot;View all of my entries&amp;quot;&lt;br /&gt;
    And I should see &amp;quot;I&#039;m a description&amp;quot;&lt;br /&gt;
    And I should not see &amp;quot;Required&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
# And save it into a file, in this case &#039;&#039;&#039;blog/tests/behat/add_entry.feature&#039;&#039;&#039;&lt;br /&gt;
# &#039;&#039;&#039;php admin/tool/behat/cli/util.php --enable&#039;&#039;&#039;  (This will update the available tests and steps definitions)&lt;br /&gt;
# &#039;&#039;&#039;vendor/bin/behat --config /path/to/your/behat/dataroot/directory/behat/behat.yml --tags @core_blog&#039;&#039;&#039;&lt;br /&gt;
# Selenium will open a browser (firefox by default) and you will see how the steps you have been writting are executed&lt;br /&gt;
&lt;br /&gt;
You can also try to expand non existing nodes or change the &#039;Then&#039; assertions to get a beautiful failure.&lt;br /&gt;
&lt;br /&gt;
For detailed steps and/or troubleshooting:&lt;br /&gt;
* https://docs.moodle.org/dev/Acceptance_testing#Running_tests&lt;br /&gt;
* https://docs.moodle.org/dev/Acceptance_testing#Writing_features&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
* PHP 5.4 (see https://docs.moodle.org/dev/Acceptance_testing#Advanced_usage for PHP 5.3, only for non-production sites)&lt;br /&gt;
* Other dependencies are managed by the composer installer&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
* Edit config.php&lt;br /&gt;
** Use $CFG-&amp;gt;behat_dataroot to set the directory where behat test environment dataroot will be stored, something like &#039;&#039;&#039;$CFG-&amp;gt;behat_dataroot = &#039;/your/directory/path&#039;;&#039;&#039;&#039;. Ensure the directory can be created or have write permissions&lt;br /&gt;
** Use $CFG-&amp;gt;behat_prefix to set the database prefix of the behat test environment database tables, something like &#039;&#039;&#039;$CFG-&amp;gt;behat_prefix = &#039;behat_&#039;;&#039;&#039;&#039;&lt;br /&gt;
* Download composer&lt;br /&gt;
** &#039;&#039;&#039;cd /your/moodle/dirroot&#039;&#039;&#039;&lt;br /&gt;
** &#039;&#039;&#039;curl http://getcomposer.org/installer | php&#039;&#039;&#039;&lt;br /&gt;
*** If you don&#039;t have curl installed or you have problems running &#039;&#039;&#039;curl http://getcomposer.org/installer | php&#039;&#039;&#039;:&lt;br /&gt;
**** Download &#039;&#039;&#039;http://getcomposer.org/installer&#039;&#039;&#039;&lt;br /&gt;
**** Store it in /your/moodle/dirroot/composerinstaller.php for example&lt;br /&gt;
**** Run it from /your/moodle/dirroot with &#039;&#039;&#039;php composerinstaller.php&#039;&#039;&#039;, you can delete this file after running the next step (&#039;&#039;&#039;php composer.phar update --dev&#039;&#039;&#039;)&lt;br /&gt;
* Install behat dependencies and enable the test environment&lt;br /&gt;
** &#039;&#039;&#039;cd /your/moodle/dirroot&#039;&#039;&#039;&lt;br /&gt;
** &#039;&#039;&#039;php admin/tool/behat/cli/init.php&#039;&#039;&#039;&lt;br /&gt;
* (Optional) If you want to run tests that involves Javascript (most of them) you will also need Selenium&lt;br /&gt;
** Download it from http://seleniumhq.org/download/, named &amp;quot;Selenium server (formerly the Selenium RC Server)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Running tests ==&lt;br /&gt;
# Start the PHP built-in web server&lt;br /&gt;
#* Open a command line interface and &#039;&#039;&#039;cd /to/your/moodle/dirroot&#039;&#039;&#039;&lt;br /&gt;
#* &#039;&#039;&#039;php -S localhost:8000&#039;&#039;&#039; (This is the default address, if you want to use another one you can override it in config.php with $CFG-&amp;gt;behat_wwwroot attribute; more info in https://docs.moodle.org/dev/Acceptance_testing#Advanced_usage)&lt;br /&gt;
# (Optional) Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* Open another command line interface and &#039;&#039;&#039;java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar&#039;&#039;&#039;&lt;br /&gt;
# Run Behat&lt;br /&gt;
#* &#039;&#039;&#039;vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behat/behat.yml&#039;&#039;&#039; (For more options &#039;&#039;&#039;vendor/bin/behat --help&#039;&#039;&#039; or http://docs.behat.org/guides/6.cli.html)&lt;br /&gt;
#* In case you don&#039;t want to run Javascript tests use the Behat tags option to skip them, &#039;&#039;&#039;vendor/bin/behat --tags ~@javascript --config /path/to/your/CFG_behat_dataroot/behat/behat.yml&#039;&#039;&#039;&lt;br /&gt;
# (Optional) If you are adding new tests or steps definitions update the tests list:&lt;br /&gt;
#* &#039;&#039;&#039;php admin/tool/behat/cli/util.php --enable&#039;&#039;&#039;&lt;br /&gt;
# (Optional) Disable test environment (in case you want to use the PHP built-in web server for regular moodle environment)&lt;br /&gt;
#* &#039;&#039;&#039;php admin/tool/behat/cli/util.php --disable&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests filters ===&lt;br /&gt;
With the &#039;&#039;&#039;--tags&#039;&#039;&#039; or the &#039;&#039;&#039;-name&#039;&#039;&#039; Behat options you can filter which tests are going to run or which ones are going to be skipped. There are few tags that you might be interested in:&lt;br /&gt;
* &#039;&#039;&#039;@javascript&#039;&#039;&#039;: All the tests that runs in a browser using Javascript; they require Selenium to be running, otherwise an exception will be thrown.&lt;br /&gt;
* &#039;&#039;&#039;@_only_local&#039;&#039;&#039;: All the tests that involves file uploading or any OS feature that is not 100% part of the browser. They should only be executed when Selenium is running in the same machine where the tests are running.&lt;br /&gt;
* &#039;&#039;&#039;@_cross_browser&#039;&#039;&#039;: All the tests that should run against multiple combinations of browsers + OS in a regular basis. The features that are sensitive to different combinations of OS and browsers should be tagges as @_cross_browser.&lt;br /&gt;
* &#039;&#039;&#039;@componentname&#039;&#039;&#039;: Moodle features uses the [https://docs.moodle.org/dev/Frankenstyle Frankenstyle] component name to tag the features according to the Moodle subsystem they belong to.&lt;br /&gt;
&lt;br /&gt;
== Advanced usage ==&lt;br /&gt;
There are a few settings for advanced use of Behat and execution in continuous integration systems, by default all this options are disabled, use this settings only if you know what you are doing.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Different test server URL&#039;&#039;&#039;, by default the test web server only can be accessed in localhost. If for example your are interested in allowing accesses from your local network because your Jenkins server is there you can set $CFG-&amp;gt;behat_wwwroot to &#039;&#039;&#039;http://my.computer.local.ip:8000&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Behat configuration&#039;&#039;&#039;, Moodle writes a behat.yml config file with info about the available tests and steps definitions along with other Behat parameters, you can override the Behat parameters we set and add your new parameters, your parameters will be merged with the Moodle ones giving priority to your values in case of conflict. This is useful for an advanced use of Behat, with multiple profiles, output formats, integration with continuous servers... &lt;br /&gt;
* &#039;&#039;&#039;Running with a browser other than Firefox&#039;&#039;&#039;, by adding the following code to your config.php you can change the selected browser that is run when behat is invoked. In this case Chrome is selected, but internet explorer, firefox, iphone, android, chrome, htmlunit should be valid options. You will need to run &#039;&#039;&#039;php admin/tool/behat/cli/init.php&#039;&#039;&#039; for changes to take effect.&lt;br /&gt;
&amp;lt;code language=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_config = array(&lt;br /&gt;
    &#039;default&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;extensions&#039; =&amp;gt; array(&lt;br /&gt;
            &#039;Behat\MinkExtension\Extension&#039; =&amp;gt; array(&lt;br /&gt;
                &#039;selenium2&#039; =&amp;gt; array(&lt;br /&gt;
                    &#039;browser&#039; =&amp;gt; &#039;chrome&#039;&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        )&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
:Note that for Chrome, you will need the Selenium Chrome Driver (https://code.google.com/p/selenium/wiki/ChromeDriver), and it will need to be installed in the command search path.&lt;br /&gt;
* &#039;&#039;&#039;Switch completely to test environment&#039;&#039;&#039;, DON&#039;T USE THIS SETTING IN PRODUCTION SITES! all the site users would be using the test environment instead of the regular one with your courses and all your data and they wouldn&#039;t be able to login in your site; this setting should only be used in development/testing installations. It&#039;s purpose is to ease the integration with cloud-based continuous integration systems, another possible use is to allow acceptance testing in a development environment without PHP 5.4.&lt;br /&gt;
** Note that when using cloud-based systems that can make use of non-standard capabilities like Saucelabs, you might want to provide configuration attributes containing the &#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039; character, which is automatically converted to &#039;&#039;&#039;&#039;_&#039;&#039;&#039;&#039; by the Symfony configuration manager that Behat is making use of (@see Symfony\Component\Config\Definition\Processor::normalizeKeys()) a way to avoid this restriction is to, adding to the vars you set like &#039;&#039;&#039;&#039;max-duration&#039;&#039;&#039;&#039; add the same var replacing dashes for underscores, this way the configuration manager will maintain the attribute containing dashes.&lt;br /&gt;
You can find more info and examples of how to use this settings in the config-dist.php file included in the Moodle codebase.&lt;br /&gt;
&lt;br /&gt;
== Writing features ==&lt;br /&gt;
&lt;br /&gt;
All Moodle components and plugins (including 3rd party plugins) can specify their tests in .feature files using all the available steps.&lt;br /&gt;
&lt;br /&gt;
Once you decided which functionality you want to specify as a feature you should:&lt;br /&gt;
# Select the most appropriate Moodle component to include your test and create a COMPONENTNAME/tests/behat/FEATURENAME.feature file&lt;br /&gt;
# Add a tag with the component name in Frankenstyle format (https://docs.moodle.org/dev/Frankenstyle) on the first line along with the plugin type or @core if it&#039;s a core subsystem&lt;br /&gt;
# Begin writing the user story of the feature, including in the &#039;As a ...&#039; statement the main beneficiary of the feature:&lt;br /&gt;
#: &amp;lt;code lang=&amp;quot;text&amp;quot;&amp;gt;@plugintype @plugintype_pluginname&lt;br /&gt;
Feature: FEATURENAME&lt;br /&gt;
  In order to ...    // Why this feature is useful&lt;br /&gt;
  As ...    // It can be &#039;an admin&#039;, &#039;a teacher&#039;, &#039;a student&#039;, &#039;a guest&#039;, &#039;a user&#039;, &#039;a tests writer&#039; and &#039;a developer&#039;&lt;br /&gt;
  I need to ...      // The feature we want&amp;lt;/code&amp;gt;&lt;br /&gt;
# From the beneficiary point of view, think of different scenarios to ensure the feature works as expected&lt;br /&gt;
# For each scenario you thought:&lt;br /&gt;
## Think of the initial context you need, for example &#039;&#039;1 course with 2 students on it and an assignment&#039;&#039;, and which steps do you need to follow (interacting with the browser) to verify the scenario works as expected&lt;br /&gt;
## What you are testing requires Javascript? Think only on the feature you are testing (for example if you want to test that you can view your profile you don&#039;t need Javascript to click on a link and assert against plain HTML, but if you want to test something related with the course&#039;s gradebook you might want to test it with Javascript)&lt;br /&gt;
## Check the steps list (more info in https://docs.moodle.org/dev/Acceptance_testing#Available_steps) and set the initial context data (see https://docs.moodle.org/dev/Acceptance_testing#Fixtures for more info) and the steps to follow to verify all works as it should work. Remember to use &#039;&#039;Given&#039;&#039;, &#039;&#039;When&#039;&#039; and &#039;&#039;Then&#039;&#039; in a way that reflects what the scenario is testing&lt;br /&gt;
## Copy the list of steps to the .feature file with the Scenario header:&lt;br /&gt;
##: &amp;lt;code lang=&amp;quot;text&amp;quot;&amp;gt;Scenario: Short description of the scenario&lt;br /&gt;
  Given step 1&lt;br /&gt;
  And step 2&lt;br /&gt;
  And step 3&lt;br /&gt;
  When step 4&lt;br /&gt;
  And step 5&lt;br /&gt;
  Then step 6&amp;lt;/code&amp;gt;&lt;br /&gt;
## If the steps you are using requires Javascript add the @javascript tag above the &amp;quot;Scenario:&amp;quot; headline&lt;br /&gt;
##:    &amp;lt;code lang=&amp;quot;text&amp;quot;&amp;gt;@javascript&lt;br /&gt;
Scenario: Short description of the scenario&lt;br /&gt;
  ...&lt;br /&gt;
  ...&amp;lt;/code&amp;gt;&lt;br /&gt;
# Run the tests, when creating your new features/scenarios you can specify a &#039;@wip&#039; (work in progress) tag in both the line above the Scenario description and the tests runner (vendor/bin/behat) to execute only the new scenario instead of running the whole set of tests.&lt;br /&gt;
# Add extra tags to the scenario or the feature if required&lt;br /&gt;
#* If there are scenarios that includes files uploads they should be tagged as @_only_local&lt;br /&gt;
#* If there are scenarios that are likely to fail in some browser-OS combinations they can be tagged as @_cross_browser, they will be tested in different OS / browser combinations by Moodle HQ continuous integration servers&lt;br /&gt;
&lt;br /&gt;
=== Available steps ===&lt;br /&gt;
&lt;br /&gt;
Moodle provides a interface to list and filter the steps you can use when writing features. You can access it through the Administration block, following &#039;&#039;&#039;Site Administration&#039;&#039;&#039; -&amp;gt; &#039;&#039;&#039;Development&#039;&#039;&#039; -&amp;gt; &#039;&#039;&#039;Acceptance testing&#039;&#039;&#039;. It allows filtering by keyword, by the Moodle component or by the type of step:&lt;br /&gt;
* Processes to set up the environment&lt;br /&gt;
* Actions that provokes an event&lt;br /&gt;
* Checkings to ensure the outcomes are the expected ones&lt;br /&gt;
&lt;br /&gt;
[[File:Acceptance_testing_UI_2.5.png]]&lt;br /&gt;
&lt;br /&gt;
=== Tips ===&lt;br /&gt;
* You can use a &#039;&#039;&#039;Background&#039;&#039;&#039; section before the &#039;&#039;&#039;Scenario&#039;&#039;&#039; sections, this steps will be executed before the steps of each scenario (http://docs.behat.org/guides/1.gherkin.html#backgrounds)&lt;br /&gt;
* Is better to tests the outcomes against the given data than against language strings, which are depending on the selected language.&lt;br /&gt;
* In case you need to interact with popup windows you need to switch to the window you want to interact with after opening it using the &#039;&#039;&#039;I switch to &amp;quot;popupwindowname&amp;quot; window&#039;&#039;&#039;, close it when you finish interacting with it and return to the main window using &#039;&#039;&#039;I switch to main window&#039;&#039;&#039;&lt;br /&gt;
* The format of the .feature files is YAML which finds out the data hierarchy from the indentation of it&#039;s elements, so be sure that the elements are correctly nested and the indentation is correct using spaces when necessary&lt;br /&gt;
&lt;br /&gt;
=== Providing values to steps ===&lt;br /&gt;
Most of the steps requires values, there are four methods to provide values to steps, the method depends on the step specification, you can know when a steps requires a value because you will see a drop down menu with a closed list of options that the step accepts as argument or an upper case string between double quotes, something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; or it ends with a &#039;&#039;&#039;:&#039;&#039;&#039; . The three methods are:&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, in the &#039;Acceptance testing&#039; interface you can see a dropdown menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
==== Uploading files ====&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_only_local&#039;&#039;&#039; tag&lt;br /&gt;
&lt;br /&gt;
=== Fixtures ===&lt;br /&gt;
&lt;br /&gt;
As seen in [[https://docs.moodle.org/dev/Acceptance_testing#Examples examples]] Moodle provides a way to quickly set up the contextual data (courses, users, enrolments...) that you need to properly test scenarios, this can be done using one of the site templates (TODO) or creating entities in the background section (common for all the steps) or in the &amp;quot;Given&amp;quot; part of your scenario. Note that this steps can only be used to set up the contextual data required to test the feature but they don&#039;t test what they are doing; for example, the &amp;quot;Given the following &amp;quot;users&amp;quot; exists&amp;quot; is not testing that Moodle is able to create a user, but to test that a user can add a blog entry you might want to use this step. For further info, acceptance tests are supposed to be black-boxed tests (the tester don&#039;t know about the internals of the application) and this steps are using internal Moodle data generators instead of running all the steps required to create a user or to create a course, which speeds up the test execution. There are other features to test that all this elements can be properly created.&lt;br /&gt;
&lt;br /&gt;
==== Available elements ====&lt;br /&gt;
Most of the available elements can only be created in relation to other elements, to hide the complexity of the Moodle internals (references by contexts, ids...) the references can be done using more human-friendly mappings. &lt;br /&gt;
&lt;br /&gt;
The examples below shows how to add elements referencing other elements, there are required fields to reference the elements, other attributes will be filled with random data if they are not specified.&lt;br /&gt;
&lt;br /&gt;
* Course categories&lt;br /&gt;
** The required field is idnumber&lt;br /&gt;
** References between parent/children by their idnumber, using the &amp;quot;category&amp;quot; field&lt;br /&gt;
  Given the following &amp;quot;categories&amp;quot; exists:&lt;br /&gt;
    | name       | category | idnumber |&lt;br /&gt;
    | Category 1 | 0        | CAT1     |&lt;br /&gt;
    | Category 2 | CAT1     | CAT2     |&lt;br /&gt;
&lt;br /&gt;
* Courses&lt;br /&gt;
** The required field is shortname&lt;br /&gt;
** Uses the category idnumber as category reference&lt;br /&gt;
  Given the following &amp;quot;courses&amp;quot; exists:&lt;br /&gt;
    | fullname | shortname | category | format | &lt;br /&gt;
    | Course 1 | COURSE1   | CAT1     | topics |&lt;br /&gt;
    | Course 2 | COURSE2   | CAT2     |        |&lt;br /&gt;
&lt;br /&gt;
* Groups&lt;br /&gt;
** The required fields are course and idnumber&lt;br /&gt;
** Uses the course shortname as course reference&lt;br /&gt;
  Given the following &amp;quot;groups&amp;quot; exists:&lt;br /&gt;
    | name    | description | course  | idnumber |&lt;br /&gt;
    | Group 1 | Anything    | COURSE1 | GROUP1   |&lt;br /&gt;
&lt;br /&gt;
* Groupings&lt;br /&gt;
** The required fields are course and idnumber&lt;br /&gt;
** Uses the course shortname as course reference&lt;br /&gt;
  Given the following &amp;quot;groupings&amp;quot; exists:&lt;br /&gt;
    | name       | course  | idnumber  |&lt;br /&gt;
    | Grouping 1 | COURSE1 | GROUPING1 |&lt;br /&gt;
    | Grouping 2 | COURSE1 | GROUPING2 |&lt;br /&gt;
&lt;br /&gt;
* Users&lt;br /&gt;
** The required field is username (if password is not set username value will be used as password too)&lt;br /&gt;
  Given the following &amp;quot;users&amp;quot; exists:&lt;br /&gt;
    | username | email       | firstname | lastname |&lt;br /&gt;
    | testuser | asd@asd.com | Test      | User     |&lt;br /&gt;
&lt;br /&gt;
* Course enrolments&lt;br /&gt;
** The required fields are user, course and role&lt;br /&gt;
** Uses the course shortname as course reference&lt;br /&gt;
** Uses the user username as user reference&lt;br /&gt;
** Uses the role shortname as role reference&lt;br /&gt;
** Uses the enrolment name as enrol reference&lt;br /&gt;
  Given the following &amp;quot;course enrolments&amp;quot; exists:&lt;br /&gt;
    | user     | course  | role           | enrol  |&lt;br /&gt;
    | testuser | COURSE1 | editingteacher | manual |&lt;br /&gt;
&lt;br /&gt;
* System role assigns&lt;br /&gt;
** The required fields are user and role&lt;br /&gt;
** Uses the user username as user reference&lt;br /&gt;
** Uses the role shortname as role reference&lt;br /&gt;
  Given the following &amp;quot;system role assigns&amp;quot; exists:&lt;br /&gt;
    | user     | role    |&lt;br /&gt;
    | testuser | manager |&lt;br /&gt;
&lt;br /&gt;
* Group members&lt;br /&gt;
** The required fields are user and group&lt;br /&gt;
** Uses the group idnumber as group reference&lt;br /&gt;
** Uses the user username as user reference&lt;br /&gt;
  Given the following &amp;quot;group members&amp;quot; exists:&lt;br /&gt;
    | user     | group  |&lt;br /&gt;
    | testuser | GROUP1 |&lt;br /&gt;
&lt;br /&gt;
* Grouping groups&lt;br /&gt;
** The required fields are grouping and group&lt;br /&gt;
** Uses the group idnumber as group reference&lt;br /&gt;
** Uses the grouping idnumber as grouping reference&lt;br /&gt;
  Given the following &amp;quot;grouping groups&amp;quot; exists:&lt;br /&gt;
    | grouping  | group  |&lt;br /&gt;
    | GROUPING1 | GROUP1 |&lt;br /&gt;
&lt;br /&gt;
* Cohorts&lt;br /&gt;
** The required field is idnumber&lt;br /&gt;
  Given the following &amp;quot;cohorts&amp;quot; exists:&lt;br /&gt;
    | name     | idnumber |&lt;br /&gt;
    | Cohort 1 | COHORT1  |&lt;br /&gt;
&lt;br /&gt;
== Adding steps definitions ==&lt;br /&gt;
&lt;br /&gt;
Each Moodle component and plugin (including 3rd party plugins) can add new steps definitions. If you are writing tests and you notice that you are repeating the same group of steps you might want to create a new step definition that allows you to substitute the group of steps for one single step, something like &#039;&#039;I add a forum post with &amp;quot;blablabla&amp;quot; as description&#039;&#039; for example; also you can create whole new steps using the APIs provided by Behat and Mink if what you need to do is not covered by any of the available steps.&lt;br /&gt;
&lt;br /&gt;
=== Check list ===&lt;br /&gt;
&lt;br /&gt;
New steps should be/have:&lt;br /&gt;
* Implemented as public methods of a PHP class whose name must begin with &#039;behat_&#039; prefix and with &#039;.php extension&lt;br /&gt;
* Using the class name as filename (adding the &#039;.php&#039; extension) and extending MOODLEDIRROOT/lib/behat/behat_base.php (or MOODLEDIRROOT/lib/behat/behat_files.php if it&#039;s a repository or is files-related)&lt;br /&gt;
* With a descriptive class name, for example the component name (it will be used when filtering steps definitions)&lt;br /&gt;
* Stored in COMPONENTNAME/tests/behat/ directory or lib/tests/behat/ if is not part of any other component&lt;br /&gt;
* Describe it&#039;s purpose in a single line inside the method doc comment, the size of the comment is not a problem&lt;br /&gt;
* Describe the regular expression with the most appropriate tag inside the method doc comment:&lt;br /&gt;
** &#039;&#039;&#039;@Given&#039;&#039;&#039; - A step to set up the initial context (for example &#039;&#039;the following &amp;quot;courses&amp;quot; exists&#039;&#039;)&lt;br /&gt;
** &#039;&#039;&#039;@When&#039;&#039;&#039; - An action that provokes an event (for example &#039;&#039;I press the button &amp;quot;buttonname&amp;quot;&#039;&#039;)&lt;br /&gt;
** &#039;&#039;&#039;@Then&#039;&#039;&#039; - Checkings to ensure the outcomes are the expected (for example &#039;&#039;I should see &amp;quot;whatever&amp;quot;&#039;&#039;)&lt;br /&gt;
* Depending on the inputs your definition expects you must use a different regular expression:&lt;br /&gt;
** &#039;&#039;&#039;If you expect a number:&#039;&#039;&#039; &amp;quot;(?P&amp;lt;info_about_what_you_expect_number&amp;gt;\d+)&amp;quot; (note that the regular expression is quoted between &#039;&#039;&#039;&amp;quot;&#039;&#039;&#039;)&lt;br /&gt;
** &#039;&#039;&#039;If you expect a string or a text:&#039;&#039;&#039; &amp;quot;(?P&amp;lt;info_about_what_you_expect_string&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot; Don&#039;t use &#039;&#039;&#039;text_selector_string&#039;&#039;&#039; and &#039;&#039;&#039;selector_string&#039;&#039;&#039; as info strings, they are reserved to selector types (note that the regular expression is quoted between &#039;&#039;&#039;&amp;quot;&#039;&#039;&#039;)&lt;br /&gt;
** &#039;&#039;&#039;If you expect a table with key/value pairs (for example to fill a form):&#039;&#039;&#039; Finish your regular expression with &#039;&#039;&#039;:&#039;&#039;&#039; and provide info in the description about the contents of the table&lt;br /&gt;
** &#039;&#039;&#039;If you expect a selector type:&#039;&#039;&#039; &amp;quot;(?P&amp;lt;selector_string&amp;gt;[^&amp;quot;]*)&amp;quot; or &amp;quot;(?P&amp;lt;text_selector_string&amp;gt;[^&amp;quot;]*)&amp;quot; depending on whether you want to use any selector or you want a text-based selector (more info about selectors in https://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps)&lt;br /&gt;
* To make test writer&#039;s life better is good to include explicative info in the subexpressions of the regular expression about what the test writer is supposed to put in there (for example &#039;&#039;I expand &amp;quot;(?P&amp;lt;nodetext&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot; node&#039;&#039;)&lt;br /&gt;
* Is recommended to use the static part of the regular expression as the name of the method, using underscores instead of spaces (see current steps definitions)&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
You can use this example below or any of the existing steps definitions as a template.&lt;br /&gt;
&lt;br /&gt;
* auth/tests/behat/behat_auth.php&lt;br /&gt;
  class behat_auth extends behat_base {&lt;br /&gt;
      /**&lt;br /&gt;
       * Logs in the user. There should exist a user with the same value as username and password&lt;br /&gt;
       *&lt;br /&gt;
       * @Given /^I log in as &amp;quot;(?P&amp;lt;username_string&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot;$/&lt;br /&gt;
       */&lt;br /&gt;
      public function i_log_in_as($username) {&lt;br /&gt;
          return array(new Given(&#039;I am on homepage&#039;),&lt;br /&gt;
              new Given(&#039;I follow &amp;quot;Login&amp;quot;&#039;),&lt;br /&gt;
              new Given(&#039;I fill in &amp;quot;Username&amp;quot; with &amp;quot;&#039;.$username.&#039;&amp;quot;&#039;),&lt;br /&gt;
              new Given(&#039;I fill in &amp;quot;Password&amp;quot; with &amp;quot;&#039;.$username.&#039;&amp;quot;&#039;),&lt;br /&gt;
              new Given(&#039;I press &amp;quot;Login&amp;quot;&#039;),&lt;br /&gt;
              new Given(&#039;I should see &amp;quot;You are logged in as&amp;quot;&#039;));&lt;br /&gt;
      }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Tips ===&lt;br /&gt;
&lt;br /&gt;
If you are creating a completely new step definition there are also a few things to consider:&lt;br /&gt;
* The definition code will be executed by Behat, not by Moodle, you have to keep this in mind for example when throwing exceptions, Behat exceptions will give more info to the user about where is the problem&lt;br /&gt;
** You can find these exceptions in &#039;&#039;&#039;vendor/behat/mink/src/Behat/Mink/Exception/*&#039;&#039;&#039;&lt;br /&gt;
* Selenium is fast, sometimes it tries to interact with DOM elements or tries to execute actions that requires JS that are not loaded or ready to used; this is why, sometimes and randomly, you can see an &amp;quot;element not found&amp;quot; failure&lt;br /&gt;
** The quickest way to solve this problem is using behat_base::find*() methods (where the * corresponds to &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&#039;&#039;&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;, &#039;&#039;&#039;_all&#039;&#039;&#039;, or to a named selector preceded by &#039;&#039;&#039;_&#039;&#039;&#039;, http://mink.behat.org/#named-selectors) which only requires the locator as argument. This methods will wait for the requested element to be ready or return an exception if the element is not found after the timeout value expires, you can also force the timeout value, which defaults to 6 seconds. An example of a named selector use is &#039;&#039;&#039;$button = $this-&amp;gt;find_button(&amp;quot;Save changes&amp;quot;);&#039;&#039;&#039; if you are not sure about the element being available you always can wrap the find*() call in a try &amp;amp; catch.&lt;br /&gt;
** For advanced usages, the spin method is defined in &#039;&#039;&#039;lib/behat/behat_base::spin&#039;&#039;&#039;, consider that all the contents of the closures passed to spin() can be executed more than once, so don&#039;t use irreversible actions that can invalidate the tests results (for example use find() methods but don&#039;t use click() methods)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you create new steps definitions or tests you must run &#039;&#039;&#039;php admin/tool/behat/cli/util.php --enable&#039;&#039;&#039; to update the Behat config file before running &#039;&#039;&#039;vendor/bin/behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Links == &lt;br /&gt;
* Technical info: https://docs.moodle.org/dev/Behat_integration&lt;br /&gt;
* Behat CLI command options: http://docs.behat.org/guides/6.cli.html&lt;br /&gt;
* How to use selectors to interact with the site elements: http://mink.behat.org/#traverse-the-page-selectors&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;br /&gt;
&lt;br /&gt;
[[es:Prueba de aceptación]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Question_behaviours&amp;diff=34234</id>
		<title>Question behaviours</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Question_behaviours&amp;diff=34234"/>
		<updated>2012-06-19T12:11:57Z</updated>

		<summary type="html">&lt;p&gt;Emerrill: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Question_engine_2}}&lt;br /&gt;
This page explains how to go about writing a new question behaviour for the new Moodle [[Question Engine 2|question engine]].&lt;br /&gt;
&lt;br /&gt;
Previous section: [[Overview_of_the_Moodle_question_engine|Overview_of_the_Moodle_question_engine]]&lt;br /&gt;
&lt;br /&gt;
To learn to write question behaviours, you are highly encouraged to read the code of some of the existing behaviours in the Moodle code base. The code for most behaviours is shorter than these instructions!&lt;br /&gt;
&lt;br /&gt;
Also note that all the question engine code has extensive PHP documenter comments that should explain the purpose of every class and method. This document is supposed to provide an overview of the key points to get you started. It does not attempt to duplicate all the details in the PHPdocs.&lt;br /&gt;
&lt;br /&gt;
This document assumes that you understand the data model, where a question attempt comprises a number of steps, and each step has some properties like a state and a mark, and an array of submitted data. This is explained in the [[Overview_of_the_Moodle_question_engine|overview_of_the_Moodle_question_engine]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==File layout==&lt;br /&gt;
&lt;br /&gt;
A question behaviour plugin lives in a folder in &amp;lt;tt&amp;gt;question/behaviour&amp;lt;/tt&amp;gt;. The layout inside that folder follows the typical layout for a Moodle plugin. For example, inside &amp;lt;tt&amp;gt;question/behaviour/mybehaviour/&amp;lt;/tt&amp;gt; we would have:&lt;br /&gt;
&lt;br /&gt;
; behaviour.php : This contains the definition of the &amp;lt;tt&amp;gt;qbehaviour_mybehaviour&amp;lt;/tt&amp;gt; class, which should extend the &amp;lt;tt&amp;gt;question_behaviour&amp;lt;/tt&amp;gt; base class.&lt;br /&gt;
; renderer.php : This contains the definition of the &amp;lt;tt&amp;gt;qbehaviour_mybehaviour_renderer&amp;lt;/tt&amp;gt; class, which should extend the &amp;lt;tt&amp;gt;qbehaviour_renderer&amp;lt;/tt&amp;gt; base class.&lt;br /&gt;
; simpletest/... : Contains the unit tests for this behaviour. You are strongly encouraged to write thorough unit tests to ensure the correctness of your behaviour.&lt;br /&gt;
; lang/en_utf8/qbehaviour_mybehaviour.php : English language strings for this behaviour. You can, of course, include other languages too. The language file must define at least the string giving the model a name, for example &amp;lt;tt&amp;gt;$string[&#039;mybehaviour&#039;] = &#039;My behaviour&#039;;&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Note that question behaviours are not expected to have their own database tables or capabilities. Therefore, there should not be a db sub-folder.&lt;br /&gt;
&lt;br /&gt;
In future, we will probably add the ability for a behaviour to have a settings.php file, so that it can have configuration options.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Question behaviour class==&lt;br /&gt;
&lt;br /&gt;
One instance of this class will be created for each &amp;lt;tt&amp;gt;question_attempt&amp;lt;/tt&amp;gt; that uses this model.&lt;br /&gt;
&lt;br /&gt;
This documentation just lists the methods that you are likely to need to override. See the PHPdocumentor comments for a complete API documentation.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Class declaration===&lt;br /&gt;
&lt;br /&gt;
The class name for the behaviour class must be the plugin name (e.g. &amp;lt;tt&amp;gt;mybehaviour&amp;lt;/tt&amp;gt;) prefixed by &amp;lt;tt&amp;gt;qbehaviour_&amp;lt;/tt&amp;gt;. You must extend the &amp;lt;tt&amp;gt;question_behaviour&amp;lt;/tt&amp;gt; base class, or another model class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class qbehaviour_mybehaviour extends question_behaviour {&lt;br /&gt;
    // ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not need to override the constructor.&lt;br /&gt;
&lt;br /&gt;
===Fields===&lt;br /&gt;
&lt;br /&gt;
When a behaviour class is created, the fields &amp;lt;tt&amp;gt;qa&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;question&amp;lt;/tt&amp;gt; are initialised to point to the question attempt and question that this model is being used by.&lt;br /&gt;
&lt;br /&gt;
From the base class code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    // From the base class.&lt;br /&gt;
    /** @var question_attempt */&lt;br /&gt;
    protected $qa;&lt;br /&gt;
    /** @var question_definition */&lt;br /&gt;
    protected $question;&lt;br /&gt;
&lt;br /&gt;
    public function __construct(question_attempt $qa) {&lt;br /&gt;
        $this-&amp;gt;qa = $qa;&lt;br /&gt;
        $this-&amp;gt;question = $qa-&amp;gt;get_question();&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Methods used during attempts at a question===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====required_question_definition_class()====&lt;br /&gt;
&lt;br /&gt;
Most behaviours can only work with a particular subclasses of &amp;lt;tt&amp;gt;question_definition&amp;lt;/tt&amp;gt;. For example, they may only be able to work work with questions that are &amp;lt;tt&amp;gt;question_automatically_gradable&amp;lt;/tt&amp;gt;s. This method lets the behaviour document that. The type of question passed to the constructor is then checked against the class name returned by this function.&lt;br /&gt;
&lt;br /&gt;
Example from &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function required_question_definition_class() {&lt;br /&gt;
        return &#039;question_automatically_gradable&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_min_fraction()====&lt;br /&gt;
&lt;br /&gt;
Returns the smallest possible [[Question_Engine_2:Design#Words_related_to_grades|fraction]] that this behaviour, applied to this question, may give. Normally this will be the min fraction of the question type, however some behaviours (for example CBM) manipulate the question scores, and so need to return an adjusted min_fraction.&lt;br /&gt;
&lt;br /&gt;
For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_min_fraction() {&lt;br /&gt;
        return $this-&amp;gt;question-&amp;gt;get_min_fraction();&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From &amp;lt;tt&amp;gt;qbehaviour_deferredcbm&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_min_fraction() {&lt;br /&gt;
        return question_cbm::adjust_fraction(&lt;br /&gt;
                parent::get_min_fraction(), question_cbm::HIGH);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_expected_data()====&lt;br /&gt;
&lt;br /&gt;
Some behaviours display additional form controls. When the question form is submitted, these submitted values are read using the standard optional_param function. The question engine needs to know what parameters to look for, and with what types.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;get_expected_data&amp;lt;/tt&amp;gt; function should return an array of expected parameters, with the corresponding param type. Note that the array of expected parameters may depend on the current state of the question attempt. For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredcbm&amp;lt;/tt&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    public function get_expected_data() {&lt;br /&gt;
        if (question_state::is_active($this-&amp;gt;qa-&amp;gt;get_state())) {&lt;br /&gt;
            return array(&#039;certainty&#039; =&amp;gt; PARAM_INT);&lt;br /&gt;
        }&lt;br /&gt;
        return parent::get_expected_data();&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====init_first_step()====&lt;br /&gt;
&lt;br /&gt;
You are unlikely to need to override this method. This is called when the question attempt is actually stared. It gives the behaviour a chance to do any necessary initialisation, including passing on the request to the question type. This is, for example, how the choices in a multiple choices question are shuffled.&lt;br /&gt;
&lt;br /&gt;
From the base class:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function init_first_step(question_attempt_step $step) {&lt;br /&gt;
        $this-&amp;gt;question-&amp;gt;init_first_step($step);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====process_action()====&lt;br /&gt;
&lt;br /&gt;
This is the most important method. It controls what happens when someone does something with the question (other than starting it, which is handled by &amp;lt;tt&amp;gt;init_first_step&amp;lt;/tt&amp;gt;.)&lt;br /&gt;
&lt;br /&gt;
When the method is called, a pending attempt step is passed in. This method can either return &amp;lt;tt&amp;gt;question_attempt::DISCARD&amp;lt;/tt&amp;gt;, to indicate nothing interesting happened, and the pending step should be discarded. Or, it can update that step, and return &amp;lt;tt&amp;gt;question_attempt::KEEP&amp;lt;/tt&amp;gt;, to have the new step retained.&lt;br /&gt;
&lt;br /&gt;
Typically, the method is implemented by examining the current state of the attempt, and the pending step, to decide what sort of action this is, and then dispatching to a more specific method.&lt;br /&gt;
&lt;br /&gt;
For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback&amp;lt;/tt&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function process_action(question_attempt_step $pendingstep) {&lt;br /&gt;
        if ($pendingstep-&amp;gt;has_behaviour_var(&#039;comment&#039;)) {&lt;br /&gt;
            return $this-&amp;gt;process_comment($pendingstep);&lt;br /&gt;
        } else if ($pendingstep-&amp;gt;has_behaviour_var(&#039;finish&#039;)) {&lt;br /&gt;
            return $this-&amp;gt;process_finish($pendingstep);&lt;br /&gt;
        } else {&lt;br /&gt;
            return $this-&amp;gt;process_save($pendingstep);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that there are some special actions that every behaviour must be able to handle:&lt;br /&gt;
&lt;br /&gt;
; behaviour_var comment =&amp;gt; &#039;... &#039;&#039;the comment&#039;&#039; ...&#039; : If this question has marks, then there will also be a behaviour_var mark and a behaviour_var maxmark. This is the action that is fired when a user manually grades a question.&lt;br /&gt;
; behaviour_var finish =&amp;gt; 1 : This is the action that is generated when the user submits and finishes a whole usage. For example when they click &#039;&#039;&#039;&#039;Submit all and finish&#039;&#039;&#039;&#039; in a quiz.&lt;br /&gt;
&lt;br /&gt;
The base class implements the &amp;lt;tt&amp;gt;process_comment&amp;lt;/tt&amp;gt; method in a way that should be suitable for most behaviours. There is also a subclass &amp;lt;tt&amp;gt;question_behaviour_with_save&amp;lt;/tt&amp;gt; of &amp;lt;tt&amp;gt;question_behaviour&amp;lt;/tt&amp;gt; which provides a &amp;lt;tt&amp;gt;process_save&amp;lt;/tt&amp;gt; implementation that may be suitable for most behaviours. You may find it helps to extend this class instead of &amp;lt;tt&amp;gt;question_behaviour&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====process_...()====&lt;br /&gt;
&lt;br /&gt;
So, the process_action function has dispatched to a more specific process_... function. What should that function do? Normally, it has to delegate some processing to the question type, and based on the results of that, upgrade the &amp;lt;tt&amp;gt;$pendingstep&amp;lt;/tt&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback&amp;lt;/tt&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function process_finish(question_attempt_pending_step $pendingstep) {&lt;br /&gt;
        if ($this-&amp;gt;qa-&amp;gt;get_state()-&amp;gt;is_finished()) {&lt;br /&gt;
            return question_attempt::DISCARD;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $response = $this-&amp;gt;qa-&amp;gt;get_last_step()-&amp;gt;get_qt_data();&lt;br /&gt;
        if (!$this-&amp;gt;question-&amp;gt;is_gradable_response($response)) {&lt;br /&gt;
            $pendingstep-&amp;gt;set_state(question_state::$gaveup);&lt;br /&gt;
        } else {&lt;br /&gt;
            list($fraction, $state) = $this-&amp;gt;question-&amp;gt;grade_response($response);&lt;br /&gt;
            $pendingstep-&amp;gt;set_fraction($fraction);&lt;br /&gt;
            $pendingstep-&amp;gt;set_state($state);&lt;br /&gt;
        }&lt;br /&gt;
        $pendingstep-&amp;gt;set_new_response_summary($this-&amp;gt;question-&amp;gt;summarise_response($response));&lt;br /&gt;
        return question_attempt::KEEP;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First, if the question attempt has already been finished, there is nothing to do, so &amp;lt;tt&amp;gt;question_attempt::DISCARD&amp;lt;/tt&amp;gt; is returned.&lt;br /&gt;
&lt;br /&gt;
If there is something to do, the response data is extracted from the &amp;lt;tt&amp;gt;$pendingstep&amp;lt;/tt&amp;gt;. The question type is asked whether the data there is complete enough to be graded. If not, the state is set to &#039;gave up&#039;. If there is, the question type is asked to compute the fraction, and that and the state are put into the &amp;lt;tt&amp;gt;$pendingstep&amp;lt;/tt&amp;gt;. Finally, the response summary, which is displayed in places like the quiz reports is updated and &amp;lt;tt&amp;gt;question_attempt::DISCARD&amp;lt;/tt&amp;gt; is returned.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====adjust_display_options()====&lt;br /&gt;
&lt;br /&gt;
This method is called before a question is rendered, so that the behaviour can change the display options depending on the current state of the question. So, for example, after the question is finished, it should be displayed in read-only mode. Before the student had submitted an answer, no feedback should be displayed.&lt;br /&gt;
&lt;br /&gt;
For example, the base class does&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function adjust_display_options(question_display_options $options) {&lt;br /&gt;
        if ($this-&amp;gt;qa-&amp;gt;get_state()-&amp;gt;is_finished())) {&lt;br /&gt;
            $options-&amp;gt;readonly = true;&lt;br /&gt;
            $options-&amp;gt;numpartscorrect = $options-&amp;gt;numpartscorrect &amp;amp;&amp;amp;&lt;br /&gt;
                    $this-&amp;gt;qa-&amp;gt;get_state()-&amp;gt;is_partially_correct() &amp;amp;&amp;amp;&lt;br /&gt;
                    !empty($this-&amp;gt;question-&amp;gt;shownumcorrect);&lt;br /&gt;
        } else {&lt;br /&gt;
            $options-&amp;gt;hide_all_feedback();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_state_string()====&lt;br /&gt;
&lt;br /&gt;
In the info box that starts each question, the is a few words summary of the current state of the question. That summary is generated by this method. It is not often necessary to override the base class implementation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_state_string($showcorrectness) {&lt;br /&gt;
        return $this-&amp;gt;qa-&amp;gt;get_state()-&amp;gt;default_string($showcorrectness);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example where it is overridden is &amp;lt;tt&amp;gt;qbehaviour_interactive&amp;lt;/tt&amp;gt;, if you want to look.&lt;br /&gt;
&lt;br /&gt;
====get_our_resume_data()====&lt;br /&gt;
&lt;br /&gt;
This method is required to make the quiz feature &#039;&#039;&#039;Each attempt builds on last&#039;&#039;&#039; work. It needs to return any behaviour variable from the current attempt that would be needed to start a new attempt in the same state.&lt;br /&gt;
&lt;br /&gt;
For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredcbm&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    protected function get_our_resume_data() {&lt;br /&gt;
        $lastcertainty = $this-&amp;gt;qa-&amp;gt;get_last_behaviour_var(&#039;certainty&#039;);&lt;br /&gt;
        if ($lastcertainty) {&lt;br /&gt;
            return array(&#039;-certainty&#039; =&amp;gt; $lastcertainty);&lt;br /&gt;
        } else {&lt;br /&gt;
            return array();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    protected function get_our_resume_data() {&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Methods used for reporting===&lt;br /&gt;
&lt;br /&gt;
As well as just processing a single attempt as it happens, we need to be able to run reports of what happened across lots of attempts, for example to determine whether questions are performing well or badly. &lt;br /&gt;
&lt;br /&gt;
Some of these methods in this section are called during the attempt, and the result stored in the database at that time, rather than when the report is run. That is just a performance consideration. Logically, these methods are all for reporting, so I list them here.&lt;br /&gt;
&lt;br /&gt;
====get_correct_response()====&lt;br /&gt;
&lt;br /&gt;
In fact, this first method is not even used for the reports. It is used in the question preview pop-up window to make the &#039;Fill in correct response&#039; button work. It returns the behaviour data that needs to be submitted as part of a correct response. Obviously, this is then combined with data from the question type.&lt;br /&gt;
&lt;br /&gt;
For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredcbm&amp;lt;/tt&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_correct_response() {&lt;br /&gt;
        if ($this-&amp;gt;qa-&amp;gt;get_state()-&amp;gt;is_active()) {&lt;br /&gt;
            return array(&#039;certainty&#039; =&amp;gt; question_cbm::HIGH);&lt;br /&gt;
        }&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_question_summary()====&lt;br /&gt;
&lt;br /&gt;
This method returns a plain-text summary of the question that was asked. This is used, for example, by the quiz Responses report. Normally the base-class implementation that just delegates to the question type is all you need.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_question_summary() {&lt;br /&gt;
        return $this-&amp;gt;question-&amp;gt;get_question_summary();&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_right_answer_summary()====&lt;br /&gt;
&lt;br /&gt;
This should return a plain-text summary of what the right answer to the question is. For example, in the base class:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_right_answer_summary() {&lt;br /&gt;
        return $this-&amp;gt;question-&amp;gt;get_right_answer_summary();&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
and in &amp;lt;tt&amp;gt;qbehaviour_deferredcbm&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function get_right_answer_summary() {&lt;br /&gt;
        $summary = parent::get_right_answer_summary();&lt;br /&gt;
        return $summary . &#039; [&#039; . question_cbm::get_string(question_cbm::HIGH) . &#039;]&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
(Hmm. I probably should not be concatenating language strings like that.)&lt;br /&gt;
&lt;br /&gt;
====adjust_random_guess_score()====&lt;br /&gt;
&lt;br /&gt;
Questions are able to report an estimate of the mark a student would get by guessing. Naturally, question types that modify the marks need to be able to manipulate that. For example in &amp;lt;tt&amp;gt;qbehaviour_deferredcbm&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public static function adjust_random_guess_score($fraction) {&lt;br /&gt;
        return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty());&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====classify_response()====&lt;br /&gt;
&lt;br /&gt;
This is used by the response analysis in the quiz statistics report: At the moment, no behaviours do more than delegate to the question type:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function classify_response() {&lt;br /&gt;
        return $this-&amp;gt;question-&amp;gt;classify_response($this-&amp;gt;qa-&amp;gt;get_last_qt_data());&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====summarise_action()====&lt;br /&gt;
&lt;br /&gt;
This is used by the response history table that is shown underneath questions on the quiz review page, in the Action column. It should return a plain text representation of the action the student took in this step. For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function summarise_action(question_attempt_step $step) {&lt;br /&gt;
        if ($step-&amp;gt;has_behaviour_var(&#039;comment&#039;)) {&lt;br /&gt;
            return $this-&amp;gt;summarise_manual_comment($step);&lt;br /&gt;
        } else if ($step-&amp;gt;has_behaviour_var(&#039;finish&#039;)) {&lt;br /&gt;
            return $this-&amp;gt;summarise_finish($step);&lt;br /&gt;
        } else {&lt;br /&gt;
            return $this-&amp;gt;summarise_save($step);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
from &amp;lt;tt&amp;gt;question_behaviour_with_save&amp;lt;/tt&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function summarise_save(question_attempt_step $step) {&lt;br /&gt;
        $data = $step-&amp;gt;get_submitted_data();&lt;br /&gt;
        if (empty($data)) {&lt;br /&gt;
            return $this-&amp;gt;summarise_start($step);&lt;br /&gt;
        }&lt;br /&gt;
        return get_string(&#039;saved&#039;, &#039;question&#039;,&lt;br /&gt;
                $this-&amp;gt;question-&amp;gt;summarise_response($step-&amp;gt;get_qt_data()));&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
and from the base class:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function summarise_start($step) {&lt;br /&gt;
        return get_string(&#039;started&#039;, &#039;question&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function summarise_finish($step) {&lt;br /&gt;
        return get_string(&#039;attemptfinished&#039;, &#039;question&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Things related to the setting UI===&lt;br /&gt;
&lt;br /&gt;
An activity using questions needs to provide various options to the user that relate to behaviours. For example, on the quiz settings form, we need to display a menu &#039;&#039;&#039;How questions behave&#039;&#039;&#039; where the teacher can choose which behaviour they want. The quiz does this by calling &amp;lt;tt&amp;gt;question_engine::get_behaviour_options()&amp;lt;/tt&amp;gt;. Similarly, when selecting the display options &#039;&#039;&#039;Users may review&#039;&#039;&#039;, &#039;&#039;&#039;During the attempt&#039;&#039;&#039;, not all options are relevant to all behaviours, The quiz can get the appropriate dependencies by calling &amp;lt;tt&amp;gt;question_engine::get_behaviour_unused_display_options()&amp;lt;/tt&amp;gt;. Naturally, the question engine needs to consult the behaviours to implement these methods This is done using the following two things.&lt;br /&gt;
&lt;br /&gt;
====IS_ARCHETYPAL====&lt;br /&gt;
&lt;br /&gt;
Some behaviours should be offered as options in the user interface. For example CBM, interactive, and so on. Others are for internal use. For example &amp;lt;tt&amp;gt;qbehaviour_informationitem&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;qbehaviour_interactiveadaptedformyqtype&amp;lt;/tt&amp;gt;. This is indicated by defining a constant &amp;lt;tt&amp;gt;IS_ARCHETYPAL&amp;lt;/tt&amp;gt; in the class. True means that this behaviour will be offered as an option in the UI.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    const IS_ARCHETYPAL = true; // or false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_unused_display_options()====&lt;br /&gt;
&lt;br /&gt;
This method should return a list of &amp;lt;tt&amp;gt;question_display_options&amp;lt;/tt&amp;gt; field that are irrelevant before &amp;lt;tt&amp;gt;question_usage_by_activity::finish_all_questions()&amp;lt;/tt&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
For example, from &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public static function get_unused_display_options() {&lt;br /&gt;
        return array(&#039;correctness&#039;, &#039;marks&#039;, &#039;specificfeedback&#039;,&lt;br /&gt;
                &#039;generalfeedback&#039;, &#039;rightanswer&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Renderer class==&lt;br /&gt;
&lt;br /&gt;
Renderers are all about generating HTML output. The overall output of questions is controlled by the &amp;lt;tt&amp;gt;core_question_renderer::question(...)&amp;lt;/tt&amp;gt; method, which in turn calls other methods of the core question renderer, the behaviour renderer and the question type renderer, to generate the various bits of output. See [[Question_Engine_2:Overview#What_are_the_parts_of_a_question.3F|this overview of the parts of a question]].&lt;br /&gt;
&lt;br /&gt;
Note that most of the methods take a &amp;lt;tt&amp;gt;question_display_options&amp;lt;/tt&amp;gt; object that specifies what should and should not be visible. For example, should feedback, or marks information, be displayed. It is important that your renderer respects these options.&lt;br /&gt;
&lt;br /&gt;
Once again, in what follows, I only list the methods your are most likely to want to override.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===controls===&lt;br /&gt;
&lt;br /&gt;
This is a block of output in the question formulation area. It goes after all the question type output in this area.&lt;br /&gt;
&lt;br /&gt;
Two examples. This method is used by the interactive behaviour to show the &#039;&#039;&#039;Submit&#039;&#039;&#039; button (if the question is in an appropriate state). It is used by the CBM model to show the certainty choices. Here is the interactive behaviour code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function controls(question_attempt $qa, question_display_options $options) {&lt;br /&gt;
        return $this-&amp;gt;submit_button($qa, $options);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;tt&amp;gt;submit_button&amp;lt;/tt&amp;gt; is a helper method provided by the base class.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===feedback===&lt;br /&gt;
&lt;br /&gt;
This is a block of output in the feedback area of the question. It is used, for example, in interactive behaviour, to show the &#039;&#039;&#039;Try again&#039;&#039;&#039; button, and in the CBM model to output some text that explains how the score was adjusted. Here is the interactive behaviour code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function feedback(question_attempt $qa, question_display_options $options) {&lt;br /&gt;
        if (!$qa-&amp;gt;get_state()-&amp;gt;is_active() || !$options-&amp;gt;readonly) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set up $attributes array ...&lt;br /&gt;
&lt;br /&gt;
        $output = html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
        if (empty($attributes[&#039;disabled&#039;])) {&lt;br /&gt;
            $output .= print_js_call(&#039;question_init_submit_button&#039;,&lt;br /&gt;
                    array($attributes[&#039;id&#039;], $qa-&amp;gt;get_slot()), true);&lt;br /&gt;
        }&lt;br /&gt;
        return $output;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that we set up a call to the JavaScript function &amp;lt;tt&amp;gt;question_init_submit_button&amp;lt;/tt&amp;gt;, passing the button id, and the question slot number. You should do that for any button you output that will cause the question to be submitted.&lt;br /&gt;
&lt;br /&gt;
==Unit tests==&lt;br /&gt;
&lt;br /&gt;
For a general introduction, see the [[Unit_tests|Moodle unit testing documentation]].&lt;br /&gt;
&lt;br /&gt;
Most of the behaviours are currently tested by working one or more test questions through a sequence of example inputs and testing the results. To do this they sub-class &amp;lt;tt&amp;gt;qbehaviour_walkthrough_test_base&amp;lt;/tt&amp;gt; which provides a lot of helper methods to facilitate writing tests like that.&lt;br /&gt;
&lt;br /&gt;
The best way to start is probably to look at the tests for some of the standard behaviours. For example &amp;lt;tt&amp;gt;qbehaviour_deferredfeedback_walkthrough_test::test_deferredfeedback_feedback_multichoice_single&amp;lt;/tt&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function test_deferredfeedback_feedback_multichoice_single() {&lt;br /&gt;
&lt;br /&gt;
        // Create a true-false question with correct answer true.&lt;br /&gt;
        $mc = test_question_maker::make_a_multichoice_single_question();&lt;br /&gt;
        $rightindex = $this-&amp;gt;get_mc_right_answer_index($mc);&lt;br /&gt;
&lt;br /&gt;
        // Start a deferred feedback attempt and add the question to it.&lt;br /&gt;
        $this-&amp;gt;start_attempt_at_question($mc, &#039;deferredfeedback&#039;, 3);&lt;br /&gt;
&lt;br /&gt;
        // Verify.&lt;br /&gt;
        $this-&amp;gt;check_current_state(question_state::$todo);&lt;br /&gt;
        $this-&amp;gt;check_current_mark(null);&lt;br /&gt;
        $this-&amp;gt;check_current_output(&lt;br /&gt;
                $this-&amp;gt;get_contains_question_text_expectation($mc),&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation(0, true, false),&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation(1, true, false),&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation(2, true, false),&lt;br /&gt;
                $this-&amp;gt;get_does_not_contain_feedback_expectation());&lt;br /&gt;
&lt;br /&gt;
        // Process the data extracted for this question.&lt;br /&gt;
        $this-&amp;gt;process_submission(array(&#039;answer&#039; =&amp;gt; $rightindex));&lt;br /&gt;
&lt;br /&gt;
        // Verify.&lt;br /&gt;
        $this-&amp;gt;check_current_state(question_state::$complete);&lt;br /&gt;
        $this-&amp;gt;check_current_mark(null);&lt;br /&gt;
        $this-&amp;gt;check_current_output(&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation($rightindex, true, true),&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),&lt;br /&gt;
                $this-&amp;gt;get_does_not_contain_correctness_expectation(),&lt;br /&gt;
                $this-&amp;gt;get_does_not_contain_feedback_expectation());&lt;br /&gt;
&lt;br /&gt;
        // Finish the attempt.&lt;br /&gt;
        $this-&amp;gt;quba-&amp;gt;finish_all_questions();&lt;br /&gt;
&lt;br /&gt;
        // Verify.&lt;br /&gt;
        $this-&amp;gt;check_current_state(question_state::$gradedright);&lt;br /&gt;
        $this-&amp;gt;check_current_mark(3);&lt;br /&gt;
        $this-&amp;gt;check_current_output(&lt;br /&gt;
                $this-&amp;gt;get_contains_mc_radio_expectation($rightindex, false, true),&lt;br /&gt;
                $this-&amp;gt;get_contains_correct_expectation());&lt;br /&gt;
&lt;br /&gt;
        // Now change the correct answer to the question, and regrade.&lt;br /&gt;
        $mc-&amp;gt;answers[13]-&amp;gt;fraction = -0.33333333;&lt;br /&gt;
        $mc-&amp;gt;answers[14]-&amp;gt;fraction = 1;&lt;br /&gt;
        $this-&amp;gt;quba-&amp;gt;regrade_all_questions();&lt;br /&gt;
&lt;br /&gt;
        // Verify.&lt;br /&gt;
        $this-&amp;gt;check_current_state(question_state::$gradedwrong);&lt;br /&gt;
        $this-&amp;gt;check_current_mark(-1);&lt;br /&gt;
        $this-&amp;gt;check_current_output(&lt;br /&gt;
                $this-&amp;gt;get_contains_incorrect_expectation());&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the helper methods hide a lot of the details, like starting an attempt, but if you browse the code, you will be able to see exactly what those (quite simple) helper functions do. I was going to summarise here what the test does, but I think the comments in the code already do that well enough.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
In the next section, [[Developing a Question Type|Developing a Question Type]] I describe what a developer will need to do to create a Question Type plugin for the new system.&lt;br /&gt;
&lt;br /&gt;
* The PHP documenter comments that explain the purposes of every method in the question engine code.&lt;br /&gt;
* Back to [[Question_Engine_2|Question Engine 2]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Emerrill</name></author>
	</entry>
</feed>