<?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=Howardsmiller</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=Howardsmiller"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Howardsmiller"/>
	<updated>2026-06-17T21:30:08Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Grunt&amp;diff=58148</id>
		<title>Grunt</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Grunt&amp;diff=58148"/>
		<updated>2020-12-07T13:11:00Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Running grunt */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
Grunt is a command line tool used to prepare our javascript and less-generated css for production usage. After making any change to javascript or less files in Moodle, you must run grunt to lint, minify and package the javascript/css properly so that it can be served by Moodle.&lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
javascript and CSS in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment. You will need to install NodeJS and the Grunt tools. For documentation on this process see [[Javascript_Modules#Install_grunt]].&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
Typical commands:&lt;br /&gt;
&lt;br /&gt;
 grunt amd               # Short-cut for grunt jshint uglify, rebuild all AMD modules.&lt;br /&gt;
 grunt shifter           # Run shifter&lt;br /&gt;
 grunt js                # Short-cut for grunt amd shifter&lt;br /&gt;
 &lt;br /&gt;
 grunt css               # Run less&lt;br /&gt;
 grunt                   # Try to do the right thing:&lt;br /&gt;
                         # * If you are in a folder called amd, do grunt amd&lt;br /&gt;
                         # * If you are in a folder called yui/src/something, do grunt shifter&lt;br /&gt;
                         # * Otherwise build everything (grunt css js).&lt;br /&gt;
 &lt;br /&gt;
 grunt watch             # Watch for changes and re-run grunt tasks depending on what file changes&lt;br /&gt;
 grunt eslint --show-lint-warnings # Show pedantic lint warnings for JS&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On Linux/Mac it will build everything in the current folder and below. You need to cd into the amd folder of your module root, i.e. &amp;lt;tt&amp;gt;dirroot/blocks/foo/amd&amp;lt;/tt&amp;gt; before running &amp;lt;tt&amp;gt;grunt amd&amp;lt;/tt&amp;gt; - this will compile only your plugins AMD source files. You can make output more verbose by adding &amp;lt;tt&amp;gt;-v&amp;lt;/tt&amp;gt; parameter, if used with &amp;lt;tt&amp;gt;grunt shifter&amp;lt;/tt&amp;gt; you will have to cd into the &amp;lt;tt&amp;gt;module/yui/src&amp;lt;/tt&amp;gt; folder, and to show what your lint errors are you can also use the &amp;lt;tt&amp;gt;-v&amp;lt;/tt&amp;gt; parameter. On Windows, you need to specify the path on the command line like &amp;lt;tt&amp;gt;--root=admin/tool/templatelibrary&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note for grunt watch&#039;&#039;&#039;. If you get an error complaining about &#039;watchman&#039;, you need to install it. Ubuntu/Debian is &#039;sudo apt install watchman&#039; (for example).&lt;br /&gt;
&lt;br /&gt;
== Using Grunt in additional plugins ==&lt;br /&gt;
&lt;br /&gt;
You may want to use Grunt for performing tasks in your custom Moodle plugins. For building AMD and YUI modules in your plugin, the standard configuration Gruntfile.js located in the Moodle root should work well. For building CSS files from LESS templates, you will have to set up a separate Grunt installation in the root of your plugin.&lt;br /&gt;
&lt;br /&gt;
If you do not have it yet, create the package.json file in the root of your plugin:&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;name&amp;quot;: &amp;quot;moodle-plugintype_pluginname&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
Install grunt, grunt less and grunt watch modules. Note that you should put the folder node_modules into your plugin&#039;s .gitignore file, too.&lt;br /&gt;
&lt;br /&gt;
    $ cd /path/to/your/plugin/root&lt;br /&gt;
    $ npm install --save-dev grunt grunt-contrib-less grunt-contrib-watch grunt-load-gruntfile&lt;br /&gt;
&lt;br /&gt;
Create a Gruntfile.js in the root of your plugin and configure the task for building CSS files from LESS:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&amp;quot;use strict&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
module.exports = function (grunt) {&lt;br /&gt;
&lt;br /&gt;
    // We need to include the core Moodle grunt file too, otherwise we can&#039;t run tasks like &amp;quot;amd&amp;quot;.&lt;br /&gt;
    require(&amp;quot;grunt-load-gruntfile&amp;quot;)(grunt);&lt;br /&gt;
    grunt.loadGruntfile(&amp;quot;../../Gruntfile.js&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // Load all grunt tasks.&lt;br /&gt;
    grunt.loadNpmTasks(&amp;quot;grunt-contrib-less&amp;quot;);&lt;br /&gt;
    grunt.loadNpmTasks(&amp;quot;grunt-contrib-watch&amp;quot;);&lt;br /&gt;
    grunt.loadNpmTasks(&amp;quot;grunt-contrib-clean&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    grunt.initConfig({&lt;br /&gt;
        watch: {&lt;br /&gt;
            // If any .less file changes in directory &amp;quot;less&amp;quot; then run the &amp;quot;less&amp;quot; task.&lt;br /&gt;
            files: &amp;quot;less/*.less&amp;quot;,&lt;br /&gt;
            tasks: [&amp;quot;less&amp;quot;]&lt;br /&gt;
        },&lt;br /&gt;
        less: {&lt;br /&gt;
            // Production config is also available.&lt;br /&gt;
            development: {&lt;br /&gt;
                options: {&lt;br /&gt;
                    // Specifies directories to scan for @import directives when parsing.&lt;br /&gt;
                    // Default value is the directory of the source, which is probably what you want.&lt;br /&gt;
                    paths: [&amp;quot;less/&amp;quot;],&lt;br /&gt;
                    compress: true&lt;br /&gt;
                },&lt;br /&gt;
                files: {&lt;br /&gt;
                    &amp;quot;styles.css&amp;quot;: &amp;quot;less/styles.less&amp;quot;&lt;br /&gt;
                }&lt;br /&gt;
            },&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
    // The default task (running &amp;quot;grunt&amp;quot; in console).&lt;br /&gt;
    grunt.registerTask(&amp;quot;default&amp;quot;, [&amp;quot;less&amp;quot;]);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now running &amp;quot;grunt&amp;quot; or &amp;quot;grunt less&amp;quot; in your plugin root folder will compile the file less/styles.less and saves it as styles.css. Running &amp;quot;grunt watch&amp;quot; will watch the less/*.less files and if some is changed, it will immediately rebuild the CSS file.&lt;br /&gt;
&lt;br /&gt;
If you are working on a custom theme, you may have multiple less/*.less files that you want to compile to their style/*.css counterparts. You can either define an explicit list all such file pairs, or let that list be created for you by making use of [http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically expand:true feature] of gruntfile.js:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// This dynamically creates the list of files to be processed.&lt;br /&gt;
files: [&lt;br /&gt;
    {   &lt;br /&gt;
        expand: true,&lt;br /&gt;
        cwd: &amp;quot;less/&amp;quot;,&lt;br /&gt;
        src: &amp;quot;*.less&amp;quot;,&lt;br /&gt;
        dest: &amp;quot;style/&amp;quot;,&lt;br /&gt;
        ext: &amp;quot;.css&amp;quot;&lt;br /&gt;
    }   &lt;br /&gt;
]   &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[YUI/Shifter]]&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[LESS]]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=400229#p1614743 Bitbucket pipeline to build YUI JS]&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=lib/formslib.php_Form_Definition&amp;diff=58109</id>
		<title>lib/formslib.php Form Definition</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=lib/formslib.php_Form_Definition&amp;diff=58109"/>
		<updated>2020-11-27T14:44:39Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* date_time_selector */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Formslib}}&lt;br /&gt;
== &#039;&#039;definition()&#039;&#039; ==&lt;br /&gt;
&lt;br /&gt;
The definition of the elements to be included in the form, their &#039;types&#039; (PARAM_*), helpbuttons included, etc. is all included in a function you must define in your class.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;definition()&#039;&#039; is used to define the elements in the form and &#039;&#039;&#039;this definition will be used for validating data submitted as well as for printing the form.&#039;&#039;&#039; For select and checkbox type elements only data that could have been selected will be allowed. And only data that corresponds to a form element in the definition will be accepted as submitted data.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;definition()&#039;&#039; should include all elements that are going to be used on form, some elements may be removed or tweaked later in &#039;&#039;definition_after_data()&#039;&#039;. Please do not create conditional elements in &#039;&#039;definition()&#039;&#039;, the definition() should not directly depend on the submitted data.&lt;br /&gt;
&lt;br /&gt;
Note that the definition function is called when the form class is instantiated. There is no option to (say) manipulate data in the class (that may affect the rendering of the form) between instantiating the form and calling any other methods. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
require_once(&amp;quot;$CFG-&amp;gt;libdir/formslib.php&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
class simplehtml_form extends moodleform {&lt;br /&gt;
&lt;br /&gt;
    function definition() {&lt;br /&gt;
        global $CFG;&lt;br /&gt;
       &lt;br /&gt;
        $mform = $this-&amp;gt;_form; // Don&#039;t forget the underscore! &lt;br /&gt;
&lt;br /&gt;
        $mform-&amp;gt;addElement()... // Add elements to your form&lt;br /&gt;
            ...&lt;br /&gt;
    }                           // Close the function&lt;br /&gt;
}                               // Close the class&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
===Passing parameters to the Form===&lt;br /&gt;
&lt;br /&gt;
The constructor for &#039;&#039;moodleform&#039;&#039; allows a number of parameters including one (&#039;&#039;$customdata&#039;&#039;) to permit an array of arbitrary data to be passed to your form. &lt;br /&gt;
&lt;br /&gt;
For example, you can pass the data &amp;quot;$email&amp;quot; and &amp;quot;$username&amp;quot; to the Form&#039;s class for use inside (say) the definition.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
 $mform_simple = new simplehtml_form( null, array(&#039;email&#039;=&amp;gt;$email, &#039;username&#039;=&amp;gt;$username ) );&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
(the first parameter is $action, &#039;&#039;null&#039;&#039; will cause the form action to be determined automatically)&lt;br /&gt;
&lt;br /&gt;
Secondly, inside the form definition you can use those parameters to set the default values to some of the form&#039;s fields&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
 $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email&#039;, get_string(&#039;email&#039;), &#039;maxlength=&amp;quot;100&amp;quot; size=&amp;quot;25&amp;quot; &#039;);&lt;br /&gt;
 $mform-&amp;gt;setType(&#039;email&#039;, PARAM_NOTAGS);&lt;br /&gt;
 $mform-&amp;gt;addRule(&#039;email&#039;, get_string(&#039;missingemail&#039;), &#039;required&#039;, null, &#039;server&#039;);&lt;br /&gt;
 // Set default value by using a passed parameter&lt;br /&gt;
 $mform-&amp;gt;setDefault(&#039;email&#039;,$this-&amp;gt;_customdata[&#039;email&#039;]);&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Use Fieldsets to group Form Elements==&lt;br /&gt;
&lt;br /&gt;
You use code like this to open a fieldset with a &#039;&#039;legend&#039;&#039;. &amp;lt;br /&amp;gt;&lt;br /&gt;
(&#039;&#039;&#039;Note&#039;&#039;&#039;: Some themes turn off legends on admin setting pages by using CSS: &amp;lt;nowiki&amp;gt;#adminsettings legend {display:none;}&amp;lt;/nowiki&amp;gt;.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;header&#039;, &#039;nameforyourheaderelement&#039;, get_string(&#039;titleforlegened&#039;, &#039;modulename&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can&#039;t yet nest these visible fieldsets unfortunately. But in fact groups of elements are wrapped in invisible fieldsets.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Since Moodle 2.5 fieldsets without any required fields are collapsed by default. To display these fieldsets on page load, use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $mform-&amp;gt;setExpanded(&#039;foo&#039;)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
To close a fieldset on page load, use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $mform-&amp;gt;setExpanded(&#039;foo&#039;, false)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You close a fieldset with moodle_form&#039;s closeHeaderBefore method. You tell closeHeaderBefore the element before you wish to end the fieldset. A fieldset is automatically closed if you open a new one. You need to use this code only if you want to close a fieldset and the subsequent form elements are not to be enclosed by a visible fieldset (they are still enclosed with an invisibe one with no legend) :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;closeHeaderBefore(&#039;buttonar&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==addElement==&lt;br /&gt;
&lt;br /&gt;
Use the addElement method to add an element to a form. The first few arguments are always the same. The first param is the type of the element to add. The second is the elementname to use which is normally the html name of the element in the form. The third is often the text for the label for the element.&lt;br /&gt;
&lt;br /&gt;
Some examples are below :&lt;br /&gt;
=== button ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;button&#039;, &#039;intro&#039;, get_string(&amp;quot;buttonlabel&amp;quot;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A button element. If you want a submit or cancel button see &#039;submit&#039; element.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== autocomplete ===&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
Available since Moodle 3.1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$searchareas = \core_search\manager::get_search_areas_list(true);                                                           &lt;br /&gt;
$areanames = array();                                                                                                       &lt;br /&gt;
foreach ($searchareas as $areaid =&amp;gt; $searcharea) {                                                                          &lt;br /&gt;
    $areanames[$areaid] = $searcharea-&amp;gt;get_visible_name();                                                                  &lt;br /&gt;
}                                                                                                                           &lt;br /&gt;
$options = array(                                                                                                           &lt;br /&gt;
    &#039;multiple&#039; =&amp;gt; true,                                                  &lt;br /&gt;
&lt;br /&gt;
                                                   &lt;br /&gt;
    &#039;noselectionstring&#039; =&amp;gt; get_string(&#039;allareas&#039;, &#039;search&#039;),                                                                &lt;br /&gt;
);         &lt;br /&gt;
$mform-&amp;gt;addElement(&#039;autocomplete&#039;, &#039;areaids&#039;, get_string(&#039;searcharea&#039;, &#039;search&#039;), $areanames, $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The autocomplete element is an advanced form element that supports server-side searching - or simple filtering of a predefined list of options. Some benefits of using this form element are that it handles very large datasets extremely well - it has great accessibility built in - and it gives a good user experience. If you have so much data you need to build pagination into a page - you could probably come up with a better design using this. The simplest way to use it is compatible with the standard &#039;select&#039; form element. You give it a list of options and some parameters to configure how it behaves. The valid parameters for this simple mode of operation are:&lt;br /&gt;
* multiple (boolean - default false) - Allow more than one selected item. The data coming from the form will be an array in this case.&lt;br /&gt;
&lt;br /&gt;
[[image:autocomplete_multiple2.png|center|thumb|alt=autocomplete with multiple option|autocomplete with multiple option.]]&lt;br /&gt;
&lt;br /&gt;
* noselectionstring (string - default &amp;lt;nowiki&amp;gt;&#039;&#039;&amp;lt;/nowiki&amp;gt;) - The text to display when nothing is selected.&lt;br /&gt;
* showsuggestions (boolean - default true) - Do not show the list of suggestions when the user starts typing.&lt;br /&gt;
* placeholder (string - default &amp;lt;nowiki&amp;gt;&#039;&#039;&amp;lt;/nowiki&amp;gt;) - The text to show in the search box when it is empty.&lt;br /&gt;
* casesensitive (boolean - default false) - Is the search case sensitive ?&lt;br /&gt;
* tags (boolean - default false) - This changes the behaviour so that the user can create new valid entries in the list by typing them and pressing enter.&lt;br /&gt;
* ajax (string - default &amp;lt;nowiki&amp;gt;&#039;&#039;&amp;lt;/nowiki&amp;gt;) - This string is the name of an AMD module that can fetch and format results.&lt;br /&gt;
* valuehtmlcallback - For use with the AJAX option, so that it can format the initial value of the form field (available since Moodle 3.5)&lt;br /&gt;
&lt;br /&gt;
More explanation on the &#039;ajax&#039; option. This should be the name of an AMD module that implements 2 functions:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
/**                                                                                                                         &lt;br /&gt;
 * Source of data for Ajax element.                                                                                         &lt;br /&gt;
 *                                                                                                                          &lt;br /&gt;
 * @param {String} selector The selector of the auto complete element.                                                      &lt;br /&gt;
 * @param {String} query The query string.                                                                                  &lt;br /&gt;
 * @param {Function} callback A callback function receiving an array of results.                                            &lt;br /&gt;
 * @return {Void}                                                                                                           &lt;br /&gt;
*/                                                                                                                         &lt;br /&gt;
transport: function(selector, query, callback) ...&lt;br /&gt;
&lt;br /&gt;
/**                                                                                                                         &lt;br /&gt;
 * Process the results for auto complete elements.                                                                          &lt;br /&gt;
 *                                                                                                                          &lt;br /&gt;
 * @param {String} selector The selector of the auto complete element.                                                      &lt;br /&gt;
 * @param {Array} results An array or results.                                                                              &lt;br /&gt;
 * @return {Array} New array of results.                                                                                    &lt;br /&gt;
 */                                                                                                                         &lt;br /&gt;
processResults: function(selector, results)...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
A good example is here: [https://github.com/moodle/moodle/blob/master/admin/tool/lp/amd/src/frameworks_datasource.js admin/tool/lp/amd/src/frameworks_datasource.js]&lt;br /&gt;
&lt;br /&gt;
The &#039;valuehtmlcallback&#039; function is needed when an AJAX-supporting form field has an initial value that needs special rendering, similar to how the AJAX code would render it when the user changes it dynamically. For example, if the field contains user ids and its initial value is &#039;1,2&#039; then you want it to use the rendered HTML display for each value (probably a user&#039;s name and picture), not just display those numbers. Here is an example, from /search/classes/output/form/search.php:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&#039;valuehtmlcallback&#039; =&amp;gt; function($value) {&lt;br /&gt;
    global $DB, $OUTPUT;&lt;br /&gt;
    $user = $DB-&amp;gt;get_record(&#039;user&#039;, [&#039;id&#039; =&amp;gt; (int)$value], &#039;*&#039;, IGNORE_MISSING);&lt;br /&gt;
    if (!$user || !user_can_view_profile($user)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
    $details = user_get_user_details($user);&lt;br /&gt;
    return $OUTPUT-&amp;gt;render_from_template(&lt;br /&gt;
        &#039;core_search/form-user-selector-suggestion&#039;, $details);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Mustache template used here is the same as the one used by the AJAX code when the field is changed dynamically.&lt;br /&gt;
&lt;br /&gt;
When using the ajax option in an mform with validation etc - it is recommended to sub-class the php class &amp;quot;MoodleQuickForm_autocomplete&amp;quot; so that you can provide a list of name and&lt;br /&gt;
values to populate the form element if the form is re-displayed due to a validation error. An example is [https://github.com/moodle/moodle/blob/MOODLE_31_STABLE/admin/tool/lp/classes/form/framework_autocomplete.php admin/tool/lp/classes/form/framework_autocomplete.php].&lt;br /&gt;
&lt;br /&gt;
We have provided several useful subclasses of this form element already that are simple to use (course and tags).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt; &lt;br /&gt;
    // Course example.&lt;br /&gt;
    //  Valid options are:                                                                                     &lt;br /&gt;
    //                       &#039;multiple&#039; - boolean multi select                                                                      &lt;br /&gt;
    //                       &#039;exclude&#039; - array or int, list of course ids to never show                                             &lt;br /&gt;
    //                       &#039;requiredcapabilities&#039; - array of capabilities. Uses ANY to combine them.                              &lt;br /&gt;
    //                       &#039;limittoenrolled&#039; - boolean Limits to enrolled courses.                                                &lt;br /&gt;
    //                       &#039;includefrontpage&#039; - boolean Enables the frontpage to be selected.  &lt;br /&gt;
    $options = array(&#039;multiple&#039; =&amp;gt; true, &#039;includefrontpage&#039; =&amp;gt; true);                                                           &lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;course&#039;, &#039;mappedcourses&#039;, get_string(&#039;courses&#039;), $options); &lt;br /&gt;
&lt;br /&gt;
    // Tags&lt;br /&gt;
    //  Valid options are:                                                                                     &lt;br /&gt;
    //                       &#039;showstandard&#039; - boolean One of the core_tag_tag constants to say which tags to display&lt;br /&gt;
    //                       &#039;component&#039; - string The component name and itemtype define the tag area&lt;br /&gt;
    //                       &#039;itemtype&#039; - string The component name and itemtype define the tag area&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;tags&#039;, &#039;interests&#039;, get_string(&#039;interestslist&#039;), array(&#039;itemtype&#039; =&amp;gt; &#039;user&#039;, &#039;component&#039; =&amp;gt; &#039;core&#039;));     &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;checkbox&#039;, &#039;ratingtime&#039;, get_string(&#039;ratingtime&#039;, &#039;forum&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a simple checkbox. The third parameter for this element is the label to display on the left side of the form. You can also supply a string as a fourth parameter to specify a label that will appear on the right of the element. Checkboxes and radio buttons can be grouped and have individual labels on their right.&lt;br /&gt;
&lt;br /&gt;
You can have a 5th parameter $attributes, as on other elements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BEWARE:&#039;&#039;&#039; Unchecked checkboxes return nothing at all (as if they didn&#039;t exist). This can surprise the unwary. You may wish to use advcheckbox instead, which does return a value when not checked. &#039;Advcheckbox&#039; eliminates this problem. &lt;br /&gt;
&lt;br /&gt;
==== advcheckbox ====&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;advcheckbox&#039;, &#039;ratingtime&#039;, get_string(&#039;ratingtime&#039;, &#039;forum&#039;), &#039;Label displayed after checkbox&#039;, array(&#039;group&#039; =&amp;gt; 1), array(0, 1));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Similar to the checkbox above, but with some important improvements:&lt;br /&gt;
&lt;br /&gt;
# The (optional) 5th parameter is a normal $attributes array, normally used to set HTML attributes for the &amp;lt;input&amp;gt; element. However, a special value of &#039;group&#039; can be given, which will add a class name to the element, and enable its grouping for a [[lib/formslib.php_add_checkbox_controller|checkbox controller]]&lt;br /&gt;
#The (optional) 6th parameter is an array of values that will be associated with the checked/unchecked state of the checkbox. With a normal checkbox you cannot choose that value, and in fact an unchecked checkbox will not even be sent with the form data.&lt;br /&gt;
#It returns a 0 value when unchecked. Compare with the ordinary checkbox which does not return anything at all.&lt;br /&gt;
&lt;br /&gt;
=== choosecoursefile ===&lt;br /&gt;
{{Moodle 1.9}}&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;choosecoursefile&#039;, &#039;mediafile&#039;, get_string(&#039;mediafile&#039;, &#039;lesson&#039;), array(&#039;courseid&#039;=&amp;gt;$COURSE-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Choose a file from the course files area. The fourth option is a list of options for the element. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note: This has been superceded by [[#filepicker|filepicker]] in Moodle 2.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
array(&#039;courseid&#039; =&amp;gt;null,  //if it is null (default then use global $COURSE&lt;br /&gt;
      &#039;height&#039;   =&amp;gt;500,   // height of the popup window&lt;br /&gt;
      &#039;width&#039;    =&amp;gt;750,   // width of the popup window&lt;br /&gt;
      &#039;options&#039;  =&amp;gt;&#039;none&#039;); //options string for the pop up window &lt;br /&gt;
                          //eg. &#039;menubar=0,location=0,scrollbars,resizable&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also pass an optional 5th parameter of attributes, as for other elements. The most useful way of using that is something like &lt;br /&gt;
&amp;lt;code php&amp;gt;array(&#039;maxlength&#039; =&amp;gt; 255, &#039;size&#039; =&amp;gt; 48)&amp;lt;/code&amp;gt;&lt;br /&gt;
to control the maxlength / size of the text box (note size will default to 48 if not specified)&lt;br /&gt;
&lt;br /&gt;
Finally, as this element is a group containing two elements (button + value), you can add validation rules by using the &#039;&#039;&#039;addGroupRule()&#039;&#039;&#039; method in this (complex) way:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;$mform-&amp;gt;addGroupRule(&#039;elementname&#039;, array(&#039;value&#039; =&amp;gt; array(array(list, of, rule, params, but, fieldname))));&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where: &#039;&#039;&#039;&amp;quot;elementname&amp;quot;&#039;&#039;&#039; is the name of the choosecoursefile group element, &#039;&#039;&#039;&amp;quot;value&amp;quot;&#039;&#039;&#039; is the name of the text field within the group and the &#039;&#039;&#039;&amp;quot;list, of, addrule, params, but, fieldname&amp;quot;&#039;&#039;&#039; is exactly that, the list of fields in the normal addRule() function but ommiting the first one, the fieldname.&lt;br /&gt;
&lt;br /&gt;
For example, the [http://cvs.moodle.org/moodle/mod/resource/type/file/resource.class.php?view=markup file/url resource type], uses one &amp;quot;choosecoursefile&amp;quot; element, and it controls the maximum length of the field (255) with this use of addGroupRule():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;$mform-&amp;gt;addGroupRule(&#039;reference&#039;, array(&#039;value&#039; =&amp;gt; array(array(get_string(&#039;maximumchars&#039;, &#039;&#039;, 255), &#039;maxlength&#039;, 255, &#039;client&#039;))));&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== date_selector ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;date_selector&#039;, &#039;assesstimefinish&#039;, get_string(&#039;to&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a date selector. You can select a Day, Month and Year using a group of select boxes. The fourth param here is an array of options. The defaults for the options are :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
array(&lt;br /&gt;
    &#039;startyear&#039; =&amp;gt; 1970, &lt;br /&gt;
    &#039;stopyear&#039;  =&amp;gt; 2020,&lt;br /&gt;
    &#039;timezone&#039;  =&amp;gt; 99,&lt;br /&gt;
    &#039;optional&#039;  =&amp;gt; false&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can override these defaults by supplying an array as fourth param with one or more keys with a value to override the default. You can supply a fifth param of attributes here as well.&lt;br /&gt;
&lt;br /&gt;
=== date_time_selector ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;date_time_selector&#039;, &#039;assesstimestart&#039;, get_string(&#039;from&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a group of select boxes to select a date (Day Month and Year) and time (Hour and Minute). When submitted, submitted data is processed and a timestamp is passed to $form-&amp;gt;get_data(); the fourth param here is an array of options. The defaults for the options are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
array(&lt;br /&gt;
    &#039;startyear&#039; =&amp;gt; 1970, &lt;br /&gt;
    &#039;stopyear&#039;  =&amp;gt; 2020,&lt;br /&gt;
    &#039;timezone&#039;  =&amp;gt; 99,&lt;br /&gt;
    &#039;step&#039;      =&amp;gt; 5,&lt;br /&gt;
    &#039;optional&#039; =&amp;gt; false,&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can override these defaults by supplying an array as fourth param with one or more keys with a value to override the default. Setting &#039;optional&#039; to true adds an &#039;enable&#039; checkbox to the selector. You can supply a fifth param of attributes here as well.&lt;br /&gt;
&lt;br /&gt;
===duration===&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;duration&#039;, &#039;timelimit&#039;, get_string(&#039;timelimit&#039;, &#039;quiz&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This field type lets the user input an interval of time. It comprises a text field, where you can type a number, and a dropdown for selecting a unit (days, hours, minutes or seconds). When submitted the value is converted to a number of seconds.&lt;br /&gt;
&lt;br /&gt;
You can add a fourth parameter to give options. At the moment the only option supported is here is an array of options. The defaults for the options is:&lt;br /&gt;
&amp;lt;code php&amp;gt;array(&#039;optional&#039; =&amp;gt; true)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also pass an optional 5th parameter of attributes, as for other elements. The most useful way of using that is something like &lt;br /&gt;
&amp;lt;code php&amp;gt;array(&#039;size&#039; =&amp;gt; 5)&amp;lt;/code&amp;gt;&lt;br /&gt;
to control the size of the text box.&lt;br /&gt;
&lt;br /&gt;
=== editor ===&lt;br /&gt;
&lt;br /&gt;
This replaces the old htmleditor field type. It allows the user to enter rich text content in a variety of formats.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;fieldname&#039;, get_string(&#039;labeltext&#039;, &#039;langfile&#039;));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;fieldname&#039;, PARAM_RAW);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: It won&#039;t work properly without the setType() as shown.&lt;br /&gt;
&lt;br /&gt;
If you would like to let the user use the filepicker to upload images etc. that are used in the content, then see [[Using_the_File_API_in_Moodle_forms]].&lt;br /&gt;
&lt;br /&gt;
You can supply a fourth param to htmleditor of an array of options that are mostly related to file handling:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
array(&lt;br /&gt;
    &#039;subdirs&#039;=&amp;gt;0,&lt;br /&gt;
    &#039;maxbytes&#039;=&amp;gt;0,&lt;br /&gt;
    &#039;maxfiles&#039;=&amp;gt;0,&lt;br /&gt;
    &#039;changeformat&#039;=&amp;gt;0,&lt;br /&gt;
    &#039;context&#039;=&amp;gt;null,&lt;br /&gt;
    &#039;noclean&#039;=&amp;gt;0,&lt;br /&gt;
    &#039;trusttext&#039;=&amp;gt;0,&lt;br /&gt;
    &#039;enable_filemanagement&#039; =&amp;gt; true);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The option &#039;enable_filemanagement&#039; will display the file management button on true and remove it on false.&lt;br /&gt;
&lt;br /&gt;
To save the data if you don&#039;t care about files:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$formdata = $mform-&amp;gt;get_data();&lt;br /&gt;
$text     = $formdata-&amp;gt;fieldname[&#039;text&#039;];&lt;br /&gt;
$format   = $formdata-&amp;gt;fieldname[&#039;format&#039;];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: Because the text editor might be &amp;quot;Atto&amp;quot; (depending on user preferences) and Atto has an &amp;quot;autosave&amp;quot; feature - it requires that the combination of $PAGE-&amp;gt;url and this elementid are unique. If not, the autosaved text for a different form may be restored into this form.&lt;br /&gt;
&lt;br /&gt;
=== file ===&lt;br /&gt;
&lt;br /&gt;
File upload input box with browse button. In the form definition type&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
after form submission and validation use&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
      ...&lt;br /&gt;
    $mform-&amp;gt;save_files($destination_directory);&lt;br /&gt;
      ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there is no requirement to save the file, you can read the file contents directly into a string as follows...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $mform-&amp;gt;get_file_content(&#039;attachment&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you need advanced settings such as required file, different max upload size or name of uploaded file&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0, true, true, false));&lt;br /&gt;
            $mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
            $mform-&amp;gt;addRule(&#039;attachment&#039;, null, &#039;required&#039;);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
      ...&lt;br /&gt;
    $mform-&amp;gt;save_files($destination_directory);&lt;br /&gt;
    $newfilename = $mform-&amp;gt;get_new_filename();&lt;br /&gt;
      ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When porting old code it is also possible to use the upload manager directly for processing of uploaded files.&lt;br /&gt;
&lt;br /&gt;
Please note that if using set_upload_manager() it must be before addElement(&#039;file&#039;,..).&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
File uploading was rewritten in 2.0. Please see inline docs for now. This page will be updated when the new API stabilises.&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
General replacement of &#039;&#039;file&#039;&#039; element.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null, array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
See also [[Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
=== hidden ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;hidden&#039;, &#039;reply&#039;, &#039;yes&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A hidden element. Set the element name (in this case &#039;&#039;&#039;reply&#039;&#039;&#039;) to the stated value (in this case &#039;&#039;&#039;yes&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
=== html ===&lt;br /&gt;
You can add arbitrary HTML to your Moodle form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;html&#039;, &#039;&amp;lt;div class=&amp;quot;qheader&amp;quot;&amp;gt;&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See [http://moodle.org/mod/forum/discuss.php?d=126935 &amp;quot;Question: Can I put a moodleform inside a table td?&amp;quot;] for a concrete example.&lt;br /&gt;
&lt;br /&gt;
=== htmleditor &amp;amp; format ===&lt;br /&gt;
&lt;br /&gt;
These elements are now deprecated. Please use the [[#editor|editor]] field type instead.&lt;br /&gt;
&lt;br /&gt;
===modgrade===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;modgrade&#039;, &#039;scale&#039;, get_string(&#039;grade&#039;), false);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This is a custom element for selecting a grade for any activity module. The fourth argument is whether to include an option for no grade which has a value 0. This select box does include scales. The default is true, include no grade option.&lt;br /&gt;
&lt;br /&gt;
A helpbutton is automatically added.&lt;br /&gt;
&lt;br /&gt;
===modvisible===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;modvisible&#039;, &#039;visible&#039;, get_string(&#039;visible&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This is a custom element for selecting a grade visibility in an activity mod update form.&lt;br /&gt;
&lt;br /&gt;
===password===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;password&#039;, &#039;password&#039;, get_string(&#039;label&#039;), $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A password element. Fourth param is an array or string of attributes.&lt;br /&gt;
&lt;br /&gt;
===passwordunmask===&lt;br /&gt;
{{Moodle 1.9}}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;passwordunmask&#039;, &#039;password&#039;, get_string(&#039;label&#039;), $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A password element with option to show the password in plaintext. Fourth param is an array or string of attributes.&lt;br /&gt;
&lt;br /&gt;
=== radio ===&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$radioarray=array();&lt;br /&gt;
$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, &#039;yesno&#039;, &#039;&#039;, get_string(&#039;yes&#039;), 1, $attributes);&lt;br /&gt;
$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, &#039;yesno&#039;, &#039;&#039;, get_string(&#039;no&#039;), 0, $attributes);&lt;br /&gt;
$mform-&amp;gt;addGroup($radioarray, &#039;radioar&#039;, &#039;&#039;, array(&#039; &#039;), false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Second param names the radio button and should be the same for each button in the group in order to toggle correctly. Third param would be the label for the form element but is generally ignored as this element will always be in a group which has it&#039;s own label. Fourth param is a string, a label to be displayed on the right of the element. The fifth is the value for this radio button. $attributes can be a string or an array of attributes.&lt;br /&gt;
&lt;br /&gt;
It is possible to add help to individual radio buttons but this requires a custom template to be defined for the group elements. See MDL-15571.&lt;br /&gt;
&lt;br /&gt;
Since 2.3 it cannot be statically called anymore, so we need to call createElement from $mform reference.&lt;br /&gt;
&lt;br /&gt;
==== setDefault ====&lt;br /&gt;
&lt;br /&gt;
To set the default for a radio button group as above use the following :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;setDefault(&#039;yesno&#039;, 0);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This would make the default &#039;no&#039;.&lt;br /&gt;
&lt;br /&gt;
===select===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;select&#039;, &#039;type&#039;, get_string(&#039;forumtype&#039;, &#039;forum&#039;), $FORUM_TYPES, $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fourth param for this element is an array of options for the select box. The keys are the values for the option and the value of the array is the text for the option. The fifth param $attributes is optional, see text element for description of attributes param.&lt;br /&gt;
&lt;br /&gt;
It is also possible to create a select with certain options disabled, using [http://stackoverflow.com/questions/2138089/how-can-i-use-quickform-to-add-disabled-select-options/2150275#2150275 this technique].&lt;br /&gt;
&lt;br /&gt;
You can set an &#039;onchange&#039; attribute when adding or creating the select element: &lt;br /&gt;
&lt;br /&gt;
$form-&amp;gt;addElement(&#039;select&#039;, &#039;iselTest&#039;, &#039;Test Select:&#039;, $arrayOfOptions, array(&#039;onchange&#039; =&amp;gt; &#039;javascript:myFunctionToDoSomething();&#039;));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====multi-select====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$select = $mform-&amp;gt;addElement(&#039;select&#039;, &#039;colors&#039;, get_string(&#039;colors&#039;), array(&#039;red&#039;, &#039;blue&#039;, &#039;green&#039;), $attributes);&lt;br /&gt;
$select-&amp;gt;setMultiple(true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====setSelected=====&lt;br /&gt;
&lt;br /&gt;
To set the default selected item in a select element, you can use the &#039;setSelected&#039; method. The &#039;setSelected&#039; can either get a value or an array of values.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$options = array(&lt;br /&gt;
    &#039;ff0000&#039; =&amp;gt; &#039;Red&#039;,&lt;br /&gt;
    &#039;00ff00&#039; =&amp;gt; &#039;Green&#039;,&lt;br /&gt;
    &#039;0000ff&#039; =&amp;gt; &#039;Blue&#039;&lt;br /&gt;
);&lt;br /&gt;
$select = $mform-&amp;gt;addElement(&#039;select&#039;, &#039;colors&#039;, get_string(&#039;colors&#039;), $options);&lt;br /&gt;
// This will select the colour blue.&lt;br /&gt;
$select-&amp;gt;setSelected(&#039;0000ff&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or for multiple-selection:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$skillsarray = array(&lt;br /&gt;
    &#039;val1&#039; =&amp;gt; &#039;Skill A&#039;,&lt;br /&gt;
    &#039;val2&#039; =&amp;gt; &#039;Skill B&#039;,&lt;br /&gt;
    &#039;val3&#039; =&amp;gt; &#039;Skill C&#039;&lt;br /&gt;
);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;select&#039;, &#039;md_skills&#039;, get_string(&#039;skills&#039;, &#039;metadata&#039;), $skillsarray);&lt;br /&gt;
$mform-&amp;gt;getElement(&#039;md_skills&#039;)-&amp;gt;setMultiple(true);&lt;br /&gt;
// This will select the skills A and B.&lt;br /&gt;
$mform-&amp;gt;getElement(&#039;md_skills&#039;)-&amp;gt;setSelected(array(&#039;val1&#039;, &#039;val2&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However you probably don&#039;t want to do this. Instead you probably want to use setDefault, or set it using the form&#039;s setData method.&lt;br /&gt;
&lt;br /&gt;
===selectyesno===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;selectyesno&#039;, &#039;maxbytes&#039;, get_string(&#039;maxattachmentsize&#039;, &#039;forum&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want a yes / no select box this one automatically translates itself and has value 1 for yes and 0 for no.&lt;br /&gt;
&lt;br /&gt;
===selectwithlink===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$options = array();&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;selectwithlink&#039;, &#039;scaleid&#039;, get_string(&#039;scale&#039;), $options, null, &lt;br /&gt;
    array(&#039;link&#039; =&amp;gt; $CFG-&amp;gt;wwwroot.&#039;/grade/edit/scale/edit.php?courseid=&#039;.$COURSE-&amp;gt;id, &#039;label&#039; =&amp;gt; get_string(&#039;scalescustomcreate&#039;)));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
select type element with options containing link&lt;br /&gt;
===static===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;static&#039;, &#039;description&#039;, get_string(&#039;description&#039;, &#039;exercise&#039;),&lt;br /&gt;
    get_string(&#039;descriptionofexercise&#039;, &#039;exercise&#039;, $COURSE-&amp;gt;students));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a static element. It should be used with care if it is used to display a static piece of text with a label. The third param is the label and the fourth is the static text itself.&lt;br /&gt;
&lt;br /&gt;
===submit, reset and cancel===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
//normally you use add_action_buttons instead of this code&lt;br /&gt;
$buttonarray=array();&lt;br /&gt;
$buttonarray[] = $mform-&amp;gt;createElement(&#039;submit&#039;, &#039;submitbutton&#039;, get_string(&#039;savechanges&#039;));&lt;br /&gt;
$buttonarray[] = $mform-&amp;gt;createElement(&#039;reset&#039;, &#039;resetbutton&#039;, get_string(&#039;revert&#039;));&lt;br /&gt;
$buttonarray[] = $mform-&amp;gt;createElement(&#039;cancel&#039;);&lt;br /&gt;
$mform-&amp;gt;addGroup($buttonarray, &#039;buttonar&#039;, &#039;&#039;, &#039; &#039;, false);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A &#039;Submit&#039; type element is a submit type form element which will submit the form. A &#039;Reset&#039; will not submit the form but will reset any changes the user has made to form contents. A &#039;Cancel&#039; element cancels form submission. You need to have a branch in your code before you check for get_data() to check if submission has been cancelled with is_cancelled(); See the example on the usage page.&lt;br /&gt;
&lt;br /&gt;
You should name your submit and reset buttons &#039;submitbutton&#039; and &#039;resetbutton&#039; or something similar (not &#039;submit&#039; and &#039;reset&#039;). This avoids problems in JavaScript of collisions between form element names and names of JavaScript methods of the form object.&lt;br /&gt;
&lt;br /&gt;
====add_action_buttons($cancel = true, $submitlabel=null);====&lt;br /&gt;
&lt;br /&gt;
You will normally use this helper function which is a method of moodleform to add all the &#039;action&#039; buttons to the end of your form. A boolean parameter allow you to specify whether to include a cancel button and specify the label for your submit button (pass the result of get_string). Default for the submit button label is get_string(&#039;savechanges&#039;). Note the &#039;&#039;&#039;$this&#039;&#039;&#039; not &#039;&#039;&#039;$mform&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$this-&amp;gt;add_action_buttons();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===text===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;name&#039;, get_string(&#039;forumname&#039;, &#039;forum&#039;), $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
For a simple text input element. (For text labels, use the &#039;static&#039; element.)  Your fourth parameter here can be a string or array of attributes for the text element. The following are equivalent :&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$attributes=&#039;size=&amp;quot;20&amp;quot;&#039;;&lt;br /&gt;
$attributes=array(&#039;size&#039;=&amp;gt;&#039;20&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Generally you are encouraged to use CSS instead of using attributes for styling.&lt;br /&gt;
&lt;br /&gt;
A format element can be used as a format select box. It will be non-selectable if you&#039;re using an html editor.&lt;br /&gt;
&lt;br /&gt;
The third param for this element is $useHtmlEditor and it defaults to null in which case an html editor is used if the browser and user profile support it.&lt;br /&gt;
&lt;br /&gt;
====RTL support====&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
As of Moodle 3.2, some form elements have been refined to better support right-to-left languages. In RTL, most fields should not have their direction flipped, a URL, a path to a file, a number, ... are always displayed LTR. Input fields and text areas now will best guess whether they should be forced to be displayed in LTR based on the PARAM type associated with it. You can call:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;setForceLtr(&#039;name&#039;, true/false);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
on some form fields (like &#039;text&#039;) to manually set the value, and for directionality.&lt;br /&gt;
&lt;br /&gt;
====float====&lt;br /&gt;
{{Moodle 3.7}}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;float&#039;, &#039;defaultmark&#039;, get_string(&#039;defaultmark&#039;, &#039;question&#039;), $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Use the float element if you want a text box to get a floating point number. This element automatically supports localised decimal separators. You don&#039;t need to use setType with the float element.&lt;br /&gt;
&lt;br /&gt;
===textarea===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;textarea&#039;, &#039;introduction&#039;, get_string(&amp;quot;introtext&amp;quot;, &amp;quot;survey&amp;quot;), &#039;wrap=&amp;quot;virtual&amp;quot; rows=&amp;quot;20&amp;quot; cols=&amp;quot;50&amp;quot;&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A textarea element. If you want an htmleditor use htmleditor element. Fourth element here is a string or array of attributes.&lt;br /&gt;
&lt;br /&gt;
===recaptcha===&lt;br /&gt;
{{Moodle 1.9}}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;recaptcha&#039;, &#039;recaptcha_field_name&#039;, $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use this recaptcha element to reduce the spam risk in your forms. Third element here is a string or array of attributes. Take care to get an API key from http://recaptcha.net/api/getkey before using this element.&lt;br /&gt;
&lt;br /&gt;
To check whether recaptcha is enabled at site level use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if (!empty($CFG-&amp;gt;recaptchapublickey) &amp;amp;&amp;amp; !empty($CFG-&amp;gt;recaptchaprivatekey)) {&lt;br /&gt;
    //recaptcha is enabled&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===tags===&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;tags&#039;, &#039;field_name&#039;, $lable, $options, $attributes);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Used for editing a list of tags, for example on a blog post.&lt;br /&gt;
&lt;br /&gt;
There is only one option available, &#039;display&#039;, which should be set to one of the contstants MoodleQuickForm_tags::ONLYOFFICIAL, NOOFFICIAL or DEFAULTUI. This controls whether the official tags are listed for easy selection, or a text area where arbitrary tags may be typed, or both. The default is both.&lt;br /&gt;
&lt;br /&gt;
The value should be set/returned as an array of tags.&lt;br /&gt;
&lt;br /&gt;
===grading===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;grading&#039;, &#039;advancedgrading&#039;, get_string(&#039;grade&#039;).&#039;:&#039;, array(&#039;gradinginstance&#039; =&amp;gt; $gradinginstance));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom element for advanced grading plugins.&lt;br /&gt;
&lt;br /&gt;
When adding the &#039;grading&#039; element to the form, developer must pass an object of class gradingform_instance as $attributes[&#039;gradinginstance&#039;]. Otherwise an exception will be thrown.&lt;br /&gt;
&lt;br /&gt;
===questioncategory===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;questioncategory&#039;, &#039;category&#039;, get_string(&#039;category&#039;, &#039;question&#039;),&lt;br /&gt;
    array(&#039;contexts&#039;=&amp;gt;$contexts, &#039;top&#039;=&amp;gt;true, &#039;currentcat&#039;=&amp;gt;$currentcat, &#039;nochildrenof&#039;=&amp;gt;$currentcat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Creates a drop down element to select a question category.&lt;br /&gt;
&lt;br /&gt;
Options are:&lt;br /&gt;
&#039;&#039;&#039;contexts&#039;&#039;&#039; - (required) context in which question appears&lt;br /&gt;
&#039;&#039;&#039;currentcat&#039;&#039;&#039; - (optional) course category&lt;br /&gt;
&#039;&#039;&#039;top&#039;&#039;&#039; - (optional) if true will put top categories on top&lt;br /&gt;
&#039;&#039;&#039;nochildrenof&#039;&#039;&#039; - (optional) Format categories into an indented list reflecting the tree structure&lt;br /&gt;
&lt;br /&gt;
=== filetypes ===&lt;br /&gt;
{{Moodle 3.4}}&lt;br /&gt;
Available since Moodle 3.4&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filetypes&#039;, &#039;allowedfiletypes&#039;, get_string(&#039;allowedfiletypes&#039;, &#039;tool_myplugin&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creates  an input element allowing the user to specify file types for the given purpose. The typical scenario is a setting that allows the teacher define a list of allowed file types submitted by students.&lt;br /&gt;
&lt;br /&gt;
The element allows the user to either type the list of filetypes manually, or select the types from the list. Also supported is selecting the whole group of file types - such as &amp;quot;image&amp;quot;. The element integrates with the [[Core filetypes]] system so all default types and groups are presented, as well as those [[:en:Working with files#Site administration settings|defined locally by the admin]].&lt;br /&gt;
&lt;br /&gt;
As the list can be types in manually, the form processing code should always normalize it first via the provided utility methods:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$formdata = $mform-&amp;gt;get_data();&lt;br /&gt;
$filetypesutil = new \core_form\filetypes_util();&lt;br /&gt;
$allowedfiletypes = $filetypesutil-&amp;gt;normalize_file_types($formdata-&amp;gt;allowedfiletypes);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This always returns an array of recognized valid values. The original list can be separated by whitespace, end of lines, commas, colons and semicolons. During the normalization, values are converted to lowercase, empty valies and duplicates are removed. Glob evaluation is not supported.&lt;br /&gt;
&lt;br /&gt;
The normalization should also happen if the previously defined list had been saved to the database and re-read for actual usage. The normalization output value can be directly used as the accepted_types option for the filepicker.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$filetypesutil = new \core_form\filetypes_util();&lt;br /&gt;
$options[&#039;accepted_types&#039;] = $filetypesutil-&amp;gt;normalize_file_types($allowedfiletypes);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default, user input is validated against the list of known file types and groups. This validation can be disabled via options.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Supported options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
; onlytypes : Allow selection from these file types only; for example [&#039;onlytypes&#039; =&amp;gt; [&#039;web_image&#039;]].&lt;br /&gt;
; allowall : Allow to select &#039;All file types&#039;, defaults to true. Does not apply with onlytypes are set.&lt;br /&gt;
; allowunknown : Skip implicit validation against the list of known file types.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filetypes&#039;, &#039;doctypes&#039;, get_string(&#039;doctypes&#039;, &#039;tool_myplugin&#039;), [&#039;onlytypes&#039; =&amp;gt; [&#039;document&#039;], &#039;allowunknown&#039; =&amp;gt; true]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==addGroup==&lt;br /&gt;
&lt;br /&gt;
A &#039;group&#039; in formslib is just a group of elements that will have a label and will be included on one line. &lt;br /&gt;
&lt;br /&gt;
For example typical code to include a submit and cancel button on the same line : &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$buttonarray=array();&lt;br /&gt;
$buttonarray[] =&amp;amp; $mform-&amp;gt;createElement(&#039;submit&#039;, &#039;submitbutton&#039;, get_string(&#039;savechanges&#039;));&lt;br /&gt;
$buttonarray[] =&amp;amp; $mform-&amp;gt;createElement(&#039;submit&#039;, &#039;cancel&#039;, get_string(&#039;cancel&#039;));&lt;br /&gt;
$mform-&amp;gt;addGroup($buttonarray, &#039;buttonar&#039;, &#039;&#039;, array(&#039; &#039;), false);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You use the same arguments for createElement as you do for addElement. Any label for the element in the third argument is normally ignored (but not in the case of the submit buttons above where the third argument is not for a label but is the text for the button).&lt;br /&gt;
&lt;br /&gt;
Here&#039;s a bad example (don&#039;t do this for real, use the &#039;optional&#039; =&amp;gt; true option of the date element): putting a date_selector (which is itself a group of elements) and a checkbox on the same line, note that you can disable every element in the group using the group name &#039;availablefromgroup&#039; but it doesn&#039;t disable the controlling element the &#039;availablefromenabled&#039; checkbox:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$availablefromgroup=array();&lt;br /&gt;
$availablefromgroup[] =&amp;amp; $mform-&amp;gt;createElement(&#039;date_selector&#039;, &#039;availablefrom&#039;, &#039;&#039;);&lt;br /&gt;
$availablefromgroup[] =&amp;amp; $mform-&amp;gt;createElement(&#039;checkbox&#039;, &#039;availablefromenabled&#039;, &#039;&#039;, get_string(&#039;enable&#039;));&lt;br /&gt;
$mform-&amp;gt;addGroup($availablefromgroup, &#039;availablefromgroup&#039;, get_string(&#039;availablefromdate&#039;, &#039;data&#039;), &#039;&amp;amp;nbsp;&#039;, false);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;availablefromgroup&#039;, &#039;availablefromenabled&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* If you want to put a group inside another array so that you can repeat items, use createElement instead of addGroup:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$group = $mform-&amp;gt;createElement(&#039;group&#039;, &#039;groupname&#039;, get_string(&#039;label&#039;), $groupitems);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* By default, groups modify the names of elements inside them by appending a number. This is often unhelpful, for example if you want to use disabledIf on the element. To prevent this behaviour, set the last parameter to false when creating a group.:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$group = $mform-&amp;gt;createElement(&#039;group&#039;, &#039;groupname&#039;, get_string(&#039;label&#039;), $groupitems, null, false);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==addRule==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;elementname&#039;, get_string(&#039;error&#039;), &#039;rule type&#039;, &#039;extraruledata&#039;, &#039;server&#039;(default), false, false);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first param(element) is an element name and second(message) is the error message that will be displayed to the user.&lt;br /&gt;
The third parameter(type) is the type of rule. The fourth param(format) is used for extra data needed with some rules such as minlength and regex. The fifth parameter(validation) validates input data on server or client side, if validation is done on client side then it will be checked on the server side as well.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 * @param    string     $element       Form element name&lt;br /&gt;
 * @param    string     $message       Message to display for invalid data&lt;br /&gt;
 * @param    string     $type          Rule type, use getRegisteredRules() to get types&lt;br /&gt;
 * @param    string     $format        (optional)Required for extra rule data&lt;br /&gt;
 * @param    string     $validation    (optional)Where to perform validation: &amp;quot;server&amp;quot;, &amp;quot;client&amp;quot;&lt;br /&gt;
 * @param    boolean    $reset         Client-side validation: reset the form element to its original value if there is an error?&lt;br /&gt;
 * @param    boolean    $force         Force the rule to be applied, even if the target form element does not exist&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Common Rule Types&#039;&#039;&#039;&lt;br /&gt;
* required &lt;br /&gt;
* maxlength&lt;br /&gt;
* minlength&lt;br /&gt;
* rangelength&lt;br /&gt;
* email&lt;br /&gt;
* regex&lt;br /&gt;
* lettersonly&lt;br /&gt;
* alphanumeric&lt;br /&gt;
* numeric&lt;br /&gt;
* nopunctuation&lt;br /&gt;
* nonzero&lt;br /&gt;
* callback&lt;br /&gt;
* compare&lt;br /&gt;
&lt;br /&gt;
===Server side and Client side===&lt;br /&gt;
In case you use the &#039;&#039;Client side&#039;&#039; validation option, you can mainly check for an empty or not input field. unless you write some &#039;&#039;Client side&#039;&#039; code which will probably be JavaScript functions to verify the data inside the input fields before it is submitted to the server. It could save some time if those functions are short, simple and quick to compute.&lt;br /&gt;
In case you need a more complex validation checks which relay on Moodle&#039;s internal PHP libraries (or other/external PHP libraries) you better use the &#039;&#039;Server side&#039;&#039; validation checks. Where you can query the DB, write complex PHP validation functions and much much more, that are not available (easily) when using JavaScript on the client&#039;s side.&lt;br /&gt;
&lt;br /&gt;
==addHelpButton==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addHelpButton(&#039;api_key_field&#039;, &#039;api_key&#039;, &#039;block_extsearch&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following parameters are expected:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @param $elementname The name of the form element to add the help button for&lt;br /&gt;
 * @param $identifier The identifier for the help string and its title (see below)&lt;br /&gt;
 * @param $component The component name to look for the help string in&lt;br /&gt;
 */&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# get_string($identifier, $component) // The title of the help page&lt;br /&gt;
# get_string(&amp;quot;{$identifier}_help&amp;quot;, $component) // The content of the help page&lt;br /&gt;
&lt;br /&gt;
So you will need to have &#039;&#039;&#039;$identifier&#039;&#039;&#039; and &#039;&#039;&#039;{$identifier}_help&#039;&#039;&#039; defined in order for the help button to be created properly. For example the multiple choice question editing form has a button for shuffling the answers. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addHelpButton(&#039;shuffleanswers&#039;, &#039;shuffleanswers&#039;, &#039;qtype_multichoice&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
and so the language file includes the strings&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;shuffleanswers&#039;] = &#039;Shuffle the choices?&#039;; &lt;br /&gt;
$string[&#039;shuffleanswers_help&#039;] = &#039;If enabled,.....&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You can also add the language string like&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;shuffleanswers_link&#039;] = &#039;question/shuffleanswers&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to add a link to more help on Moodle docs. See [[String_API]] for more information about help icons.&lt;br /&gt;
&lt;br /&gt;
==setDefault==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;select&#039;, &#039;grade&#039;, get_string(&#039;gradeforsubmission&#039;, &#039;exercise&#039;), $grades);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;grade&#039;, array(&#039;grade&#039;, get_string(&#039;gradeforsubmission&#039;, &#039;exercise&#039;), &#039;exercise&#039;));&lt;br /&gt;
$mform-&amp;gt;setDefault(&#039;grade&#039;, 100);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the default of the form value with setDefault($elementname, $value); where elementname is the elementname whose default you want to set and $value is the default to set. We set the defaults for the form in definition(). This default is what is used if no data is loaded into the form with set_data(); eg. on display of the form for an &#039;add&#039; rather than &#039;update&#039; function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;desc&#039;, get_string(&#039;description&#039;));     &lt;br /&gt;
$mform-&amp;gt;setDefault(&#039;desc&#039;, array(&#039;text&#039;=&amp;gt;$defaulttext));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that when setting the default for an editor element you must use an array to define the default &amp;quot;text&amp;quot; value as shown above.&lt;br /&gt;
&lt;br /&gt;
==disabledIf==&lt;br /&gt;
&lt;br /&gt;
For any element or groups of element in a form you can conditionally disable the group or individual element depending on conditions.&lt;br /&gt;
&lt;br /&gt;
You can use $mform-&amp;gt;disabledIf($elementName, $dependentOn, $condition = &#039;notchecked&#039;, $value=&#039;1&#039;)&lt;br /&gt;
&lt;br /&gt;
* elementname can be a group. If you specify a group all elements in the group will be disabled (if dependentOn is in elementname group that is ignored and not disabled). These are the element names you&#039;ve used as the second argument in addElement or addGroup.&lt;br /&gt;
* dependentOn is the actual name of the element as it will appear in html. This can be different to the name used in addGroup particularly but also addElement where you&#039;re adding a complex element like a date_selector. Check the html of your page. You typically make the depedentOn a checkbox or select box.&lt;br /&gt;
* $condition will be &#039;notchecked&#039;, &#039;checked&#039;, &#039;noitemselected&#039;, &#039;eq&#039;, &#039;in&#039; or, if it is anything else, we test for &#039;neq&#039;.&lt;br /&gt;
** If $condition is &#039;eq&#039; or &#039;neq&#039; then we check the value of the dependentOn field and check for equality (==) or nonequality (!=) in js&lt;br /&gt;
** If $condition is &#039;checked&#039; or &#039;notchecked&#039; then we check to see if a checkbox is checked or not.&lt;br /&gt;
** If $condition is &#039;in&#039; then we check to see if a selected item is in the given list or not. (This was introduced in Moodle 2.7+)&lt;br /&gt;
** If $condition is &#039;noitemselected&#039; then we check to see whether nothing is selected in a dropdown list.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
 // Disable my control unless a checkbox is checked.&lt;br /&gt;
 $mform-&amp;gt;disabledIf(&#039;mycontrol&#039;, &#039;somecheckbox&#039;);&lt;br /&gt;
 &lt;br /&gt;
 // Disable my control if a checkbox &#039;&#039;&#039;is&#039;&#039;&#039; checked.&lt;br /&gt;
 $mform-&amp;gt;disabledIf(&#039;mycontrol&#039;, &#039;somecheckbox&#039;, &#039;checked&#039;);&lt;br /&gt;
 &lt;br /&gt;
 // Disable my control when a dropdown has value 42.&lt;br /&gt;
 $mform-&amp;gt;disabledIf(&#039;mycontrol&#039;, &#039;someselect&#039;, &#039;eq&#039;, 42);&lt;br /&gt;
&lt;br /&gt;
 // Disable my control unless a dropdown has value 42.&lt;br /&gt;
 $mform-&amp;gt;disabledIf(&#039;mycontrol&#039;, &#039;someselect&#039;, &#039;neq&#039;, 42);&lt;br /&gt;
&lt;br /&gt;
The possible choices here are in the dependency manager in lib/form/form.js.&lt;br /&gt;
===A tricky case===&lt;br /&gt;
&lt;br /&gt;
You need to take care with disabledIf if you plan to use it with groups of checkboxes.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say you have a group of 5 checkboxes and you want to enable a depending item such as a drop down menu only when the first and the last checkboxes are selected.&lt;br /&gt;
&lt;br /&gt;
To fix ideas:&lt;br /&gt;
&lt;br /&gt;
If the selection in the checkboxes group is:&lt;br /&gt;
&lt;br /&gt;
 mycheck_01 == 1&lt;br /&gt;
 mycheck_02 == 0&lt;br /&gt;
 mycheck_03 == 0&lt;br /&gt;
 mycheck_04 == 0&lt;br /&gt;
 mycheck_05 == 1&lt;br /&gt;
&lt;br /&gt;
the depending item must be enabled while ANY OTHER COMBINATION must disable the drop down menu.&lt;br /&gt;
&lt;br /&gt;
The following code will, apparently, fail:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_01&#039;, &#039;neq&#039;, &#039;1&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_02&#039;, &#039;neq&#039;, &#039;0&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_03&#039;, &#039;neq&#039;, &#039;0&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_04&#039;, &#039;neq&#039;, &#039;0&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_05&#039;, &#039;neq&#039;, &#039;1&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In fact, once you get the drop down menu enabled, you are free to unselect mycheck_01 whilst still having the depending item enabled.&lt;br /&gt;
This apparent bug occurs because a non-checked checkbox behaves like a non existing mform element. So the js code will not find the element &amp;quot;mycheck_01&amp;quot; and will not apply the corresponding rule.&lt;br /&gt;
&lt;br /&gt;
A working solution for this kind of issue seems to be:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_01&#039;, &#039;notchecked&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_02&#039;, &#039;checked&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_03&#039;, &#039;checked&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_04&#039;, &#039;checked&#039;);&lt;br /&gt;
$mform-&amp;gt;disabledIf(&#039;dropdownmenu&#039;, &#039;mycheck_05&#039;, &#039;notchecked&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
To see a failing example as the one described, try the attachments provided in MDL-38975. See also in MDL-38975 for the working solution in action with modifications suggested by Eloy.&lt;br /&gt;
&lt;br /&gt;
==hideIf==&lt;br /&gt;
{{Moodle 3.4}}&lt;br /&gt;
For any element or groups of element in a form you can conditionally hide the group or individual element depending on conditions.&lt;br /&gt;
This uses the same syntax as disabledIf just with hideIf instead.&lt;br /&gt;
&lt;br /&gt;
==setType==&lt;br /&gt;
&lt;br /&gt;
PARAM_* types are used to specify how a submitted variable should be cleaned. These should be used for get parameters such as id, course etc. which are used to load a page and also with setType(); method. Every form element should have a type specified except select, radio box and checkbox elements, these elements do a good job of cleaning themselves (only specified options are allowed as user input).&lt;br /&gt;
&lt;br /&gt;
===Most Commonly Used PARAM_* Types===&lt;br /&gt;
&lt;br /&gt;
These are the most commonly used PARAM_* types and their proper uses. More types can be seen in moodlelib.php starting around line 100.&lt;br /&gt;
&lt;br /&gt;
* PARAM_CLEAN is deprecated and you should try to use a more specific type.&lt;br /&gt;
* PARAM_TEXT should be used for cleaning data that is expected to contain multi-lang content. It will strip all html tags. But will still let tags for multilang support through.&lt;br /&gt;
* PARAM_NOTAGS should be used for cleaning data that is expected to be plain text. It will strip *all* html type tags. It will *not* let tags for multilang support through. This should be used for instance for email addresses where no multilang support is appropriate.&lt;br /&gt;
* PARAM_RAW means no cleaning whatsoever, it is used mostly for data from the html editor. Data from the editor is later cleaned before display using format_text() function. PARAM_RAW can also be used for data that is validated by some other way or printed by p() or s().&lt;br /&gt;
* PARAM_INT should be used for integers. PARAM_FLOAT is also available for decimal numbers but is not recommended for user input since it does not work for languages that use , as a decimal separator.&lt;br /&gt;
* PARAM_ACTION is an alias of PARAM_ALPHA and is used for hidden fields specifying form actions.&lt;br /&gt;
&lt;br /&gt;
==disable_form_change_checker==&lt;br /&gt;
&lt;br /&gt;
By default, any Moodle form will pop-up an &amp;quot;Are you sure?&amp;quot; alert if you make some changes and then try to leave the page without saving. Occasionally, that is undesirable, in which case you can call&lt;br /&gt;
&lt;br /&gt;
 $mform-&amp;gt;disable_form_change_checker()&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&lt;br /&gt;
* [http://www.midnighthax.com/quickform.php PEAR HTML QuickForm Getting Started Guide] by Keith Edmunds of Midnighthax.com&lt;br /&gt;
* [http://pear.php.net/manual/en/package.html.html-quickform.php PEAR::HTML_QuickForm manual]&lt;br /&gt;
&lt;br /&gt;
[[Category:Formslib]]&lt;br /&gt;
[[Category:Interfaces]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Web_services&amp;diff=57120</id>
		<title>Web services</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Web_services&amp;diff=57120"/>
		<updated>2020-04-02T21:31:10Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Developer documentation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== How it works ===&lt;br /&gt;
This simple example will give you an idea of how our web services infrastructure works. &lt;br /&gt;
#The client sends a username and password to the web service login script.&lt;br /&gt;
#The script returns a token for that user account.&lt;br /&gt;
#The client calls a particular web service function on a protocol server including the token .&lt;br /&gt;
#The protocol server uses the token to check that the user can call the function.&lt;br /&gt;
#The protocol server calls the matching external function, located in a externallib.php file inside the relevant module.&lt;br /&gt;
#The external function checks that the current user has_capability to do this operation.&lt;br /&gt;
#The external function calls the matching Moodle core function (in lib.php usually).&lt;br /&gt;
#The core function can return a result to the external function. &lt;br /&gt;
#The external function will return a result to the protocol server.&lt;br /&gt;
#The protocol server returns the result to the client.&lt;br /&gt;
&lt;br /&gt;
== Developer documentation==&lt;br /&gt;
The full API can be found on any Moodle sites under &#039;&#039;&#039; Administration block &amp;gt; Plugins &amp;gt; Web services &amp;gt; API Documentation&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Additional services are available for uploading and downloading files which are not in the API Documentation - they are accessed in a different way. See [[Web services files handling]]&lt;br /&gt;
&lt;br /&gt;
* [[How to contribute a web service function to core]]&lt;br /&gt;
* [[Adding a web service to a plugin| Adding a web service to your plugin]]&lt;br /&gt;
* [[Creating a web service client | Implement a web service client]]&lt;br /&gt;
* [[Web services files handling]]&lt;br /&gt;
* [[Web_services_Roadmap | Web service Listing &amp;amp; Roadmap]]&lt;br /&gt;
&lt;br /&gt;
== Specification and brainstorming ==&lt;br /&gt;
* [[External services security | External services security]]&lt;br /&gt;
* [[External services description | External services description]]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web_service_API_functions|Web service API functions]]&lt;br /&gt;
* [[:en:Web_services_FAQ|Web services FAQ]]&lt;br /&gt;
* [[:en:How_to_create_and_enable_a_web_service|How to create and enable a web service]]&lt;br /&gt;
* [[:en:Enable mobile web services|How to enable the mobile web service]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
* [http://www.slideshare.net/juanleyva/mastering-moodle-web-services-development Mastering Moodle Web Services development] - Last session of the Hackfest in the MoodleMoot UK 2016&lt;br /&gt;
&lt;br /&gt;
[[Category:Web_Services]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Web_services&amp;diff=57119</id>
		<title>Web services</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Web_services&amp;diff=57119"/>
		<updated>2020-04-02T21:30:56Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Developer documentation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== How it works ===&lt;br /&gt;
This simple example will give you an idea of how our web services infrastructure works. &lt;br /&gt;
#The client sends a username and password to the web service login script.&lt;br /&gt;
#The script returns a token for that user account.&lt;br /&gt;
#The client calls a particular web service function on a protocol server including the token .&lt;br /&gt;
#The protocol server uses the token to check that the user can call the function.&lt;br /&gt;
#The protocol server calls the matching external function, located in a externallib.php file inside the relevant module.&lt;br /&gt;
#The external function checks that the current user has_capability to do this operation.&lt;br /&gt;
#The external function calls the matching Moodle core function (in lib.php usually).&lt;br /&gt;
#The core function can return a result to the external function. &lt;br /&gt;
#The external function will return a result to the protocol server.&lt;br /&gt;
#The protocol server returns the result to the client.&lt;br /&gt;
&lt;br /&gt;
== Developer documentation==&lt;br /&gt;
The full API can be found on any Moodle sites under &#039;&#039;&#039; Administration block &amp;gt; Plugins &amp;gt; Web services &amp;gt; API Documentation&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039; Additional services are available for uploading and downloading files which are not in the API Documentation - they are accessed in a different way. See [[Web services files handling]]&lt;br /&gt;
&lt;br /&gt;
* [[How to contribute a web service function to core]]&lt;br /&gt;
* [[Adding a web service to a plugin| Adding a web service to your plugin]]&lt;br /&gt;
* [[Creating a web service client | Implement a web service client]]&lt;br /&gt;
* [[Web services files handling]]&lt;br /&gt;
* [[Web_services_Roadmap | Web service Listing &amp;amp; Roadmap]]&lt;br /&gt;
&lt;br /&gt;
== Specification and brainstorming ==&lt;br /&gt;
* [[External services security | External services security]]&lt;br /&gt;
* [[External services description | External services description]]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web_service_API_functions|Web service API functions]]&lt;br /&gt;
* [[:en:Web_services_FAQ|Web services FAQ]]&lt;br /&gt;
* [[:en:How_to_create_and_enable_a_web_service|How to create and enable a web service]]&lt;br /&gt;
* [[:en:Enable mobile web services|How to enable the mobile web service]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
* [http://www.slideshare.net/juanleyva/mastering-moodle-web-services-development Mastering Moodle Web Services development] - Last session of the Hackfest in the MoodleMoot UK 2016&lt;br /&gt;
&lt;br /&gt;
[[Category:Web_Services]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Task_API&amp;diff=56328</id>
		<title>Task API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Task_API&amp;diff=56328"/>
		<updated>2019-08-14T10:54:32Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Adhoc task usage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Task&lt;br /&gt;
|state = Integrated&lt;br /&gt;
|tracker = MDL-25505&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=229139&lt;br /&gt;
|assignee = Damyon&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.7}}&lt;br /&gt;
== Tasks ==&lt;br /&gt;
&lt;br /&gt;
A task is a unit of work that needs to be done later. Good uses for tasks:&lt;br /&gt;
&lt;br /&gt;
* Run a slow operation in the background&lt;br /&gt;
* Run a maintenance task on a regular schedule&lt;br /&gt;
&lt;br /&gt;
In general any operation that takes more than a few seconds should be a candidate for a task.&lt;br /&gt;
&lt;br /&gt;
== Benefits ==&lt;br /&gt;
&lt;br /&gt;
* Better user experience (give them feedback immediately, that their task has been queued)&lt;br /&gt;
* Prevent browser timeouts&lt;br /&gt;
* Better performance for clusters (tasks can be run on separate, non-webserving cluster node, tasks can run in parallel)&lt;br /&gt;
* Failed tasks will be retried&lt;br /&gt;
* A better user interface will prevent users queuing multiple tasks, because they thought it had &amp;quot;got stuck&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Types of task ==&lt;br /&gt;
&lt;br /&gt;
=== Scheduled tasks ===&lt;br /&gt;
Scheduled tasks are tasks that will run on a regular schedule. A default schedule can be set, but admins have the ability to change the default schedule if required. Note: Tasks will only run as often as cron is run in Moodle. In 2.7 it is recommended to run cron once per minute to get the benefit from the new task scheduling (don&#039;t worry - it will do much less work each time it runs).&lt;br /&gt;
&lt;br /&gt;
=== Adhoc tasks ===&lt;br /&gt;
Adhoc tasks are for when you need to queue something to run in the background immediately, they would be executed as soon as possible. Adhoc tasks can contain custom data, specific to this specific instance of the task.&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
&lt;br /&gt;
=== Scheduled task usage ===&lt;br /&gt;
&lt;br /&gt;
Scheduled tasks are created by subclassing \core\task\scheduled_task. They also require an entry in &amp;quot;db/tasks.php&amp;quot; for your plugin.&lt;br /&gt;
&lt;br /&gt;
1. Create a subclass of \core\task\scheduled_task that contains your code to run in a schedule (within &amp;quot;&amp;lt;pluginroot&amp;gt;/classes/task/cut_my_toe_nails.php&amp;quot; for the autoloading mechanism to pick up).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
namespace mod_hygene\task;&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * An example of a scheduled task.&lt;br /&gt;
 */&lt;br /&gt;
class cut_my_toe_nails extends \core\task\scheduled_task {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Return the task&#039;s name as shown in admin screens.&lt;br /&gt;
     *&lt;br /&gt;
     * @return string&lt;br /&gt;
     */&lt;br /&gt;
    public function get_name() {&lt;br /&gt;
        return get_string(&#039;cutmytoenails&#039;, &#039;mod_hygene&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Execute the task.&lt;br /&gt;
     */&lt;br /&gt;
    public function execute() {&lt;br /&gt;
        // Apply fungus cream.&lt;br /&gt;
        // Apply chainsaw.&lt;br /&gt;
        // Apply olive oil.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
2. Create entry in db/tasks.php for your plugin (then a version bump to your plugin will install the task):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$tasks = [&lt;br /&gt;
    [&lt;br /&gt;
        &#039;classname&#039; =&amp;gt; &#039;mod_hygene\task\cut_my_toe_nails&#039;,&lt;br /&gt;
        &#039;blocking&#039; =&amp;gt; 0,&lt;br /&gt;
        &#039;minute&#039; =&amp;gt; &#039;30&#039;,&lt;br /&gt;
        &#039;hour&#039; =&amp;gt; &#039;17&#039;,&lt;br /&gt;
        &#039;day&#039; =&amp;gt; &#039;*&#039;,&lt;br /&gt;
        &#039;month&#039; =&amp;gt; &#039;1,7&#039;,&lt;br /&gt;
        &#039;dayofweek&#039; =&amp;gt; &#039;0&#039;,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fields required in the db/tasks.php file are:&lt;br /&gt;
* classname - the fully namespaced classname of your task. This needs to comply with the autoloading rules.&lt;br /&gt;
* blocking - if this is set to 1, no other scheduled task will run at the same time as this task. Do not set this to 1 unless you really need it as it will impact the performance of the task queue.&lt;br /&gt;
* minute, hour, day, dayofweek, month - This is the default schedule for running the task. The syntax matches the syntax of unix cron. Starting with Moodle 2.8, the minute and hour values accept a special &#039;R&#039; syntax which causes a random value to be set in the database at install time (useful to avoid overloading web services).&lt;br /&gt;
&lt;br /&gt;
3. Increase the version number in version.php for your plugin and visit the Site &amp;quot;Administration -&amp;gt; Notifications&amp;quot; page to install the new task.&lt;br /&gt;
&lt;br /&gt;
4. (Optional - since 2.8) Run this task even when the plugin is disabled. In rare cases, you may want the scheduled tasks for a plugin to run, even when the plugin is disabled. Some of the enrolment plugins do this to clean up data. If this is the case, the scheduled task must override the  &amp;quot;get_run_if_component_disabled()&amp;quot; method and return true instead of false. If they do not do this, the scheduled task will not be run while the plugin is disabled.&lt;br /&gt;
&lt;br /&gt;
When called from the command line for testing purposes errors can be hidden and a misleading error about locks can be displayed. To get at the details of an error Moodle 3.2 supports a showdebugging option that will show the actual errors. Read about it here&lt;br /&gt;
https://docs.moodle.org/32/en/Administration_via_command_line#Scheduled_tasks&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Cron syntax examples ====&lt;br /&gt;
* day - Day of month field for task schedule.&lt;br /&gt;
** &amp;quot;*&amp;quot; - Every day&lt;br /&gt;
** &amp;quot;*/2&amp;quot; - Every 2nd day&lt;br /&gt;
** &amp;quot;1&amp;quot; - The first of every month&lt;br /&gt;
** &amp;quot;1,15&amp;quot; - The first and fifteenth of every month&lt;br /&gt;
* dayofweek - Day of week field for task schedule.&lt;br /&gt;
** &amp;quot;*&amp;quot; - Every day&lt;br /&gt;
** &amp;quot;0&amp;quot; - Every Sunday&lt;br /&gt;
** &amp;quot;6&amp;quot; - Every Saturday&lt;br /&gt;
** &amp;quot;1,5&amp;quot; - Every Monday and Friday&lt;br /&gt;
* hour - Hour field for task schedule.&lt;br /&gt;
** &amp;quot;*&amp;quot; - Every hour&lt;br /&gt;
** &amp;quot;*/2&amp;quot; - Every 2 hours&lt;br /&gt;
** &amp;quot;2-10&amp;quot; - Every hour from 2am until 10am (inclusive)&lt;br /&gt;
** &amp;quot;2,6,9&amp;quot; - 2am, 6am and 9am&lt;br /&gt;
** &amp;quot;R&amp;quot; - A random value between 0 and 23 is chosen at task install time (or when reset to default) [Supported since Moodle 2.8]&lt;br /&gt;
* minute - Minute field for task schedule.&lt;br /&gt;
** &amp;quot;*&amp;quot; - Every minute&lt;br /&gt;
** &amp;quot;*/5&amp;quot; - Every 5 minutes&lt;br /&gt;
** &amp;quot;2-10&amp;quot; - Every minute between 2 and 10 minutes past the hour (inclusive)&lt;br /&gt;
** &amp;quot;2,6,9&amp;quot; - 2, 6 and 9 minutes past the hour&lt;br /&gt;
** &amp;quot;R&amp;quot; - A random value between 0 and 59 is chosen at task install time (or when reset to default) [Supported since Moodle 2.8]&lt;br /&gt;
* month - Month field for task schedule.&lt;br /&gt;
** &amp;quot;*&amp;quot; - Every month&lt;br /&gt;
** &amp;quot;*/2&amp;quot; - Every second month&lt;br /&gt;
** &amp;quot;1&amp;quot; - Every January&lt;br /&gt;
** &amp;quot;1,5&amp;quot; - Every January and May&lt;br /&gt;
&lt;br /&gt;
=== Adhoc task usage ===&lt;br /&gt;
&lt;br /&gt;
This is even easier than scheduled tasks.&lt;br /&gt;
&lt;br /&gt;
1. Create a subclass of \core\task\adhoc_task that contains your code to run in the background.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
class take_over_the_world extends \core\task\adhoc_task {&lt;br /&gt;
    public function execute() {&lt;br /&gt;
        // gain 100,000,000 friends on facebook.&lt;br /&gt;
        // crash the stock market.&lt;br /&gt;
        // run for president.&lt;br /&gt;
&lt;br /&gt;
        // Get the custom data.&lt;br /&gt;
         $data = $this-&amp;gt;get_custom_data();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Create an instance of the task and queue it (adding custom data if required).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   // create the instance&lt;br /&gt;
   $domination = new take_over_the_world();&lt;br /&gt;
   // set blocking if required (it probably isn&#039;t)&lt;br /&gt;
   // $domination-&amp;gt;set_blocking(true);&lt;br /&gt;
   // add custom data&lt;br /&gt;
   $domination-&amp;gt;set_custom_data(array(&lt;br /&gt;
       &#039;plansfortomorrownight&#039; =&amp;gt; &#039;The same thing we do every night, Pinky!&#039;&lt;br /&gt;
   ));&lt;br /&gt;
&lt;br /&gt;
   // queue it&lt;br /&gt;
   \core\task\manager::queue_adhoc_task($domination);&lt;br /&gt;
&lt;br /&gt;
   // profit&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. There is no 3.&lt;br /&gt;
&lt;br /&gt;
By default, your task will run as admin. If you would like it to run as a specific user, call set_userid().&lt;br /&gt;
&lt;br /&gt;
=== Failures ===&lt;br /&gt;
A task, either scheduled or adhoc can sometimes fail. An example would be updating an RSS field when the network is temporarily down. This is handled by the task system automatically - all the failing task needs to do is throw an exception. The task will be retried after 1 minute. If the task keeps failing, the retry algorithm will add more time between each successive attempts up to a max of 24 hours.&lt;br /&gt;
&lt;br /&gt;
=== Caches ===&lt;br /&gt;
There is one special case that needs to be considered with this new system. If a particular scheduled or adhoc task runs for a long time and updates many DB records - particularly something related to enrolment - the next task in the queue may suffer because various API&#039;s in moodle use static caching to speed up requests, but assume that the data will not change much between the start and end of the request. In this case, you can force the cron to exit after running a task that has done many DB updates. The next cron will be run in the next minute, and will have all static caches cleared because it&#039;s a new process. To do this call \core\task\manager::clear_static_caches();&lt;br /&gt;
&lt;br /&gt;
=== Security ===&lt;br /&gt;
When scheduling a task to run in the background - or creating a scheduled task, the task will run in the context of the cron user (see &amp;quot;cron_setup_user()&amp;quot;). If you need to perform access checks in your background task, you should pass the userid/context in custom_data and then pass that userid to the access check functions (&amp;quot;require_capability(&#039;moodle/course:update&#039;,  $context, $userid)&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
=== Legacy cron ===&lt;br /&gt;
The older syntax of cron.php or modname_cron() is still supported - but is not as good as this new API. This is because:&lt;br /&gt;
* the legacy cron functions run serially - a long running cron in one plugin will hold up the other plugins crons&lt;br /&gt;
* the legacy cron functions are fragile - a failure in one cron in one plugin will prevent the cron in other functions from running at all&lt;br /&gt;
* the scheduling cannot be changed by admins&lt;br /&gt;
&lt;br /&gt;
Note that Legacy cron has been deprecated for Moodle 3.5 (MDL-52846) and will be deleted for Moodle 3.9 (MDL-61165).&lt;br /&gt;
&lt;br /&gt;
=== Generating output ===&lt;br /&gt;
Since Moodle 3.5 it is safe to use the [[Output_API]] in cron tasks. Prior to this there may be cases where the Output API has not been initialised.&lt;br /&gt;
These issues were addressed in MDL-61800 and were also backported to Moodle 3.4.3 ,and Moodle 3.3.3.&lt;br /&gt;
&lt;br /&gt;
== For Admins ==&lt;br /&gt;
Admins have a new screen where they can adjust the schedules for any scheduled task. They can also reset any scheduled task to its default schedule.&lt;br /&gt;
[[File:task_admin_screenshot.png|Screenshot of admin page]]&lt;br /&gt;
&lt;br /&gt;
 NOTE: You can also run [https://docs.moodle.org/32/en/Administration_via_command_line#Scheduled_tasks Scheduled tasks] via command line, individually.&lt;br /&gt;
&lt;br /&gt;
== Specification ==&lt;br /&gt;
The specification for this feature is here: https://docs.moodle.org/dev/Scheduled_Tasks_Proposal&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;diff=55899</id>
		<title>Writing PHPUnit tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;diff=55899"/>
		<updated>2019-04-12T09:14:34Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: Fix broken link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
Moodle PHPUnit integration is designed to allow easy adding of new tests. At the start of each test the state is automatically reset to fresh new installation (unless explicitly told not to reset).&lt;br /&gt;
&lt;br /&gt;
=Testcase classes=&lt;br /&gt;
&lt;br /&gt;
There are three basic test class that are supposed to used in all Moodle unit tests - basic_testcase, advanced_testcase and provider_testcase. &#039;&#039;&#039;Please note it is strongly recommended to put only one testcase into each class file.&#039;&#039;&#039;&lt;br /&gt;
;basic_testcase : Very simple tests that do not modify database, dataroot or any PHP globals. It can be used for example when trying examples from the official PHPUnit tutorial.&lt;br /&gt;
;advanced_testcase : Enhanced testcase class enhanced for easy testing of Moodle code.&lt;br /&gt;
;provider_testcase: Enhanced testcase class, enhanced for easy testing of [[Privacy API|Privacy Providers]].&lt;br /&gt;
&lt;br /&gt;
There is a fourth testcase class that is specially designed for testing of our Moodle database layer, it should not be used for other purposes.&lt;br /&gt;
&lt;br /&gt;
== Assertions ==&lt;br /&gt;
&lt;br /&gt;
The complete list of assertions can be found in the [https://phpunit.readthedocs.io/en/7.0/assertions.html].&lt;br /&gt;
&lt;br /&gt;
==Sample plugin testcase==&lt;br /&gt;
&lt;br /&gt;
PHPUnit tests are located in &amp;lt;code&amp;gt;tests/*_test.php&amp;lt;/code&amp;gt; files in your plugin, for example mod/myplugin/tests/sample_test.php, the file should contain only one class that extends &amp;lt;code&amp;gt;advanced_testcase&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 class mod_myplugin_sample_testcase extends advanced_testcase {&lt;br /&gt;
     public function test_adding() {&lt;br /&gt;
         $this-&amp;gt;assertEquals(2, 1+2);&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See [[PHPUnit integration#Class and file naming rules]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Inclusion of Moodle library files==&lt;br /&gt;
&lt;br /&gt;
If you want to include some Moodle library files you should always declare &#039;&#039;&#039;global $CFG&#039;&#039;&#039;. The reason is that testcase files may be included from non-moodle code which does not make the global $CFG available automatically.&lt;br /&gt;
&lt;br /&gt;
==Automatic state reset==&lt;br /&gt;
By default after each test Moodle database and dataroot is automatically reset to the original state which was present right after installation. make sure to use $this-&amp;gt;resetAfterTest() to indicate that the database or changes of standard global variables are expected.&lt;br /&gt;
&lt;br /&gt;
If you received the error &amp;quot;Warning: unexpected database modification, resetting DB state&amp;quot; it is because the test is not using $this-&amp;gt;resetAfterTest().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 class mod_myplugin_testcase extends advanced_testcase {&lt;br /&gt;
     public function test_deleting() {&lt;br /&gt;
         global $DB;&lt;br /&gt;
         $this-&amp;gt;resetAfterTest(true);&lt;br /&gt;
         $DB-&amp;gt;delete_records(&#039;user&#039;);&lt;br /&gt;
         $this-&amp;gt;assertEmpty($DB-&amp;gt;get_records(&#039;user&#039;));&lt;br /&gt;
     }&lt;br /&gt;
     public function test_user_table_was_reset() {&lt;br /&gt;
         global $DB;&lt;br /&gt;
         $this-&amp;gt;assertEquals(2, $DB-&amp;gt;count_records(&#039;user&#039;, array()));&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Generators=&lt;br /&gt;
&lt;br /&gt;
Tests that need to modify default installation may use generators to create new courses, users, etc. All examples on this page should be used from test methods of a test class derived from advanced_testcase.&lt;br /&gt;
&lt;br /&gt;
Note if you are using PHPUnit [https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers @dataProvider] functions to provide parameters to unit tests, you can not use the data generator or change the user etc in the data provider function.&lt;br /&gt;
&lt;br /&gt;
==Creating users==&lt;br /&gt;
At the start of each test there are only two users present - guest and administrator. If you need to add more test accounts use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $user = $this-&amp;gt;getDataGenerator()-&amp;gt;create_user();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also specify properties of the user account, for example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $user1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_user(array(&#039;email&#039;=&amp;gt;&#039;user1@example.com&#039;, &#039;username&#039;=&amp;gt;&#039;user1&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default no user is logged-in, use setUser() method to change current $USER value:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setUser($user1);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Guest and admin accounts have a shortcut methods:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setGuestUser();&lt;br /&gt;
 $this-&amp;gt;setAdminUser();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Null can be used to set current user back to not-logged-in:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setUser(null);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating course categories==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $category1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category();&lt;br /&gt;
 $category2 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category(array(&#039;name&#039;=&amp;gt;&#039;Some subcategory&#039;, &#039;parent&#039;=&amp;gt;$category1-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating courses==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $course1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 &lt;br /&gt;
 $category = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category();&lt;br /&gt;
 $course2 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course(array(&#039;name&#039;=&amp;gt;&#039;Some course&#039;, &#039;category&#039;=&amp;gt;$category-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating activities==&lt;br /&gt;
&lt;br /&gt;
Some activity plugins include instance generators. The generator class are defined in plugindirectory/tests/generator/lib.php.&lt;br /&gt;
&lt;br /&gt;
Example of creation of new course with one page resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $course = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 $generator = $this-&amp;gt;getDataGenerator()-&amp;gt;get_plugin_generator(&#039;mod_page&#039;);&lt;br /&gt;
 $generator-&amp;gt;create_instance(array(&#039;course&#039;=&amp;gt;$course-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following is functionally the same, but a bit shorter:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $course = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 $page = $this-&amp;gt;getDataGenerator()-&amp;gt;create_module(&#039;page&#039;, array(&#039;course&#039; =&amp;gt; $course-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating cohorts==&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
Since 2.4 there the data generator supports creation of new cohorts.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $cohort = $this-&amp;gt;getDataGenerator()-&amp;gt;create_cohort();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Simplified user enrolments==&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
Instead of standard enrolment API it is possible to use simplified method in data generator. It is intended to be used with self and manual enrolment plugins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid);&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid, $teacherroleid);&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid, $teacherroleid, &#039;manual&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating scales==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_scale();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_scale(array(&#039;name&#039; =&amp;gt; $name, &#039;scale&#039; =&amp;gt; $scale, &#039;courseid&#039; =&amp;gt; $courseid, &#039;userid&#039; =&amp;gt; $userid, &#039;description&#039; =&amp;gt; description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating roles==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_role();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_role(array(&#039;shortname&#039; =&amp;gt; $shortname, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; description, &#039;archetype&#039; =&amp;gt; $archetype));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating tags==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_tag();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_tag(array(&lt;br /&gt;
    &#039;userid&#039; =&amp;gt; $userid, &lt;br /&gt;
    &#039;rawname&#039; =&amp;gt; $rawname,&lt;br /&gt;
    &#039;name&#039; =&amp;gt; $name, &lt;br /&gt;
    &#039;description&#039; =&amp;gt; $description, &lt;br /&gt;
    &#039;descriptionformat&#039; =&amp;gt; $descriptionformat,&lt;br /&gt;
    &#039;flag&#039; =&amp;gt; $flag&lt;br /&gt;
));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Groups==&lt;br /&gt;
&lt;br /&gt;
===Creating groups===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; $description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Adding users to groups===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group_member(array(&#039;userid&#039; =&amp;gt; $userid, &#039;groupid&#039; =&amp;gt; $groupid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group_member(array(&#039;userid&#039; =&amp;gt; $userid, &#039;groupid&#039; =&amp;gt; $groupid, &#039;component&#039; =&amp;gt; $component, &#039;itemid&#039; =&amp;gt; $itemid));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating groupings===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; $description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Adding groups to groupings===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping_group(array(&#039;groupingid&#039; =&amp;gt; $groupingid, &#039;groupid&#039; =&amp;gt; $groupid));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Repositories==&lt;br /&gt;
&lt;br /&gt;
===Creating repository instances===&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Some respository plugins include instance generators. The generator class are defined in plugindirectory/tests/generator/lib.php..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_repository($type, $record, $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating repository types===&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Some respository plugins include type generators. The generator class are defined in plugindirectory/tests/generator/lib.php..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_repository_type($type, $record, $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating grades==&lt;br /&gt;
&lt;br /&gt;
===Grade categories===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_category(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_category(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;fullname&#039; =&amp;gt; $fullname));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Grade items===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item(array(&#039;itemtype&#039; =&amp;gt; $itemtype, &#039;itemname&#039; =&amp;gt; $itemname, &#039;outcomeid&#039; =&amp;gt; $outcomeid, &#039;scaleid&#039; =&amp;gt; $scaleid, &#039;gradetype&#039; =&amp;gt; $gradetype));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Outcomes===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_outcome();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item(array(&#039;fullname&#039; =&amp;gt; $fullname));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Other types of plugin==&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Any other type of plugin can have a generator. The generator class should extend component_generator_base, and then you can get an instance using $mygenerator = $this-&amp;gt;getDataGenerator()-&amp;gt;get_plugin_generator($frankenstylecomponentname);&lt;br /&gt;
&lt;br /&gt;
For some types of plugin, like mod documented above, there may be a more specific class than component_generator_base to extend, like testing_module_generator. That will give a consistent set of method names to use. Otherwise, you can create whatever methods you like on your generator, to create the different things you need to work whith.&lt;br /&gt;
&lt;br /&gt;
=Long tests=&lt;br /&gt;
&lt;br /&gt;
All standard test should execute as fast as possible. Tests that take a loner time to execute (&amp;gt;10s) or are otherwise expensive (such as querying external servers that might be flooded by all dev machines) should be execute only when PHPUNIT_LONGTEST is true. This constant can be set in phpunit.xml or directly in config.php.&lt;br /&gt;
&lt;br /&gt;
=Large test data=&lt;br /&gt;
See advanced_testcase::createXMLDataSet() and advanced_testcase::createCsvDataSet() and related functions there for easier ways to manage large test data sets within files rather than arrays in code. See [[PHPUnit_integration#Extra_methods]]&lt;br /&gt;
&lt;br /&gt;
=Testing sending of messages=&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
You can temporarily redirect all messages sent via message_send() to a message sink object. This allows developers to verify that the tested code is sending expected messages.&lt;br /&gt;
&lt;br /&gt;
To test code using messaging first disable the use of transactions and then redirect the messaging into a new message sink, you can inspect the results later.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;preventResetByRollback();&lt;br /&gt;
$sink = $this-&amp;gt;redirectMessages();&lt;br /&gt;
//... code that is sending messages&lt;br /&gt;
$messages = $sink-&amp;gt;get_messages();&lt;br /&gt;
$this-&amp;gt;assertEquals(3, count($messages));&lt;br /&gt;
//.. test messages were generated in correct order with appropriate content&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Testing sending of emails=&lt;br /&gt;
{{Moodle 2.6}}&lt;br /&gt;
You can temporarily redirect emails sent via email_to_user() to a email message sink object. This allows developers to verify that the tested code is sending expected emails.&lt;br /&gt;
&lt;br /&gt;
To test code using messaging first unset &#039;noemailever&#039; setting and then redirect the emails into a new message sink where you can inspect the results later.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
unset_config(&#039;noemailever&#039;);&lt;br /&gt;
$sink = $this-&amp;gt;redirectEmails();&lt;br /&gt;
//... code that is sending email&lt;br /&gt;
$messages = $sink-&amp;gt;get_messages();&lt;br /&gt;
$this-&amp;gt;assertEquals(1, count($messages));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Logstores=&lt;br /&gt;
You can test events which were written to a logstore, but you must disable transactions, enable at least one valid logstore, and disable logstore buffering to ensure that the events are written to the database before the tests execute.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;preventResetByRollback();&lt;br /&gt;
set_config(&#039;enabled_stores&#039;, &#039;logstore_standard&#039;, &#039;tool_log&#039;);&lt;br /&gt;
set_config(&#039;buffersize&#039;, 0, &#039;logstore_standard&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Best practice=&lt;br /&gt;
&lt;br /&gt;
There are several best practices, suggestions, and things to avoid which you should consider when writing unit tests. Some of these are described below.&lt;br /&gt;
&lt;br /&gt;
==Keep use of resetAfterTest to a minimum==&lt;br /&gt;
Although many of the examples described above use the &amp;lt;code&amp;gt;resetAfterTest&amp;lt;/code&amp;gt; nomenclature to reset the database and filesystem after your test completes, you should ideally not use this unless you have to.&lt;br /&gt;
Generally speaking you should aim to write code which is mockable, and does not require real fixtures.&lt;br /&gt;
Use of resetAfterTest will also slow your tests down.&lt;br /&gt;
&lt;br /&gt;
==Be careful with shared setUp and instance variables== &lt;br /&gt;
&lt;br /&gt;
You should be careful of how you create and use instance variables in PHPUnit tests for two main reasons:&lt;br /&gt;
&lt;br /&gt;
Firstly, if you create any fixtures in the setUp, or call the resetAfterTest function, these fixtures and conditions will apply for _all_ tests in the testsuite.&lt;br /&gt;
You will not be able to add another test to the suite which does not require these conditions without those conditions being fulfilled anyway.&lt;br /&gt;
This can lead to slow tests.&lt;br /&gt;
&lt;br /&gt;
Secondly, because of the way in which PHPUnit operates. it creates an instance of each testcase during its bootstrap phase. These are stored in memory until the _entire suite_ completes.&lt;br /&gt;
This means that any fixture which is setup and not actively discarded will not be garbage collected and lead to memory bloat.&lt;br /&gt;
In severe cases this can lead to memory exhaustion.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Make use of the dataProvider functionality==&lt;br /&gt;
&lt;br /&gt;
The dataProvider functionality of PHPUnit is an extremely powerful and useful feature which allows you to verify a function quickly and easily with a range of different conditions.&lt;br /&gt;
However, the following rules should be followed when using dataProviders:&lt;br /&gt;
* Keep addition of resettable data requring resetAfterTest to a minimum - this will lead to many slow tests&lt;br /&gt;
* You can only _describe_ data in a dataProvider, you cannot create the data. The dataProvider is called after the testSuite is instantiated, but before any tests are run. Each test will run a full setUp and tearDown, which will destroy any data which was created.&lt;br /&gt;
=Extra test settings=&lt;br /&gt;
&lt;br /&gt;
Usually the test should not interact with any external systems and it should work the same on all systems. But sometimes you need to specify some option for connection to external systems or system configuration. It is intentionally not possible to use $CFG settings from config.php.&lt;br /&gt;
&lt;br /&gt;
There are several ways how to inject your custom settings:&lt;br /&gt;
* define test setting constants in your phpunit.xml file&lt;br /&gt;
* define test setting constants in your config.php&lt;br /&gt;
&lt;br /&gt;
These constants may be then used in your test or plugin code.&lt;br /&gt;
&lt;br /&gt;
=Upgrading unit tests to work with Moodle 3.7 and up (PHPUnit 7.5)=&lt;br /&gt;
{{Moodle 3.7}}&lt;br /&gt;
&lt;br /&gt;
With Moodle 3.7, &#039;&#039;&#039;PHPUnit was upgraded to 7.5&#039;&#039;&#039; (from 6.x being used in older version). This was done to better align the testing environment with PHP versions supported by Moodle 3.7 (7.1, 7.2 and 7.3). (see MDL-65204 and linked issues for more details). While internally [https://phpunit.de/announcements/phpunit-7.html a lot of things changed with PHPUnit 7] (PHP 7.1-isms, typed signatures, void returns, assertEquals() changes), thanks to our &#039;&#039;&#039;wrapping layer&#039;&#039;&#039; (basic and advanced testcases...) impact expected into old existing unit tests is expected to be reduced and upgrades, easy to achieve.&lt;br /&gt;
&lt;br /&gt;
To find more information about the changes coming with PHPUnit 7, it&#039;s recommended to read the following resources:&lt;br /&gt;
* [https://phpunit.de/announcements/phpunit-7.html  PHPUnit 7 release Announcement].&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/blob/7.5/ChangeLog-7.5.md Detailed changelog of the release], paying special attention to the added, deprecated, changed and deleted sections.&lt;br /&gt;
* [https://phpunit.readthedocs.io/en/7.5/ Official PHPUnit manual].&lt;br /&gt;
* Changes performed into core, mainly: new, stricter, signatures ([https://github.com/moodle/moodle/commit/26218b7 26218b7]) and assertEquals() changes, specially important when comparing strings, now performed using strict (===) equals ([https://github.com/moodle/moodle/commit/85f47ba 85f47ba]).&lt;br /&gt;
&lt;br /&gt;
=Upgrading unit tests to work with Moodle 3.4 and up (PHPUnit 6)=&lt;br /&gt;
{{Moodle 3.4}}&lt;br /&gt;
&lt;br /&gt;
With Moodle 3.4, &#039;&#039;&#039;PHPUnit was upgraded to 6.4&#039;&#039;&#039; (from 5.5 being used in older version). This was done to better align the testing environment with PHP versions supported by Moodle 3.4 (7.0, 7.1 and 7.2). (see MDL-60611 and linked issues for more details). While internally [https://github.com/sebastianbergmann/phpunit/wiki/Release-Announcement-for-PHPUnit-6.0.0#backwards-compatibility-issues a lot of things changed with PHPUnit 6] (namespaced classes being the more noticeable), thanks to our &#039;&#039;&#039;wrapping layer&#039;&#039;&#039; (basic and advanced testcases...) impact expected into old existing unit tests is expected to be reduced and upgrades, easy to achieve.&lt;br /&gt;
&lt;br /&gt;
Still, in &#039;&#039;&#039;some cases, it will impossible to maintain compatibility&#039;&#039;&#039; of tests between old (pre 3.4) tests and new ones, especially &#039;&#039;&#039;when direct use of any phpunit class is performed&#039;&#039;&#039;. Luckily, both travis and CI tests will detect this situation and it shouldn&#039;t be hard to keep all supported branches in core passing ok. Plugins may be trickier, if the same branch is attempting to work against multiple core branches and they are using some phpunit class directly.&lt;br /&gt;
&lt;br /&gt;
To find more information about the changes coming with PHPUnit 6, it&#039;s recommended to read the following resources:&lt;br /&gt;
* [https://thephp.cc/news/2017/02/migrating-to-phpunit-6 A very good mini-guide] showing all the important changes.&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/wiki/Release-Announcement-for-PHPUnit-6.0.0#backwards-compatibility-issues PHPUnit 6 release Announcement].&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/blob/6.0/ChangeLog-6.0.md#600---2017-02-03 Detailed changelog of the release], paying special attention to the changed and deleted sections.&lt;br /&gt;
* Changes performed into core, mainly: namespace class renaming ([https://github.com/moodle/moodle/commit/801a372dadb6e11c8781547603e3f0a59ce5638f 801a372]) and deprecated stuff ([https://github.com/moodle/moodle/commit/796e48a58bf18533bdca423fff7949ab119101c4 796e48a])&lt;br /&gt;
&lt;br /&gt;
=See also=&lt;br /&gt;
* [[PHPUnit integration]]&lt;br /&gt;
* [[PHPUnit]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=AMD_Modal&amp;diff=55762</id>
		<title>AMD Modal</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=AMD_Modal&amp;diff=55762"/>
		<updated>2019-03-19T14:07:26Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* How to manually trigger a Modal */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
This is currently (2017) the recommended way to create various sorts of pop-up dialogue boxes in JavaScript.&lt;br /&gt;
&lt;br /&gt;
= Why should you use it? =&lt;br /&gt;
The AMD modal modules provide a simple interface for creating a modal within Moodle. The module will ensure all accessibility requirements are met including applying the correct aria roles, focus control, aria hiding background elements and locking keyboard navigation.&lt;br /&gt;
&lt;br /&gt;
The modals will fire events for common actions that occur within the modal, e.g. show / hide, for other code to listen to and react accordingly.&lt;br /&gt;
&lt;br /&gt;
Moodle ships with a couple of standard modal types for you to re-use including a simple cancel modal, a confirm (yes/no) modal and a save/cancel modal. Hopefully with more to come!&lt;br /&gt;
&lt;br /&gt;
= How do you create a basic modal? =&lt;br /&gt;
If you&#039;d simply like to display a modal with some simple content then all you&#039;ll need is the modal factory. The factory provides a create function that accepts some configuration for your modal that the factory will use to create the modal and optionally a trigger element (the element that will open the modal when activated). The create function will return a promise that is resolved with the created modal.&lt;br /&gt;
&lt;br /&gt;
The configuration is provided as an object with key/value pairs. The options are:&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;key&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| title&lt;br /&gt;
| the title to display in the modal header - note: this will render HTML&lt;br /&gt;
|-&lt;br /&gt;
| body&lt;br /&gt;
| the main content to be rendered in the modal body&lt;br /&gt;
|-&lt;br /&gt;
| footer&lt;br /&gt;
| the content to be rendered in the modal footer&lt;br /&gt;
|-&lt;br /&gt;
| type&lt;br /&gt;
| one of the modal types registered with the factory&lt;br /&gt;
|-&lt;br /&gt;
| large&lt;br /&gt;
| a boolean to indicate if the modal should be wider than the default size&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example 1&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The modals will also accept a promise for the body and footer content. It is expected that the promise will be resolved with two strings, the html content and any associated javascript. This allows the modal module to work natively with the templates module, such as in the example below.&lt;br /&gt;
&lt;br /&gt;
Example 2&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/templates&#039;], function($, ModalFactory, Templates) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    // Can include JS which is run when modal is attached to DOM.&lt;br /&gt;
    body: Templates.render(&#039;core/modal_test_3&#039;, {}),&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the modals aren&#039;t actually added to the DOM until they are made visible any javascript resolved with the promise is cached and only run after all of the elements are added to the DOM,  so you don&#039;t have to worry about any special handling in the javascript that is being loaded from the template.&lt;br /&gt;
&lt;br /&gt;
= How to manually trigger a Modal =&lt;br /&gt;
&lt;br /&gt;
If your trigger is (say) a class that covers a number of actual elements, you may want to grab the actual element that caused the event. Unfortunately, ModalEvents do not capture this and you need to approach the modal a different way. Here is an example - the scenario is a list of items with delete buttons. You need to know which delete button in order to get the item id. Note that code is somewhat simplified for clarity.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    $(&#039;a.item-delete&#039;).on(&#039;click&#039;, function(e) {&lt;br /&gt;
        var clickedLink = $(e.currentTarget);&lt;br /&gt;
        ModalFactory.create({&lt;br /&gt;
            type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
            title: &#039;Delete item&#039;,&lt;br /&gt;
            body: &#039;Do you really want to delete?&#039;,&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(modal) {&lt;br /&gt;
            modal.setSaveButtonText(&#039;Delete&#039;);&lt;br /&gt;
            var root = modal.getRoot();&lt;br /&gt;
            root.on(ModalEvents.save, function() {&lt;br /&gt;
                var elementid = clickedLink.data(&#039;id&#039;);&lt;br /&gt;
                // Do something to delete item&lt;br /&gt;
            });&lt;br /&gt;
            modal.show();&lt;br /&gt;
    });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;Normal&#039; javascript is used to trap the event and the required element can be captured. No &#039;trigger&#039; is passed to the ModalFactory so the resulting modal cannot display itself. The modal save event (in this case clicking the delete button) is handled as normally and will still trigger as expected. &#039;modal.show()&#039; is called to display the modal.&lt;br /&gt;
&lt;br /&gt;
= How do you create a different type of modal? =&lt;br /&gt;
Moodle comes with a few specialised types of modals for common use cases. Look at lib/amd/src/modal_factory.js to see what is available. These can be created using the factory, similar to the examples above, by specifying the type of modal in the configuration provided to the create function. Each of the modal types may have different configuration options, for example the save/cancel modal doesn&#039;t allow you to set the footer content.&lt;br /&gt;
&lt;br /&gt;
Example 3&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/templates&#039;], function($, ModalFactory, Templates) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
    title: &#039;Modal save cancel&#039;,&lt;br /&gt;
    body: &#039;This modal is a save/cancel modal&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each type of modal may fire additional events to allow your code to handle the new functionality being offered. See the modal_events.js module for a list of events that can be fired. For example, if you wanted to have a save/cancel modal that you did some form validation on before saving you could do something like the example below.&lt;br /&gt;
&lt;br /&gt;
Example 4&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/modal_events&#039;, &#039;core/templates&#039;],&lt;br /&gt;
        function($, ModalFactory, ModalEvents, Templates) {&lt;br /&gt;
  &lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
    title: &#039;Modal save cancel&#039;,&lt;br /&gt;
    body: &#039;This modal is a save/cancel modal&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    modal.getRoot().on(ModalEvents.save, function(e) {&lt;br /&gt;
      // Stop the default save button behaviour which is to close the modal.&lt;br /&gt;
      e.preventDefault();&lt;br /&gt;
      // Do your form validation here.&lt;br /&gt;
    });&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= How do you write a new type of modal? =&lt;br /&gt;
If you&#039;d like to write a new type of modal to be re-used throughout your code you can extend the default modal implementation and make any customisations you require. In order to create a new modal type you&#039;ll need to create a new AMD module and import the core/modal module to extend. You can also optionally create a new modal template that builds upon the core/modal template. Finally, you can register your new type with the modal registry to allow you to create your new type using the modal factory.&lt;br /&gt;
&lt;br /&gt;
For example, let&#039;s create a modal that opens a login form:&lt;br /&gt;
First we can create the HTML for out modal by including and extending the core modal template. All we need to do is override the block defined in that template with our new title, body and footer content.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume the file is &amp;lt;your_module&amp;gt;/templates/modal_login.mustache&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{&amp;lt; core/modal }}&lt;br /&gt;
    {{$title}}{{#str}} login {{/str}}{{/title}}&lt;br /&gt;
    {{$body}}&lt;br /&gt;
        &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;form&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;form-group row&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label for=&amp;quot;inputEmail&amp;quot; class=&amp;quot;col-sm-2 col-form-label&amp;quot;&amp;gt;{{#str}} email {{/str}}&amp;lt;/label&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;col-sm-10&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=&amp;quot;email&amp;quot; class=&amp;quot;form-control&amp;quot; id=&amp;quot;inputEmail&amp;quot; placeholder=&amp;quot;{{#str}} email {{/str}}&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;form-group row&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label for=&amp;quot;inputPassword&amp;quot; class=&amp;quot;col-sm-2 col-form-label&amp;quot;&amp;gt;{{#str}} password {{/str}}&amp;lt;/label&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;col-sm-10&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=&amp;quot;password&amp;quot; class=&amp;quot;form-control&amp;quot; id=&amp;quot;inputPassword&amp;quot; placeholder=&amp;quot;{{#str}} password {{/str}}&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;/form&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    {{/body}}&lt;br /&gt;
    {{$footer}}&lt;br /&gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;btn btn-primary&amp;quot; data-action=&amp;quot;login&amp;quot;&amp;gt;{{#str}} login {{/str}}&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;btn btn-secondary&amp;quot; data-action=&amp;quot;cancel&amp;quot;&amp;gt;{{#str}} cancel {{/str}}&amp;lt;/button&amp;gt;&lt;br /&gt;
    {{/footer}}&lt;br /&gt;
{{/ core/modal }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we can create the new AMD module for the login modal. You can extend the existing modal module which will give you all of the core modal functionality for free allowing you to focus on writing only the specific logic you need. In this example we would only need to write the logic for handling the login.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume the file is &amp;lt;your_module&amp;gt;/amd/src/modal_login.js&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/notification&#039;, &#039;core/custom_interaction_events&#039;, &#039;core/modal&#039;, &#039;core/modal_registry&#039;],&lt;br /&gt;
        function($, Notification, CustomEvents, Modal, ModalRegistry) {&lt;br /&gt;
&lt;br /&gt;
    var registered = false;&lt;br /&gt;
    var SELECTORS = {&lt;br /&gt;
        LOGIN_BUTTON: &#039;[data-action=&amp;quot;login&amp;quot;]&#039;,&lt;br /&gt;
        CANCEL_BUTTON: &#039;[data-action=&amp;quot;cancel&amp;quot;]&#039;,&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Constructor for the Modal.&lt;br /&gt;
     *&lt;br /&gt;
     * @param {object} root The root jQuery element for the modal&lt;br /&gt;
     */&lt;br /&gt;
    var ModalLogin = function(root) {&lt;br /&gt;
        Modal.call(this, root);&lt;br /&gt;
&lt;br /&gt;
        if (!this.getFooter().find(SELECTORS.LOGIN_BUTTON).length) {&lt;br /&gt;
            Notification.exception({message: &#039;No login button found&#039;});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!this.getFooter().find(SELECTORS.CANCEL_BUTTON).length) {&lt;br /&gt;
            Notification.exception({message: &#039;No cancel button found&#039;});&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    ModalLogin.TYPE = &#039;your_module-login&#039;;&lt;br /&gt;
    ModalLogin.prototype = Object.create(Modal.prototype);&lt;br /&gt;
    ModalLogin.prototype.constructor = ModalLogin;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Set up all of the event handling for the modal.&lt;br /&gt;
     *&lt;br /&gt;
     * @method registerEventListeners&lt;br /&gt;
     */&lt;br /&gt;
    ModalLogin.prototype.registerEventListeners = function() {&lt;br /&gt;
        // Apply parent event listeners.&lt;br /&gt;
        Modal.prototype.registerEventListeners.call(this);&lt;br /&gt;
&lt;br /&gt;
        this.getModal().on(CustomEvents.events.activate, SELECTORS.LOGIN_BUTTON, function(e, data) {&lt;br /&gt;
            // Add your logic for when the login button is clicked. This could include the form validation,&lt;br /&gt;
            // loading animations, error handling etc.&lt;br /&gt;
        }.bind(this));&lt;br /&gt;
&lt;br /&gt;
        this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, function(e, data) {&lt;br /&gt;
            // Add your logic for when the cancel button is clicked.&lt;br /&gt;
        }.bind(this));&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    // Automatically register with the modal registry the first time this module is imported so that you can create modals&lt;br /&gt;
    // of this type using the modal factory.&lt;br /&gt;
    if (!registered) {&lt;br /&gt;
        ModalRegistry.register(ModalLogin.TYPE, ModalLogin, &#039;&amp;lt;your_module&amp;gt;/modal_login&#039;);&lt;br /&gt;
        registered = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return ModalLogin;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you have both your template and javascript ready to go then all you need to do is tie them into where ever you&#039;d like to launch the modal. For the purpose of this example let&#039;s assume that we&#039;ve got a page with a login button with id=&amp;quot;login&amp;quot; then you might add this javascript to the page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/templates&#039;, &#039;core/modal_factory&#039;, &#039;&amp;lt;your_module&amp;gt;/modal_login&#039;], function($, Templates, ModalFactory, ModalLogin) {&lt;br /&gt;
    var trigger = $(&#039;#login&#039;);&lt;br /&gt;
&lt;br /&gt;
    ModalFactory.create({type: ModalLogin.TYPE}, trigger); &lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Related =&lt;br /&gt;
* AMD and [[Javascript Modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=AMD_Modal&amp;diff=55761</id>
		<title>AMD Modal</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=AMD_Modal&amp;diff=55761"/>
		<updated>2019-03-19T09:57:09Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* How do you create a basic modal? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
This is currently (2017) the recommended way to create various sorts of pop-up dialogue boxes in JavaScript.&lt;br /&gt;
&lt;br /&gt;
= Why should you use it? =&lt;br /&gt;
The AMD modal modules provide a simple interface for creating a modal within Moodle. The module will ensure all accessibility requirements are met including applying the correct aria roles, focus control, aria hiding background elements and locking keyboard navigation.&lt;br /&gt;
&lt;br /&gt;
The modals will fire events for common actions that occur within the modal, e.g. show / hide, for other code to listen to and react accordingly.&lt;br /&gt;
&lt;br /&gt;
Moodle ships with a couple of standard modal types for you to re-use including a simple cancel modal, a confirm (yes/no) modal and a save/cancel modal. Hopefully with more to come!&lt;br /&gt;
&lt;br /&gt;
= How do you create a basic modal? =&lt;br /&gt;
If you&#039;d simply like to display a modal with some simple content then all you&#039;ll need is the modal factory. The factory provides a create function that accepts some configuration for your modal that the factory will use to create the modal and optionally a trigger element (the element that will open the modal when activated). The create function will return a promise that is resolved with the created modal.&lt;br /&gt;
&lt;br /&gt;
The configuration is provided as an object with key/value pairs. The options are:&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;key&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| title&lt;br /&gt;
| the title to display in the modal header - note: this will render HTML&lt;br /&gt;
|-&lt;br /&gt;
| body&lt;br /&gt;
| the main content to be rendered in the modal body&lt;br /&gt;
|-&lt;br /&gt;
| footer&lt;br /&gt;
| the content to be rendered in the modal footer&lt;br /&gt;
|-&lt;br /&gt;
| type&lt;br /&gt;
| one of the modal types registered with the factory&lt;br /&gt;
|-&lt;br /&gt;
| large&lt;br /&gt;
| a boolean to indicate if the modal should be wider than the default size&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example 1&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The modals will also accept a promise for the body and footer content. It is expected that the promise will be resolved with two strings, the html content and any associated javascript. This allows the modal module to work natively with the templates module, such as in the example below.&lt;br /&gt;
&lt;br /&gt;
Example 2&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/templates&#039;], function($, ModalFactory, Templates) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    // Can include JS which is run when modal is attached to DOM.&lt;br /&gt;
    body: Templates.render(&#039;core/modal_test_3&#039;, {}),&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the modals aren&#039;t actually added to the DOM until they are made visible any javascript resolved with the promise is cached and only run after all of the elements are added to the DOM,  so you don&#039;t have to worry about any special handling in the javascript that is being loaded from the template.&lt;br /&gt;
&lt;br /&gt;
= How to manually trigger a Modal =&lt;br /&gt;
&lt;br /&gt;
If your trigger is (say) a class that covers a number of actual elements, you may want to grab the actual element that caused the event. Unfortunately, ModalEvents do not capture this and you need to approach the modal a different way. Here is an example - the scenario is a list of items with delete buttons. You need to know which delete button in order to get the item id. Note that code is somewhat simplified for clarity.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    $(&#039;a.item-delete&#039;).on(&#039;click&#039;, function(e) {&lt;br /&gt;
        var clickedLink = $(e.currentTarget);&lt;br /&gt;
        ModalFactory.create({&lt;br /&gt;
            type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
            title: &#039;Delete item&#039;,&lt;br /&gt;
            body: &#039;Do you really want to delete?&#039;,&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(modal) {&lt;br /&gt;
            modal.setSaveButtonText(&#039;Delete&#039;);&lt;br /&gt;
            var root = modal.getRoot();&lt;br /&gt;
            root.on(ModalEvents.save, function() {&lt;br /&gt;
                var elementid = clickedLink.data(&#039;templateid&#039;);&lt;br /&gt;
                // Do something to delete item&lt;br /&gt;
            });&lt;br /&gt;
            modal.show();&lt;br /&gt;
    });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;Normal&#039; javascript is used to trap the event and the required element can be captured. No &#039;trigger&#039; is passed to the ModalFactory so the resulting modal cannot display itself. The modal save event (in this case clicking the delete button) is handled as normally and will still trigger as expected. &#039;modal.show()&#039; is called to display the modal.&lt;br /&gt;
&lt;br /&gt;
= How do you create a different type of modal? =&lt;br /&gt;
Moodle comes with a few specialised types of modals for common use cases. Look at lib/amd/src/modal_factory.js to see what is available. These can be created using the factory, similar to the examples above, by specifying the type of modal in the configuration provided to the create function. Each of the modal types may have different configuration options, for example the save/cancel modal doesn&#039;t allow you to set the footer content.&lt;br /&gt;
&lt;br /&gt;
Example 3&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/templates&#039;], function($, ModalFactory, Templates) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
    title: &#039;Modal save cancel&#039;,&lt;br /&gt;
    body: &#039;This modal is a save/cancel modal&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each type of modal may fire additional events to allow your code to handle the new functionality being offered. See the modal_events.js module for a list of events that can be fired. For example, if you wanted to have a save/cancel modal that you did some form validation on before saving you could do something like the example below.&lt;br /&gt;
&lt;br /&gt;
Example 4&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/modal_events&#039;, &#039;core/templates&#039;],&lt;br /&gt;
        function($, ModalFactory, ModalEvents, Templates) {&lt;br /&gt;
  &lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
    title: &#039;Modal save cancel&#039;,&lt;br /&gt;
    body: &#039;This modal is a save/cancel modal&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    modal.getRoot().on(ModalEvents.save, function(e) {&lt;br /&gt;
      // Stop the default save button behaviour which is to close the modal.&lt;br /&gt;
      e.preventDefault();&lt;br /&gt;
      // Do your form validation here.&lt;br /&gt;
    });&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= How do you write a new type of modal? =&lt;br /&gt;
If you&#039;d like to write a new type of modal to be re-used throughout your code you can extend the default modal implementation and make any customisations you require. In order to create a new modal type you&#039;ll need to create a new AMD module and import the core/modal module to extend. You can also optionally create a new modal template that builds upon the core/modal template. Finally, you can register your new type with the modal registry to allow you to create your new type using the modal factory.&lt;br /&gt;
&lt;br /&gt;
For example, let&#039;s create a modal that opens a login form:&lt;br /&gt;
First we can create the HTML for out modal by including and extending the core modal template. All we need to do is override the block defined in that template with our new title, body and footer content.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume the file is &amp;lt;your_module&amp;gt;/templates/modal_login.mustache&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{&amp;lt; core/modal }}&lt;br /&gt;
    {{$title}}{{#str}} login {{/str}}{{/title}}&lt;br /&gt;
    {{$body}}&lt;br /&gt;
        &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;form&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;form-group row&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label for=&amp;quot;inputEmail&amp;quot; class=&amp;quot;col-sm-2 col-form-label&amp;quot;&amp;gt;{{#str}} email {{/str}}&amp;lt;/label&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;col-sm-10&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=&amp;quot;email&amp;quot; class=&amp;quot;form-control&amp;quot; id=&amp;quot;inputEmail&amp;quot; placeholder=&amp;quot;{{#str}} email {{/str}}&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;form-group row&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label for=&amp;quot;inputPassword&amp;quot; class=&amp;quot;col-sm-2 col-form-label&amp;quot;&amp;gt;{{#str}} password {{/str}}&amp;lt;/label&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;col-sm-10&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=&amp;quot;password&amp;quot; class=&amp;quot;form-control&amp;quot; id=&amp;quot;inputPassword&amp;quot; placeholder=&amp;quot;{{#str}} password {{/str}}&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;/form&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    {{/body}}&lt;br /&gt;
    {{$footer}}&lt;br /&gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;btn btn-primary&amp;quot; data-action=&amp;quot;login&amp;quot;&amp;gt;{{#str}} login {{/str}}&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;btn btn-secondary&amp;quot; data-action=&amp;quot;cancel&amp;quot;&amp;gt;{{#str}} cancel {{/str}}&amp;lt;/button&amp;gt;&lt;br /&gt;
    {{/footer}}&lt;br /&gt;
{{/ core/modal }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we can create the new AMD module for the login modal. You can extend the existing modal module which will give you all of the core modal functionality for free allowing you to focus on writing only the specific logic you need. In this example we would only need to write the logic for handling the login.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume the file is &amp;lt;your_module&amp;gt;/amd/src/modal_login.js&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/notification&#039;, &#039;core/custom_interaction_events&#039;, &#039;core/modal&#039;, &#039;core/modal_registry&#039;],&lt;br /&gt;
        function($, Notification, CustomEvents, Modal, ModalRegistry) {&lt;br /&gt;
&lt;br /&gt;
    var registered = false;&lt;br /&gt;
    var SELECTORS = {&lt;br /&gt;
        LOGIN_BUTTON: &#039;[data-action=&amp;quot;login&amp;quot;]&#039;,&lt;br /&gt;
        CANCEL_BUTTON: &#039;[data-action=&amp;quot;cancel&amp;quot;]&#039;,&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Constructor for the Modal.&lt;br /&gt;
     *&lt;br /&gt;
     * @param {object} root The root jQuery element for the modal&lt;br /&gt;
     */&lt;br /&gt;
    var ModalLogin = function(root) {&lt;br /&gt;
        Modal.call(this, root);&lt;br /&gt;
&lt;br /&gt;
        if (!this.getFooter().find(SELECTORS.LOGIN_BUTTON).length) {&lt;br /&gt;
            Notification.exception({message: &#039;No login button found&#039;});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!this.getFooter().find(SELECTORS.CANCEL_BUTTON).length) {&lt;br /&gt;
            Notification.exception({message: &#039;No cancel button found&#039;});&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    ModalLogin.TYPE = &#039;your_module-login&#039;;&lt;br /&gt;
    ModalLogin.prototype = Object.create(Modal.prototype);&lt;br /&gt;
    ModalLogin.prototype.constructor = ModalLogin;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Set up all of the event handling for the modal.&lt;br /&gt;
     *&lt;br /&gt;
     * @method registerEventListeners&lt;br /&gt;
     */&lt;br /&gt;
    ModalLogin.prototype.registerEventListeners = function() {&lt;br /&gt;
        // Apply parent event listeners.&lt;br /&gt;
        Modal.prototype.registerEventListeners.call(this);&lt;br /&gt;
&lt;br /&gt;
        this.getModal().on(CustomEvents.events.activate, SELECTORS.LOGIN_BUTTON, function(e, data) {&lt;br /&gt;
            // Add your logic for when the login button is clicked. This could include the form validation,&lt;br /&gt;
            // loading animations, error handling etc.&lt;br /&gt;
        }.bind(this));&lt;br /&gt;
&lt;br /&gt;
        this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, function(e, data) {&lt;br /&gt;
            // Add your logic for when the cancel button is clicked.&lt;br /&gt;
        }.bind(this));&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    // Automatically register with the modal registry the first time this module is imported so that you can create modals&lt;br /&gt;
    // of this type using the modal factory.&lt;br /&gt;
    if (!registered) {&lt;br /&gt;
        ModalRegistry.register(ModalLogin.TYPE, ModalLogin, &#039;&amp;lt;your_module&amp;gt;/modal_login&#039;);&lt;br /&gt;
        registered = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return ModalLogin;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you have both your template and javascript ready to go then all you need to do is tie them into where ever you&#039;d like to launch the modal. For the purpose of this example let&#039;s assume that we&#039;ve got a page with a login button with id=&amp;quot;login&amp;quot; then you might add this javascript to the page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/templates&#039;, &#039;core/modal_factory&#039;, &#039;&amp;lt;your_module&amp;gt;/modal_login&#039;], function($, Templates, ModalFactory, ModalLogin) {&lt;br /&gt;
    var trigger = $(&#039;#login&#039;);&lt;br /&gt;
&lt;br /&gt;
    ModalFactory.create({type: ModalLogin.TYPE}, trigger); &lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Related =&lt;br /&gt;
* AMD and [[Javascript Modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=55753</id>
		<title>Useful core Javascript modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=55753"/>
		<updated>2019-03-16T12:10:37Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Language strings (core/str) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
The source code for all these modules (and a number of others) can be found in lib/amd/src. &lt;br /&gt;
&lt;br /&gt;
== Configuration settings (core/config) ==&lt;br /&gt;
&lt;br /&gt;
Example of using config module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/config’], function(mdlcfg) {&lt;br /&gt;
    console.log(mdlcfg.wwwroot); // outputs the wwwroot of moodle to console&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Language strings (core/str) ==&lt;br /&gt;
&lt;br /&gt;
Example of using language strings module (retrieved via ajax, if the string is not yet loaded)&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([‘core/str’], function(str) {&lt;br /&gt;
    // start retrieving the localized string; store the promise that some time in the future the string will be there.&lt;br /&gt;
    var editaPresent = str.get_string(&#039;edita&#039;, &#039;core&#039;, stringargument);&lt;br /&gt;
    // as soon as the string is retrieved, i.e. the promise has been fulfilled,&lt;br /&gt;
    // edit the text of a UI element so that it then is the localized string&lt;br /&gt;
    // Note: $.when can be used with an arbitrary number of promised things&lt;br /&gt;
    $.when(editaPresent).done(function(localizedEditString) {&lt;br /&gt;
         $(&amp;quot;someUIElementSelector&amp;quot;).text = localizedEditString;&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also convert multiple strings and add parameters in a single call&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    // Real example from user/amd/src/status_field.js&lt;br /&gt;
    var strings = [&lt;br /&gt;
        {&lt;br /&gt;
            key: &#039;unenrol&#039;,&lt;br /&gt;
            component: &#039;enrol&#039;&lt;br /&gt;
        },&lt;br /&gt;
        {&lt;br /&gt;
            key: &#039;unenrolconfirm&#039;,&lt;br /&gt;
            component: &#039;enrol&#039;,&lt;br /&gt;
            param: {&lt;br /&gt;
                user: parentContainer.data(&#039;fullname&#039;),&lt;br /&gt;
                course: parentContainer.data(&#039;coursename&#039;),&lt;br /&gt;
                enrolinstancename: parentContainer.data(&#039;enrolinstancename&#039;)&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    ];&lt;br /&gt;
    str.get_strings(strings).then(function (results) {&lt;br /&gt;
        console.log(results);&lt;br /&gt;
    });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string will be retrieved via AJAX request on the first use and cached in browser local storage until purge caches or site upgrade&lt;br /&gt;
&lt;br /&gt;
IT IS NOT RECOMMENDED to call:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;string_for_js(&#039;edita&#039;, &#039;core&#039;); // You do not need this!&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because:&lt;br /&gt;
* The list of strings used now needs to be maintained in 2 places&lt;br /&gt;
* The strings are always sent, and bloat the page size even if they are not used&lt;br /&gt;
* All strings fetched via the AJAX method above are cached in browser local storage anyway, so these strings will never be used 99% of the time&lt;br /&gt;
&lt;br /&gt;
==Notifications (core/notification)==&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/notification’], function(notification) {&lt;br /&gt;
    notification.alert(&#039;Hello&#039;, &#039;Welcome to my site!&#039;, &#039;Continue&#039;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Strings and notifications:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/str&#039;, &#039;core/notification’], function(str, notification) {&lt;br /&gt;
                str.get_strings([&lt;br /&gt;
                        {&#039;key&#039; : &#039;delete&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;confirmdeletetag&#039;, component : &#039;tag&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;yes&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;no&#039;},&lt;br /&gt;
                    ]).done(function(s) {&lt;br /&gt;
                        notification.confirm(s[0], s[1], s[2], s[3], function() {&lt;br /&gt;
                            window.location.href = href;&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                ).fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== URL module (core/url) ==&lt;br /&gt;
&lt;br /&gt;
Useful Functions:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Generate an absolute URL by using the relative path to a file and optionally slash arguments&lt;br /&gt;
url.fileUrl(relativeScript, slashArgs)&lt;br /&gt;
// Generate an absolute URL from the given relative path, include the URL parameters and possibly the current session key&lt;br /&gt;
url.relativeUrl(relativePath, params, includeSessionKey)&lt;br /&gt;
// Generates an image url using the filename of the image and the Moodle component (core, plugin, etc.) where it can be found&lt;br /&gt;
url.imageUrl(imagename, component)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Prerequisites:&lt;br /&gt;
// - A Javascript file is present under $CFG-&amp;gt;wwwroot.&amp;quot;/path/to/file.js&amp;quot;&lt;br /&gt;
require([&#039;core/url&#039;], function(url) {&lt;br /&gt;
    url.fileUrl(&amp;quot;/path/to/file.js&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
    console.log(&amp;quot;Generated URL: &amp;quot; + url);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ajax Module (core/ajax) ==&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. This saves having to worry about security in ajax functionality because it&#039;s all done for you. &lt;br /&gt;
&lt;br /&gt;
For the full story, see [[AJAX]]&lt;br /&gt;
&lt;br /&gt;
== Tree (core/tree) ==&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
If you want to add a hierarchical interface, you should use the tree module. It will handle user interaction and accessibility for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/tree&#039;], function($, Tree) {&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
            new Tree(&amp;quot;css/jquery selector of the tree container&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[Tree]] module&lt;br /&gt;
&lt;br /&gt;
== Modal (core/modal) ==&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
If you&#039;d like to add a modal to your page the modal modules will handle all of the magic for you, all you need to bring is your content!&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
&lt;br /&gt;
    // Maybe... add a class to the modal dialog, to be able to style it.&lt;br /&gt;
    modal.getRoot().addClass(&#039;mydialog&#039;);&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_Modal]] module&lt;br /&gt;
&lt;br /&gt;
== Paged content (core/paged_content_factory) ==&lt;br /&gt;
{{Moodle 3.6}}&lt;br /&gt;
&lt;br /&gt;
If you need to create a paged content region to asynchronously load the pages as the user requests them then you can use the paged content modules.&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_paged_content]] module&lt;br /&gt;
&lt;br /&gt;
== PubSub (core/pubsub) ==&lt;br /&gt;
{{Moodle 3.6}}&lt;br /&gt;
&lt;br /&gt;
A simple module to do event subscription and publishing in JavaScript without the need for jQuery.&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_pubsub]] module&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=55752</id>
		<title>Useful core Javascript modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=55752"/>
		<updated>2019-03-16T12:10:22Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Language strings (core/str) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
The source code for all these modules (and a number of others) can be found in lib/amd/src. &lt;br /&gt;
&lt;br /&gt;
== Configuration settings (core/config) ==&lt;br /&gt;
&lt;br /&gt;
Example of using config module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/config’], function(mdlcfg) {&lt;br /&gt;
    console.log(mdlcfg.wwwroot); // outputs the wwwroot of moodle to console&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Language strings (core/str) ==&lt;br /&gt;
&lt;br /&gt;
Example of using language strings module (retrieved via ajax, if the string is not yet loaded)&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([‘core/str’], function(str) {&lt;br /&gt;
    // start retrieving the localized string; store the promise that some time in the future the string will be there.&lt;br /&gt;
    var editaPresent = str.get_string(&#039;edita&#039;, &#039;core&#039;, stringargument);&lt;br /&gt;
    // as soon as the string is retrieved, i.e. the promise has been fulfilled,&lt;br /&gt;
    // edit the text of a UI element so that it then is the localized string&lt;br /&gt;
    // Note: $.when can be used with an arbitrary number of promised things&lt;br /&gt;
    $.when(editaPresent).done(function(localizedEditString) {&lt;br /&gt;
         $(&amp;quot;someUIElementSelector&amp;quot;).text = localizedEditString;&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also convert multiple strings and add parameters in a single call&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    // Real example from user/amd/src/status_field.js&lt;br /&gt;
    var strings = [&lt;br /&gt;
        {&lt;br /&gt;
            key: &#039;unenrol&#039;,&lt;br /&gt;
            component: &#039;enrol&#039;&lt;br /&gt;
        },&lt;br /&gt;
        {&lt;br /&gt;
            key: &#039;unenrolconfirm&#039;,&lt;br /&gt;
            component: &#039;enrol&#039;,&lt;br /&gt;
            param: {&lt;br /&gt;
                user: parentContainer.data(&#039;fullname&#039;),&lt;br /&gt;
                course: parentContainer.data(&#039;coursename&#039;),&lt;br /&gt;
                enrolinstancename: parentContainer.data(&#039;enrolinstancename&#039;)&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    ];&lt;br /&gt;
    str.get_strings(strings).then(function (results) {&lt;br /&gt;
        console.log(results);&lt;br /&gt;
    });&lt;br /&gt;
/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string will be retrieved via AJAX request on the first use and cached in browser local storage until purge caches or site upgrade&lt;br /&gt;
&lt;br /&gt;
IT IS NOT RECOMMENDED to call:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;string_for_js(&#039;edita&#039;, &#039;core&#039;); // You do not need this!&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because:&lt;br /&gt;
* The list of strings used now needs to be maintained in 2 places&lt;br /&gt;
* The strings are always sent, and bloat the page size even if they are not used&lt;br /&gt;
* All strings fetched via the AJAX method above are cached in browser local storage anyway, so these strings will never be used 99% of the time&lt;br /&gt;
&lt;br /&gt;
==Notifications (core/notification)==&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/notification’], function(notification) {&lt;br /&gt;
    notification.alert(&#039;Hello&#039;, &#039;Welcome to my site!&#039;, &#039;Continue&#039;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Strings and notifications:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/str&#039;, &#039;core/notification’], function(str, notification) {&lt;br /&gt;
                str.get_strings([&lt;br /&gt;
                        {&#039;key&#039; : &#039;delete&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;confirmdeletetag&#039;, component : &#039;tag&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;yes&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;no&#039;},&lt;br /&gt;
                    ]).done(function(s) {&lt;br /&gt;
                        notification.confirm(s[0], s[1], s[2], s[3], function() {&lt;br /&gt;
                            window.location.href = href;&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                ).fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== URL module (core/url) ==&lt;br /&gt;
&lt;br /&gt;
Useful Functions:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Generate an absolute URL by using the relative path to a file and optionally slash arguments&lt;br /&gt;
url.fileUrl(relativeScript, slashArgs)&lt;br /&gt;
// Generate an absolute URL from the given relative path, include the URL parameters and possibly the current session key&lt;br /&gt;
url.relativeUrl(relativePath, params, includeSessionKey)&lt;br /&gt;
// Generates an image url using the filename of the image and the Moodle component (core, plugin, etc.) where it can be found&lt;br /&gt;
url.imageUrl(imagename, component)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Prerequisites:&lt;br /&gt;
// - A Javascript file is present under $CFG-&amp;gt;wwwroot.&amp;quot;/path/to/file.js&amp;quot;&lt;br /&gt;
require([&#039;core/url&#039;], function(url) {&lt;br /&gt;
    url.fileUrl(&amp;quot;/path/to/file.js&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
    console.log(&amp;quot;Generated URL: &amp;quot; + url);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ajax Module (core/ajax) ==&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. This saves having to worry about security in ajax functionality because it&#039;s all done for you. &lt;br /&gt;
&lt;br /&gt;
For the full story, see [[AJAX]]&lt;br /&gt;
&lt;br /&gt;
== Tree (core/tree) ==&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
If you want to add a hierarchical interface, you should use the tree module. It will handle user interaction and accessibility for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/tree&#039;], function($, Tree) {&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
            new Tree(&amp;quot;css/jquery selector of the tree container&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[Tree]] module&lt;br /&gt;
&lt;br /&gt;
== Modal (core/modal) ==&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
If you&#039;d like to add a modal to your page the modal modules will handle all of the magic for you, all you need to bring is your content!&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
&lt;br /&gt;
    // Maybe... add a class to the modal dialog, to be able to style it.&lt;br /&gt;
    modal.getRoot().addClass(&#039;mydialog&#039;);&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_Modal]] module&lt;br /&gt;
&lt;br /&gt;
== Paged content (core/paged_content_factory) ==&lt;br /&gt;
{{Moodle 3.6}}&lt;br /&gt;
&lt;br /&gt;
If you need to create a paged content region to asynchronously load the pages as the user requests them then you can use the paged content modules.&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_paged_content]] module&lt;br /&gt;
&lt;br /&gt;
== PubSub (core/pubsub) ==&lt;br /&gt;
{{Moodle 3.6}}&lt;br /&gt;
&lt;br /&gt;
A simple module to do event subscription and publishing in JavaScript without the need for jQuery.&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_pubsub]] module&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Adding_a_web_service_to_a_plugin&amp;diff=55532</id>
		<title>Adding a web service to a plugin</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Adding_a_web_service_to_a_plugin&amp;diff=55532"/>
		<updated>2019-02-01T14:40:21Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Declare the web service function */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
&lt;br /&gt;
= Quick start =&lt;br /&gt;
== Example ==&lt;br /&gt;
Have a look to the [https://github.com/moodlehq/moodle-local_wstemplate web service plugin template]. This plugin contains a web service hello_world function. To make testing easy for you, the plugin is distributed with a test client in the folder /client.&lt;br /&gt;
== File structure ==&lt;br /&gt;
The file structure is explained in these two documents:&lt;br /&gt;
* [[Web_services_API|Web services API]]&lt;br /&gt;
* [[External_functions_API|External function API]]&lt;br /&gt;
&lt;br /&gt;
= Tutorial =&lt;br /&gt;
We will create a web service into a local plugin. This service will contain one &#039;&#039;local_myplugin_create_groups($groups)&#039;&#039; web service function. This web service function will create a group into a Moodle course.&lt;br /&gt;
&lt;br /&gt;
== Write the specification documentation ==&lt;br /&gt;
Before starting coding, let&#039;s identify our needs writing some short specification documents.&lt;br /&gt;
&lt;br /&gt;
=== functional specification===&lt;br /&gt;
&#039;&#039;local_myplugin_create_groups($groups)&#039;&#039; will take a list of groups as parameters and it will return the same groups with their newly created id. If ever one group creation fails, the function will throw an exception, and no creation will happen.&lt;br /&gt;
&lt;br /&gt;
=== technical specification ===&lt;br /&gt;
* &#039;&#039;&#039;the core function the external function will call&#039;&#039;&#039;:  &#039;&#039;groups_create_group()&#039;&#039; from [http://cvs.moodle.org/moodle/group/ /group/lib.php].&lt;br /&gt;
* &#039;&#039;&#039;the parameter types&#039;&#039;&#039;: a list of object. This object are groups, with id/name/courseid.&lt;br /&gt;
* &#039;&#039;&#039;the returned value types&#039;&#039;&#039;:  a list of objects (groups) with their id.&lt;br /&gt;
* &#039;&#039;&#039;the user capabilities to check&#039;&#039;&#039;: &#039;&#039;moodle/course:managegroups&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Write a simple test client ==&lt;br /&gt;
The first thing you should code is a web service test client. You will often discover use cases that you didn&#039;t think about. We are not showing any test client code here, see [[Creating_a_web_service_client|How to create a web service client]].&lt;br /&gt;
&lt;br /&gt;
== Declare the service ==&lt;br /&gt;
This step is optional. You can pre-build a service including any web service functions, so the Moodle administrator doesn&#039;t need to do it. Add into /local/myplugin/db/services.php:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
  $services = array(&lt;br /&gt;
      &#039;mypluginservice&#039; =&amp;gt; array(                                                //the name of the web service&lt;br /&gt;
          &#039;functions&#039; =&amp;gt; array (&#039;local_myplugin_create_groups&#039;), //web service functions of this service&lt;br /&gt;
          &#039;requiredcapability&#039; =&amp;gt; &#039;&#039;,                //if set, the web service user need this capability to access &lt;br /&gt;
                                                                              //any function of this service. For example: &#039;some/capability:specified&#039;                 &lt;br /&gt;
          &#039;restrictedusers&#039; =&amp;gt;0,                                             //if enabled, the Moodle administrator must link some user to this service&lt;br /&gt;
                                                                              //into the administration&lt;br /&gt;
          &#039;enabled&#039;=&amp;gt;1,                                                       //if enabled, the service can be reachable on a default installation&lt;br /&gt;
       )&lt;br /&gt;
  );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: it is not possible for an administrator to add/remove any function from a pre-built service.&lt;br /&gt;
&lt;br /&gt;
== Declare the web service function ==&lt;br /&gt;
Following the [[Web_services_API|Web service API]], you must declare the web service function in the &#039;&#039;local/myplugin/db/services.php&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$functions = array(&lt;br /&gt;
    &#039;local_myplugin_create_groups&#039; =&amp;gt; array(         //web service function name&lt;br /&gt;
        &#039;classname&#039;   =&amp;gt; &#039;local_myplugin_external&#039;,  //class containing the external function&lt;br /&gt;
        &#039;methodname&#039;  =&amp;gt; &#039;create_groups&#039;,          //external function name&lt;br /&gt;
        &#039;classpath&#039;   =&amp;gt; &#039;local/myplugin/externallib.php&#039;,  //file containing the class/external function&lt;br /&gt;
        &#039;description&#039; =&amp;gt; &#039;Creates new groups.&#039;,    //human readable description of the web service function&lt;br /&gt;
        &#039;type&#039;        =&amp;gt; &#039;write&#039;,                  //database rights of the web service function (read, write)&lt;br /&gt;
        &#039;ajax&#039; =&amp;gt; true,        // is the service available to &#039;internal&#039; ajax calls. &lt;br /&gt;
        &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE)    // Optional, only available for Moodle 3.1 onwards. List of built-in services (by shortname) where the function will be included.  Services created manually via the Moodle interface are not supported.&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Web service functions should match the [https://docs.moodle.org/dev/Web_services_Roadmap#Naming_convention naming convention].&lt;br /&gt;
&lt;br /&gt;
== Write the external function descriptions ==&lt;br /&gt;
Every web service function is mapped to an external function. External function are described in the [[External_functions_API|External functions API]].&lt;br /&gt;
Each external function is written with two other functions describing the parameters and the return values. These description functions are used by web service servers to:&lt;br /&gt;
* validate the web service function parameters&lt;br /&gt;
* validate the web service function returned values&lt;br /&gt;
* build WSDL files or other protocol documents &lt;br /&gt;
&lt;br /&gt;
These two description functions are located in the same file and the same class mentioned in local/myplugin/db/services.php.&lt;br /&gt;
&lt;br /&gt;
Thus for the web service function &#039;&#039;&#039;local_myplugin_create_groups()&#039;&#039;&#039;, we need write a class named &#039;&#039;&#039;local_myplugin_external&#039;&#039;&#039; in the file &#039;&#039;&#039;local/myplugin/externallib.php&#039;&#039;&#039;. The class will contain:&lt;br /&gt;
* create_groups(...)&lt;br /&gt;
* create_groups_parameters()&lt;br /&gt;
* create_groups_return()&lt;br /&gt;
&lt;br /&gt;
=== create_groups_parameters() ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
require_once(&amp;quot;$CFG-&amp;gt;libdir/externallib.php&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
class local_myplugin_external extends external_api {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns description of method parameters&lt;br /&gt;
     * @return external_function_parameters&lt;br /&gt;
     */&lt;br /&gt;
    public static function create_groups_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;groups&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    new external_single_structure(&lt;br /&gt;
                        array(&lt;br /&gt;
                            &#039;courseid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;id of course&#039;),&lt;br /&gt;
                            &#039;name&#039; =&amp;gt; new external_value(PARAM_TEXT, &#039;multilang compatible name, course unique&#039;),&lt;br /&gt;
                            &#039;description&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;group description text&#039;),&lt;br /&gt;
                            &#039;enrolmentkey&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;group enrol secret phrase&#039;),&lt;br /&gt;
                        )&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;
&lt;br /&gt;
A web service function without parameters will have a parameter description function like that:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
     * Returns description of method parameters&lt;br /&gt;
     * @return external_function_parameters&lt;br /&gt;
     */&lt;br /&gt;
    public static function functionname_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
               //if I had any parameters, they would be described here. But I don&#039;t have any, so this array is empty.&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A parameter can be described as: &lt;br /&gt;
* a list               =&amp;gt; external_multiple_structure&lt;br /&gt;
* an object         =&amp;gt; external_single_structure&lt;br /&gt;
* a primary type =&amp;gt; external_value&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Our create_groups() function expects one parameter named &#039;&#039;groups&#039;&#039;, so we will first write:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns description of method parameters&lt;br /&gt;
     * @return external_function_parameters&lt;br /&gt;
     */&lt;br /&gt;
    public static function create_groups_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;groups&#039; =&amp;gt; ...&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;
This &#039;&#039;groups&#039;&#039; parameter is a list of group. So we will write :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;  &lt;br /&gt;
                &#039;groups&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    ...&lt;br /&gt;
                )          &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An external_multiple_structure object (list) can be constructed with: &lt;br /&gt;
* &#039;&#039;external_multiple_structure&#039;&#039; (list)&lt;br /&gt;
* &#039;&#039;external_single_structure&#039;&#039; (object)&lt;br /&gt;
* &#039;&#039;external_value&#039;&#039; (primary type). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For our function it will be a &#039;&#039;external_single_structure&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;             &lt;br /&gt;
                    new external_single_structure(&lt;br /&gt;
                        array(&lt;br /&gt;
                            &#039;courseid&#039; =&amp;gt; ...,&lt;br /&gt;
                            &#039;name&#039; =&amp;gt; ...,&lt;br /&gt;
                            &#039;description&#039; =&amp;gt; ...,&lt;br /&gt;
                            &#039;enrolmentkey&#039; =&amp;gt; ...,&lt;br /&gt;
                        )&lt;br /&gt;
                    )           &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus we obtain :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;  &lt;br /&gt;
                &#039;groups&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    new external_single_structure(&lt;br /&gt;
                        array(&lt;br /&gt;
                            &#039;courseid&#039; =&amp;gt; ...,&lt;br /&gt;
                            &#039;name&#039; =&amp;gt; ...,&lt;br /&gt;
                            &#039;description&#039; =&amp;gt; ...,&lt;br /&gt;
                            &#039;enrolmentkey&#039; =&amp;gt; ...,&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                )          &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each group values is a &#039;&#039;external_value&#039;&#039; (primary type):&lt;br /&gt;
* &#039;&#039;courseid&#039;&#039; is an integer&lt;br /&gt;
* &#039;&#039;name&#039;&#039; is a string (text only, not tag)&lt;br /&gt;
* &#039;&#039;description&#039;&#039; is a string (can be anything)&lt;br /&gt;
* &#039;&#039;enrolmentkey&#039;&#039; is also a string (can be anything) &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We add them to the description :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;  &lt;br /&gt;
                &#039;groups&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    new external_single_structure(&lt;br /&gt;
                        array(&lt;br /&gt;
                            &#039;courseid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;id of course&#039;), //the second argument is a human readable description text. This text is displayed in the automatically generated documentation.&lt;br /&gt;
                            &#039;name&#039; =&amp;gt; new external_value(PARAM_TEXT, &#039;multilang compatible name, course unique&#039;),&lt;br /&gt;
                            &#039;description&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;group description text&#039;),&lt;br /&gt;
                            &#039;enrolmentkey&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;group enrol secret phrase&#039;),&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                )          &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== create_groups_returns() ===&lt;br /&gt;
&lt;br /&gt;
It&#039;s similar to create_groups_parameters(), but instead of describing the parameters, it describes the return values.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function create_groups_returns() {&lt;br /&gt;
        return new external_multiple_structure(&lt;br /&gt;
            new external_single_structure(&lt;br /&gt;
                array(&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; new external_value(PARAM_INT, &#039;group record id&#039;),&lt;br /&gt;
                    &#039;courseid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;id of course&#039;),&lt;br /&gt;
                    &#039;name&#039; =&amp;gt; new external_value(PARAM_TEXT, &#039;multilang compatible name, course unique&#039;),&lt;br /&gt;
                    &#039;description&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;group description text&#039;),&lt;br /&gt;
                    &#039;enrolmentkey&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;group enrol secret phrase&#039;),&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;
=== Required, Optional or Default value ===&lt;br /&gt;
A value can be VALUE_REQUIRED, VALUE_OPTIONAL, or VALUE_DEFAULT. If not mentioned, a value is VALUE_REQUIRED by default.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;                 &lt;br /&gt;
                            &#039;yearofstudy&#039; =&amp;gt; new external_value(PARAM_INT, &#039;year of study&#039;,VALUE_DEFAULT, 1979),                        &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* VALUE_REQUIRED - if the value is not supplied =&amp;gt; the server throws an error message&lt;br /&gt;
* VALUE_OPTIONAL - if the value is not supplied =&amp;gt; the value is ignored. Note that VALUE_OPTIONAL can&#039;t be used in top level parameters, it must be used only within array/objects key definition. If you need top level Optional parameters you should use VALUE_DEFAULT instead.&lt;br /&gt;
* VALUE_DEFAULT   - if the value is not supplied =&amp;gt; the default value is used&lt;br /&gt;
&lt;br /&gt;
Note: Because some web service protocols are strict about the number and types of arguments - it is not possible to specify an optional parameter as one of the top-most parameters for a function. Examples:&lt;br /&gt;
&lt;br /&gt;
Not cool:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public static function get_biscuit_parameters() {                                                                  &lt;br /&gt;
        return new external_function_parameters(                                                                                    &lt;br /&gt;
            array(                                                                                                                  &lt;br /&gt;
                &#039;chocolatechips&#039; =&amp;gt; new external_value(PARAM_BOOL, PARAM_REQUIRED),&lt;br /&gt;
                &#039;glutenfree&#039; =&amp;gt; new external_value(PARAM_BOOL, PARAM_DEFAULT, false),&lt;br /&gt;
                &#039;icingsugar&#039; =&amp;gt; new external_value(PARAM_BOOL, VALUE_OPTIONAL), // ERROR! top level optional parameter!!!&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;
Cool:&lt;br /&gt;
&amp;lt;code php&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
    public static function get_biscuit_parameters() {                                                                  &lt;br /&gt;
        return new external_function_parameters(                                                                                    &lt;br /&gt;
            array(&lt;br /&gt;
                &#039;ifeellike&#039; =&amp;gt; new external_single_structure(&lt;br /&gt;
                    array(                                                                                                                  &lt;br /&gt;
                        &#039;chocolatechips&#039; =&amp;gt; new external_value(PARAM_BOOL, VALUE_REQUIRED),&lt;br /&gt;
                        &#039;glutenfree&#039; =&amp;gt; new external_value(PARAM_BOOL, PARAM_DEFAULT, false),&lt;br /&gt;
                        &#039;icingsugar&#039; =&amp;gt; new external_value(PARAM_BOOL, VALUE_OPTIONAL), // ALL GOOD!! We have nested the params in a external_single_structure.&lt;br /&gt;
                    )&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;
&lt;br /&gt;
== Implement the external function ==&lt;br /&gt;
We declared our web service function and we defined the external function parameters and return values. We will now implement the external function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Create groups&lt;br /&gt;
     * @param array $groups array of group description arrays (with keys groupname and courseid)&lt;br /&gt;
     * @return array of newly created groups&lt;br /&gt;
     */&lt;br /&gt;
    public static function create_groups($groups) { //Don&#039;t forget to set it as static&lt;br /&gt;
        global $CFG, $DB;&lt;br /&gt;
        require_once(&amp;quot;$CFG-&amp;gt;dirroot/group/lib.php&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        $params = self::validate_parameters(self::create_groups_parameters(), array(&#039;groups&#039;=&amp;gt;$groups));&lt;br /&gt;
&lt;br /&gt;
        $transaction = $DB-&amp;gt;start_delegated_transaction(); //If an exception is thrown in the below code, all DB queries in this code will be rollback.&lt;br /&gt;
&lt;br /&gt;
        $groups = array();&lt;br /&gt;
&lt;br /&gt;
        foreach ($params[&#039;groups&#039;] as $group) {&lt;br /&gt;
            $group = (object)$group;&lt;br /&gt;
&lt;br /&gt;
            if (trim($group-&amp;gt;name) == &#039;&#039;) {&lt;br /&gt;
                throw new invalid_parameter_exception(&#039;Invalid group name&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            if ($DB-&amp;gt;get_record(&#039;groups&#039;, array(&#039;courseid&#039;=&amp;gt;$group-&amp;gt;courseid, &#039;name&#039;=&amp;gt;$group-&amp;gt;name))) {&lt;br /&gt;
                throw new invalid_parameter_exception(&#039;Group with the same name already exists in the course&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // now security checks&lt;br /&gt;
            $context = get_context_instance(CONTEXT_COURSE, $group-&amp;gt;courseid);&lt;br /&gt;
            self::validate_context($context);&lt;br /&gt;
            require_capability(&#039;moodle/course:managegroups&#039;, $context);&lt;br /&gt;
&lt;br /&gt;
            // finally create the group&lt;br /&gt;
            $group-&amp;gt;id = groups_create_group($group, false);&lt;br /&gt;
            $groups[] = (array)$group;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $transaction-&amp;gt;allow_commit();&lt;br /&gt;
&lt;br /&gt;
        return $groups;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parameter validation ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $params = self::validate_parameters(self::create_groups_parameters(), array(&#039;groups&#039;=&amp;gt;$groups));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This &#039;&#039;validate_parameters&#039;&#039; function validates the external function parameters against the description. It will return an exception if some required parameters are missing, if parameters are not well-formed, and check the parameters validity. It is essential that you do this call to avoid potential hack. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; the parameters of the external function and their declaration in the description &#039;&#039;&#039;must be the same order&#039;&#039;&#039;. In this example we have only one parameter named $groups, so we don&#039;t need to worry about the order.&lt;br /&gt;
&lt;br /&gt;
=== Context and Capability checks ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/// now security checks&lt;br /&gt;
$context = context_course::instance($group-&amp;gt;courseid);&lt;br /&gt;
self::validate_context($context);&lt;br /&gt;
require_capability(&#039;moodle/course:managegroups&#039;, $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: validate_context() is required in all external functions before operating on any data belonging to a context. This function does sanity and security checks on the context that was passed to the external function - and sets up the global $PAGE and $OUTPUT for rendering return values. Do NOT use require_login(), or $PAGE-&amp;gt;set_context() in an external function.&lt;br /&gt;
&lt;br /&gt;
=== Exceptions===&lt;br /&gt;
You can throw exceptions. There are automatically handle by Moodle web service servers.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//Note: it is good practice to add detailled information in $debuginfo, &lt;br /&gt;
//         and only send back a generic exception message when Moodle DEBUG mode &amp;lt; NORMAL.&lt;br /&gt;
//         It&#039;s what we do here throwing the invalid_parameter_exception($debug) exception&lt;br /&gt;
throw new invalid_parameter_exception(&#039;Group with the same name already exists in the course&#039;); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Correct return values ===&lt;br /&gt;
The return values will be validated by the Moodle web service servers: &lt;br /&gt;
* return values contain some values not described =&amp;gt; these values will be skipped.&lt;br /&gt;
* return values miss some required values (VALUE_REQUIRED) =&amp;gt; the server will return an error.&lt;br /&gt;
* return values types don&#039;t match the description (int != PARAM_ALPHA) =&amp;gt; the server will return an error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; cast all your returned objects into arrays.&lt;br /&gt;
&lt;br /&gt;
== Making web service accessible through Apache Thrift ==&lt;br /&gt;
This step is optional. If you wish to generate SDK for different programming languages and platforms using [http://thrift.apache.org Apache Thrift framework] then [https://bitbucket.org/hhteam/moodle_thrift_tools/wiki/Home Moodle Thrift tools] can help you. &lt;br /&gt;
&lt;br /&gt;
Two steps should be performed: &lt;br /&gt;
&lt;br /&gt;
* Generate .thrift files for Moodle API using thriftgenerator script.&lt;br /&gt;
* Generate thrift handlers for PHP and copy the php files generated to your plugin source tree.&lt;br /&gt;
&lt;br /&gt;
It is also recommended to include thrift files into the distribution of your plugin in order to simplify creation of client bindings for the users of your API.&lt;br /&gt;
&lt;br /&gt;
That&#039;s it. Now the web API of your plugin is accessible through Apache Thrift Framework.&lt;br /&gt;
&lt;br /&gt;
== Bump the plugin version ==&lt;br /&gt;
Edit your local/myplugin/version.php and increase the plugin version. This should trigger a Moodle upgrade and the new web service should be available in the administration (&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web Services &amp;gt; Manage services&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Deprecation ==&lt;br /&gt;
External functions deprecation process is different from the [[Deprecation|standard deprecation]]. If you are interested in deprecating any of your external functions you should create a FUNCTIONNAME_is_deprecated function in your external function class. Return true if the external function is deprecated. This is an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Mark the function as deprecated.&lt;br /&gt;
     * @return bool&lt;br /&gt;
     */&lt;br /&gt;
    public static function create_groups_is_deprecated() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
=See also=&lt;br /&gt;
* [[Web_services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
* [[Creating_a_web_service_client|Implement a web service client]]&lt;br /&gt;
&lt;br /&gt;
Specification:&lt;br /&gt;
* [[External services security]]&lt;br /&gt;
* [[External services description]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web Services]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=55527</id>
		<title>File API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=55527"/>
		<updated>2019-01-30T09:55:22Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Serving files to users */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is for managing all the files stored by Moodle. If you are interested in how the file API works internally, see [[File API internals]]. The page is just about what you need to know to use the file API. Related is the [[Repository API]], which lets users get files into Moodle.&lt;br /&gt;
&lt;br /&gt;
If you are looking for an explanation on how to manage moodle files in moodle forms, you most likely need to read [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]].&lt;br /&gt;
&lt;br /&gt;
==File areas==&lt;br /&gt;
&lt;br /&gt;
Files are conceptually stored in &#039;&#039;&#039;file areas&#039;&#039;&#039;. A file area is uniquely identified by:&lt;br /&gt;
* A context id.&lt;br /&gt;
* full component name (using [[Frankenstyle]]), for example &#039;course&#039;, &#039;mod_forum&#039;, &#039;mod_glossary&#039;, &#039;block_html&#039;.&lt;br /&gt;
* A file area type, for example &#039;intro&#039; or &#039;post&#039;.&lt;br /&gt;
* A unique itemid. Normally, the itemid relates to something depending on the file area type. For example, for a &#039;course&#039;, &#039;intro&#039; file area, the itemid is 0. For forum post, it is the post id.&lt;br /&gt;
&lt;br /&gt;
File areas are not listed separately anywhere, they are stored implicitly in the files table. Please note that each subsystem is allowed to access only own file areas, for example only code in /mod/assignment/* may access files with component &#039;mod_assignment&#039;.&lt;br /&gt;
&lt;br /&gt;
===Naming file areas===&lt;br /&gt;
&lt;br /&gt;
The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas if possible (intro, post, attachment, description, ...).&lt;br /&gt;
&lt;br /&gt;
==Serving files to users==&lt;br /&gt;
&lt;br /&gt;
You must refer to the file with a URL that includes a file-serving script, often pluginfile.php. For example&lt;br /&gt;
&lt;br /&gt;
The general form of the URL is something like&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/infomation.ext&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A specific example might be&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Usually, you do not need to construct this URL directly - the function moodle_url::make_pluginfile_url() should be used instead:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = moodle_url::make_pluginfile_url($file-&amp;gt;get_contextid(), $file-&amp;gt;get_component(), $file-&amp;gt;get_filearea(), $file-&amp;gt;get_itemid(), $file-&amp;gt;get_filepath(), $file-&amp;gt;get_filename(), false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note: If you do not need the &#039;itemid&#039;, then pass null in as this parameter and it will be entirely missed out from the URL - you need to take this into account when serving the file in the callback function, below.&lt;br /&gt;
&lt;br /&gt;
The final parameter (&#039;false&#039; here) is &#039;forcedownload&#039;.&lt;br /&gt;
&lt;br /&gt;
The file serving script then looks at the context id, and component name, and the file area name, and based on that arranges for the file to be served, following appropriate security checks.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;strong&amp;gt;Note: &amp;lt;/strong&amp;gt;In most cases, when developing third party plugins, pluginfile.php looks for a &#039;&#039;&#039;callback function&#039;&#039;&#039; in the appropriate plugin. These functions are stored in &#039;&#039;&#039;lib.php&#039;&#039;&#039; files and are named &#039;&#039;&#039;component_name_pluginfile()&#039;&#039;&#039;. The arbitrary/extra/infomation.ext is passed to the callback. For example, files in the mod_forum+post file area end up being served by the mod_forum_pluginfile function in mod/forum/lib.php. This function in MYPLUGIN/lib.php will usually follow a pattern like the example below, but the details will vary depending on the restrictions your plugin places on accessing different files (e.g. assignment files can only be accessed by teachers and the student who submitted the file, forum attachments require access to the discussion they are posted on):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Serve the files from the MYPLUGIN file areas&lt;br /&gt;
 *&lt;br /&gt;
 * @param stdClass $course the course object&lt;br /&gt;
 * @param stdClass $cm the course module object&lt;br /&gt;
 * @param stdClass $context the context&lt;br /&gt;
 * @param string $filearea the name of the file area&lt;br /&gt;
 * @param array $args extra arguments (itemid, path)&lt;br /&gt;
 * @param bool $forcedownload whether or not force download&lt;br /&gt;
 * @param array $options additional options affecting the file serving&lt;br /&gt;
 * @return bool false if the file not found, just send the file otherwise and do not return anything&lt;br /&gt;
 */&lt;br /&gt;
function MYPLUGIN_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {&lt;br /&gt;
    // Check the contextlevel is as expected - if your plugin is a block, this becomes CONTEXT_BLOCK, etc.&lt;br /&gt;
    if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return false; &lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Make sure the filearea is one of those used by the plugin.&lt;br /&gt;
    if ($filearea !== &#039;expectedfilearea&#039; &amp;amp;&amp;amp; $filearea !== &#039;anotherexpectedfilearea&#039;) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Make sure the user is logged in and has access to the module (plugins that are not course modules should leave out the &#039;cm&#039; part).&lt;br /&gt;
    require_login($course, true, $cm);&lt;br /&gt;
&lt;br /&gt;
    // Check the relevant capabilities - these may vary depending on the filearea being accessed.&lt;br /&gt;
    if (!has_capability(&#039;mod/MYPLUGIN:view&#039;, $context)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Leave this line out if you set the itemid to null in make_pluginfile_url (set $itemid to 0 instead).&lt;br /&gt;
    $itemid = array_shift($args); // The first item in the $args array.&lt;br /&gt;
    &lt;br /&gt;
    // Use the itemid to retrieve any relevant data records and perform any security checks to see if the&lt;br /&gt;
    // user really does have access to the file in question.&lt;br /&gt;
&lt;br /&gt;
    // Extract the filename / filepath from the $args array.&lt;br /&gt;
    $filename = array_pop($args); // The last item in the $args array.&lt;br /&gt;
    if (!$args) {&lt;br /&gt;
        $filepath = &#039;/&#039;; // $args is empty =&amp;gt; the path is &#039;/&#039;&lt;br /&gt;
    } else {&lt;br /&gt;
        $filepath = &#039;/&#039;.implode(&#039;/&#039;, $args).&#039;/&#039;; // $args contains elements of the filepath&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Retrieve the file from the Files API.&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
    $file = $fs-&amp;gt;get_file($context-&amp;gt;id, &#039;mod_MYPLUGIN&#039;, $filearea, $itemid, $filepath, $filename);&lt;br /&gt;
    if (!$file) {&lt;br /&gt;
        return false; // The file does not exist.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. &lt;br /&gt;
    send_stored_file($file, 86400, 0, $forcedownload, $options);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You normally use an API function to generate these URL automatically, most often the &amp;lt;tt&amp;gt;file_rewrite_pluginfile_urls&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
==Getting files from the user==&lt;br /&gt;
&lt;br /&gt;
* See [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
Please note that in reality developers outside of core will not deal with file api directly in majority of cases, instead use formslib elements which are doing all this automatically.&lt;br /&gt;
&lt;br /&gt;
===Browsing files===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$browser = get_file_browser();&lt;br /&gt;
$context = get_system_context();&lt;br /&gt;
&lt;br /&gt;
$filearea = null;&lt;br /&gt;
$itemid   = null;&lt;br /&gt;
$filename = null;&lt;br /&gt;
if ($fileinfo = $browser-&amp;gt;get_file_info($context, $component, $filearea, $itemid, &#039;/&#039;, $filename)) {&lt;br /&gt;
    // build a Breadcrumb trail&lt;br /&gt;
    $level = $fileinfo-&amp;gt;get_parent();&lt;br /&gt;
    while ($level) {&lt;br /&gt;
        $path[] = array(&#039;name&#039;=&amp;gt;$level-&amp;gt;get_visible_name());&lt;br /&gt;
        $level = $level-&amp;gt;get_parent();&lt;br /&gt;
    }&lt;br /&gt;
    $path = array_reverse($path);&lt;br /&gt;
    $children = $fileinfo-&amp;gt;get_children();&lt;br /&gt;
    foreach ($children as $child) {&lt;br /&gt;
        if ($child-&amp;gt;is_directory()) {&lt;br /&gt;
            echo $child-&amp;gt;get_visible_name();&lt;br /&gt;
            // display contextid, itemid, component, filepath and filename&lt;br /&gt;
            var_dump($child-&amp;gt;get_params());&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;
===Moving files around===&lt;br /&gt;
&lt;br /&gt;
For example, if you have just built a file at the path&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $from_zip_file = $CFG-&amp;gt;dataroot . &#039;/temp/backup/&#039; . $preferences-&amp;gt;backup_unique_code .&lt;br /&gt;
         &#039;/&#039; . $preferences-&amp;gt;backup_name;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And you want to move it into the course_backup file area, do&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $context = get_context_instance(CONTEXT_COURSE, $preferences-&amp;gt;backup_course);&lt;br /&gt;
 $fs = get_file_storage();&lt;br /&gt;
 $file_record = array(&#039;contextid&#039;=&amp;gt;$context-&amp;gt;id, &#039;component&#039;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;backup&#039;,&lt;br /&gt;
         &#039;itemid&#039;=&amp;gt;0, &#039;filepath&#039;=&amp;gt;&#039;/&#039;, &#039;filename&#039;=&amp;gt;$preferences-&amp;gt;backup_name,&lt;br /&gt;
         &#039;timecreated&#039;=&amp;gt;time(), &#039;timemodified&#039;=&amp;gt;time());&lt;br /&gt;
 $fs-&amp;gt;create_file_from_pathname($file_record, $from_zip_file);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== List area files ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
foreach ($files as $f) {&lt;br /&gt;
    // $f is an instance of stored_file&lt;br /&gt;
    echo $f-&amp;gt;get_filename();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or as links...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$out = array();&lt;br /&gt;
        &lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
            &lt;br /&gt;
foreach ($files as $file) {&lt;br /&gt;
    $filename = $file-&amp;gt;get_filename();&lt;br /&gt;
    $url = moodle_url::make_file_url(&#039;/pluginfile.php&#039;, array($file-&amp;gt;get_contextid(), &#039;mod_assignment&#039;, &#039;submission&#039;,&lt;br /&gt;
            $file-&amp;gt;get_itemid(), $file-&amp;gt;get_filepath(), $filename));&lt;br /&gt;
    $out[] = html_writer::link($url, $filename);&lt;br /&gt;
}&lt;br /&gt;
$br = html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
        &lt;br /&gt;
return implode($br, $out);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create file ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s how to create a file whose contents will be a text string. This is the equivalent of the PHP function &amp;lt;tt&amp;gt;file_put_contents&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Create file containing text &#039;hello world&#039;&lt;br /&gt;
$fs-&amp;gt;create_file_from_string($fileinfo, &#039;hello world&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to create a file in the Moodle file area based on a &#039;real&#039; file e.g. in a temporary folder, you can use &amp;lt;tt&amp;gt;create_file_from_pathname&amp;lt;/tt&amp;gt; instead. Similarly, you can create a file based on some other file already in Moodle&#039;s local files by using &amp;lt;tt&amp;gt;create_file_from_storedfile&amp;lt;/tt&amp;gt;. Browse through &amp;lt;tt&amp;gt;lib/filestorage/file_storage.php&amp;lt;/tt&amp;gt; for details. &lt;br /&gt;
&lt;br /&gt;
Unlike with ordinary files, this method will not automatically overwrite an existing file. If you wish to overwrite a file, you must first get the file and (if it exists) delete it, and only then create it again.&lt;br /&gt;
&lt;br /&gt;
=== Read file ===&lt;br /&gt;
&lt;br /&gt;
This is a way to read a file, equivalent to &amp;lt;tt&amp;gt;file_get_contents&amp;lt;/tt&amp;gt;. &#039;&#039;&#039;Please note your are allowed to do this ONLY from mod/mymodule/* code, it is not acceptable to do this anywhere else.&#039;&#039;&#039; Other code has to use file_browser interface instead.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo[&#039;contextid&#039;], $fileinfo[&#039;component&#039;], $fileinfo[&#039;filearea&#039;],&lt;br /&gt;
                      $fileinfo[&#039;itemid&#039;], $fileinfo[&#039;filepath&#039;], $fileinfo[&#039;filename&#039;]);&lt;br /&gt;
&lt;br /&gt;
// Read contents&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $contents = $file-&amp;gt;get_content();&lt;br /&gt;
} else {&lt;br /&gt;
    // file doesn&#039;t exist - do something&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to access the file directly on disk, this is not permitted. Instead, you need to make a copy of the file in a temporary area and use that. You can do this with &amp;lt;tt&amp;gt;$file-&amp;gt;copy_content_to($pathname)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Delete file ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo[&#039;contextid&#039;], $fileinfo[&#039;component&#039;], $fileinfo[&#039;filearea&#039;], &lt;br /&gt;
        $fileinfo[&#039;itemid&#039;], $fileinfo[&#039;filepath&#039;], $fileinfo[&#039;filename&#039;]);&lt;br /&gt;
&lt;br /&gt;
// Delete it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $file-&amp;gt;delete();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert between file formats (office documents) ===&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
This functionality requires &amp;quot;unoconv&amp;quot; to be installed and configured on the site - so it is not available on all installations.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo[&#039;contextid&#039;], $fileinfo[&#039;component&#039;], $fileinfo[&#039;filearea&#039;], &lt;br /&gt;
        $fileinfo[&#039;itemid&#039;], $fileinfo[&#039;filepath&#039;], $fileinfo[&#039;filename&#039;]);&lt;br /&gt;
&lt;br /&gt;
// Try and convert it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $convertedfile = $fs-&amp;gt;get_converted_document($file, &#039;pdf&#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;
* [[File API internals]] how the File API works internally.&lt;br /&gt;
* [[Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=55525</id>
		<title>File API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=55525"/>
		<updated>2019-01-29T16:13:06Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Serving files to users */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is for managing all the files stored by Moodle. If you are interested in how the file API works internally, see [[File API internals]]. The page is just about what you need to know to use the file API. Related is the [[Repository API]], which lets users get files into Moodle.&lt;br /&gt;
&lt;br /&gt;
If you are looking for an explanation on how to manage moodle files in moodle forms, you most likely need to read [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]].&lt;br /&gt;
&lt;br /&gt;
==File areas==&lt;br /&gt;
&lt;br /&gt;
Files are conceptually stored in &#039;&#039;&#039;file areas&#039;&#039;&#039;. A file area is uniquely identified by:&lt;br /&gt;
* A context id.&lt;br /&gt;
* full component name (using [[Frankenstyle]]), for example &#039;course&#039;, &#039;mod_forum&#039;, &#039;mod_glossary&#039;, &#039;block_html&#039;.&lt;br /&gt;
* A file area type, for example &#039;intro&#039; or &#039;post&#039;.&lt;br /&gt;
* A unique itemid. Normally, the itemid relates to something depending on the file area type. For example, for a &#039;course&#039;, &#039;intro&#039; file area, the itemid is 0. For forum post, it is the post id.&lt;br /&gt;
&lt;br /&gt;
File areas are not listed separately anywhere, they are stored implicitly in the files table. Please note that each subsystem is allowed to access only own file areas, for example only code in /mod/assignment/* may access files with component &#039;mod_assignment&#039;.&lt;br /&gt;
&lt;br /&gt;
===Naming file areas===&lt;br /&gt;
&lt;br /&gt;
The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas if possible (intro, post, attachment, description, ...).&lt;br /&gt;
&lt;br /&gt;
==Serving files to users==&lt;br /&gt;
&lt;br /&gt;
You must refer to the file with a URL that includes a file-serving script, often pluginfile.php. For example&lt;br /&gt;
&lt;br /&gt;
The general form of the URL is something like&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/infomation.ext&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A specific example might be&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Usually, you do not need to construct this URL directly - the function moodle_url::make_pluginfile_url() should be used instead:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = moodle_url::make_pluginfile_url($file-&amp;gt;get_contextid(), $file-&amp;gt;get_component(), $file-&amp;gt;get_filearea(), $file-&amp;gt;get_itemid(), $file-&amp;gt;get_filepath(), $file-&amp;gt;get_filename());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note: If you do not need the &#039;itemid&#039;, then pass null in as this parameter and it will be entirely missed out from the URL - you need to take this into account when serving the file in the callback function, below.&lt;br /&gt;
&lt;br /&gt;
The file serving script then looks at the context id, and component name, and the file area name, and based on that arranges for the file to be served, following appropriate security checks.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;strong&amp;gt;Note: &amp;lt;/strong&amp;gt;In most cases, when developing third party plugins, pluginfile.php looks for a &#039;&#039;&#039;callback function&#039;&#039;&#039; in the appropriate plugin. These functions are stored in &#039;&#039;&#039;lib.php&#039;&#039;&#039; files and are named &#039;&#039;&#039;component_name_pluginfile()&#039;&#039;&#039;. The arbitrary/extra/infomation.ext is passed to the callback. For example, files in the mod_forum+post file area end up being served by the mod_forum_pluginfile function in mod/forum/lib.php. This function in MYPLUGIN/lib.php will usually follow a pattern like the example below, but the details will vary depending on the restrictions your plugin places on accessing different files (e.g. assignment files can only be accessed by teachers and the student who submitted the file, forum attachments require access to the discussion they are posted on):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Serve the files from the MYPLUGIN file areas&lt;br /&gt;
 *&lt;br /&gt;
 * @param stdClass $course the course object&lt;br /&gt;
 * @param stdClass $cm the course module object&lt;br /&gt;
 * @param stdClass $context the context&lt;br /&gt;
 * @param string $filearea the name of the file area&lt;br /&gt;
 * @param array $args extra arguments (itemid, path)&lt;br /&gt;
 * @param bool $forcedownload whether or not force download&lt;br /&gt;
 * @param array $options additional options affecting the file serving&lt;br /&gt;
 * @return bool false if the file not found, just send the file otherwise and do not return anything&lt;br /&gt;
 */&lt;br /&gt;
function MYPLUGIN_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {&lt;br /&gt;
    // Check the contextlevel is as expected - if your plugin is a block, this becomes CONTEXT_BLOCK, etc.&lt;br /&gt;
    if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return false; &lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Make sure the filearea is one of those used by the plugin.&lt;br /&gt;
    if ($filearea !== &#039;expectedfilearea&#039; &amp;amp;&amp;amp; $filearea !== &#039;anotherexpectedfilearea&#039;) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Make sure the user is logged in and has access to the module (plugins that are not course modules should leave out the &#039;cm&#039; part).&lt;br /&gt;
    require_login($course, true, $cm);&lt;br /&gt;
&lt;br /&gt;
    // Check the relevant capabilities - these may vary depending on the filearea being accessed.&lt;br /&gt;
    if (!has_capability(&#039;mod/MYPLUGIN:view&#039;, $context)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Leave this line out if you set the itemid to null in make_pluginfile_url (set $itemid to 0 instead).&lt;br /&gt;
    $itemid = array_shift($args); // The first item in the $args array.&lt;br /&gt;
    &lt;br /&gt;
    // Use the itemid to retrieve any relevant data records and perform any security checks to see if the&lt;br /&gt;
    // user really does have access to the file in question.&lt;br /&gt;
&lt;br /&gt;
    // Extract the filename / filepath from the $args array.&lt;br /&gt;
    $filename = array_pop($args); // The last item in the $args array.&lt;br /&gt;
    if (!$args) {&lt;br /&gt;
        $filepath = &#039;/&#039;; // $args is empty =&amp;gt; the path is &#039;/&#039;&lt;br /&gt;
    } else {&lt;br /&gt;
        $filepath = &#039;/&#039;.implode(&#039;/&#039;, $args).&#039;/&#039;; // $args contains elements of the filepath&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Retrieve the file from the Files API.&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
    $file = $fs-&amp;gt;get_file($context-&amp;gt;id, &#039;mod_MYPLUGIN&#039;, $filearea, $itemid, $filepath, $filename);&lt;br /&gt;
    if (!$file) {&lt;br /&gt;
        return false; // The file does not exist.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. &lt;br /&gt;
    send__stored_file($file, 86400, 0, $forcedownload, $options);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You normally use an API function to generate these URL automatically, most often the &amp;lt;tt&amp;gt;file_rewrite_pluginfile_urls&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
==Getting files from the user==&lt;br /&gt;
&lt;br /&gt;
* See [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
Please note that in reality developers outside of core will not deal with file api directly in majority of cases, instead use formslib elements which are doing all this automatically.&lt;br /&gt;
&lt;br /&gt;
===Browsing files===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$browser = get_file_browser();&lt;br /&gt;
$context = get_system_context();&lt;br /&gt;
&lt;br /&gt;
$filearea = null;&lt;br /&gt;
$itemid   = null;&lt;br /&gt;
$filename = null;&lt;br /&gt;
if ($fileinfo = $browser-&amp;gt;get_file_info($context, $component, $filearea, $itemid, &#039;/&#039;, $filename)) {&lt;br /&gt;
    // build a Breadcrumb trail&lt;br /&gt;
    $level = $fileinfo-&amp;gt;get_parent();&lt;br /&gt;
    while ($level) {&lt;br /&gt;
        $path[] = array(&#039;name&#039;=&amp;gt;$level-&amp;gt;get_visible_name());&lt;br /&gt;
        $level = $level-&amp;gt;get_parent();&lt;br /&gt;
    }&lt;br /&gt;
    $path = array_reverse($path);&lt;br /&gt;
    $children = $fileinfo-&amp;gt;get_children();&lt;br /&gt;
    foreach ($children as $child) {&lt;br /&gt;
        if ($child-&amp;gt;is_directory()) {&lt;br /&gt;
            echo $child-&amp;gt;get_visible_name();&lt;br /&gt;
            // display contextid, itemid, component, filepath and filename&lt;br /&gt;
            var_dump($child-&amp;gt;get_params());&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;
===Moving files around===&lt;br /&gt;
&lt;br /&gt;
For example, if you have just built a file at the path&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $from_zip_file = $CFG-&amp;gt;dataroot . &#039;/temp/backup/&#039; . $preferences-&amp;gt;backup_unique_code .&lt;br /&gt;
         &#039;/&#039; . $preferences-&amp;gt;backup_name;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And you want to move it into the course_backup file area, do&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $context = get_context_instance(CONTEXT_COURSE, $preferences-&amp;gt;backup_course);&lt;br /&gt;
 $fs = get_file_storage();&lt;br /&gt;
 $file_record = array(&#039;contextid&#039;=&amp;gt;$context-&amp;gt;id, &#039;component&#039;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;backup&#039;,&lt;br /&gt;
         &#039;itemid&#039;=&amp;gt;0, &#039;filepath&#039;=&amp;gt;&#039;/&#039;, &#039;filename&#039;=&amp;gt;$preferences-&amp;gt;backup_name,&lt;br /&gt;
         &#039;timecreated&#039;=&amp;gt;time(), &#039;timemodified&#039;=&amp;gt;time());&lt;br /&gt;
 $fs-&amp;gt;create_file_from_pathname($file_record, $from_zip_file);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== List area files ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
foreach ($files as $f) {&lt;br /&gt;
    // $f is an instance of stored_file&lt;br /&gt;
    echo $f-&amp;gt;get_filename();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or as links...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$out = array();&lt;br /&gt;
        &lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
            &lt;br /&gt;
foreach ($files as $file) {&lt;br /&gt;
    $filename = $file-&amp;gt;get_filename();&lt;br /&gt;
    $url = moodle_url::make_file_url(&#039;/pluginfile.php&#039;, array($file-&amp;gt;get_contextid(), &#039;mod_assignment&#039;, &#039;submission&#039;,&lt;br /&gt;
            $file-&amp;gt;get_itemid(), $file-&amp;gt;get_filepath(), $filename));&lt;br /&gt;
    $out[] = html_writer::link($url, $filename);&lt;br /&gt;
}&lt;br /&gt;
$br = html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
        &lt;br /&gt;
return implode($br, $out);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create file ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s how to create a file whose contents will be a text string. This is the equivalent of the PHP function &amp;lt;tt&amp;gt;file_put_contents&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Create file containing text &#039;hello world&#039;&lt;br /&gt;
$fs-&amp;gt;create_file_from_string($fileinfo, &#039;hello world&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to create a file in the Moodle file area based on a &#039;real&#039; file e.g. in a temporary folder, you can use &amp;lt;tt&amp;gt;create_file_from_pathname&amp;lt;/tt&amp;gt; instead. Similarly, you can create a file based on some other file already in Moodle&#039;s local files by using &amp;lt;tt&amp;gt;create_file_from_storedfile&amp;lt;/tt&amp;gt;. Browse through &amp;lt;tt&amp;gt;lib/filestorage/file_storage.php&amp;lt;/tt&amp;gt; for details. &lt;br /&gt;
&lt;br /&gt;
Unlike with ordinary files, this method will not automatically overwrite an existing file. If you wish to overwrite a file, you must first get the file and (if it exists) delete it, and only then create it again.&lt;br /&gt;
&lt;br /&gt;
=== Read file ===&lt;br /&gt;
&lt;br /&gt;
This is a way to read a file, equivalent to &amp;lt;tt&amp;gt;file_get_contents&amp;lt;/tt&amp;gt;. &#039;&#039;&#039;Please note your are allowed to do this ONLY from mod/mymodule/* code, it is not acceptable to do this anywhere else.&#039;&#039;&#039; Other code has to use file_browser interface instead.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo[&#039;contextid&#039;], $fileinfo[&#039;component&#039;], $fileinfo[&#039;filearea&#039;],&lt;br /&gt;
                      $fileinfo[&#039;itemid&#039;], $fileinfo[&#039;filepath&#039;], $fileinfo[&#039;filename&#039;]);&lt;br /&gt;
&lt;br /&gt;
// Read contents&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $contents = $file-&amp;gt;get_content();&lt;br /&gt;
} else {&lt;br /&gt;
    // file doesn&#039;t exist - do something&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to access the file directly on disk, this is not permitted. Instead, you need to make a copy of the file in a temporary area and use that. You can do this with &amp;lt;tt&amp;gt;$file-&amp;gt;copy_content_to($pathname)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Delete file ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo[&#039;contextid&#039;], $fileinfo[&#039;component&#039;], $fileinfo[&#039;filearea&#039;], &lt;br /&gt;
        $fileinfo[&#039;itemid&#039;], $fileinfo[&#039;filepath&#039;], $fileinfo[&#039;filename&#039;]);&lt;br /&gt;
&lt;br /&gt;
// Delete it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $file-&amp;gt;delete();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert between file formats (office documents) ===&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
This functionality requires &amp;quot;unoconv&amp;quot; to be installed and configured on the site - so it is not available on all installations.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo[&#039;contextid&#039;], $fileinfo[&#039;component&#039;], $fileinfo[&#039;filearea&#039;], &lt;br /&gt;
        $fileinfo[&#039;itemid&#039;], $fileinfo[&#039;filepath&#039;], $fileinfo[&#039;filename&#039;]);&lt;br /&gt;
&lt;br /&gt;
// Try and convert it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $convertedfile = $fs-&amp;gt;get_converted_document($file, &#039;pdf&#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;
* [[File API internals]] how the File API works internally.&lt;br /&gt;
* [[Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55472</id>
		<title>Using the File API in Moodle forms</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55472"/>
		<updated>2019-01-18T11:44:28Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Element preparation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 all files are stored in a central database accessible via the [[File API|File API]], and every file is associated with a component and a &amp;quot;file area&amp;quot; in Moodle, such as a particular module.&lt;br /&gt;
&lt;br /&gt;
A common use case is to provide a form (using Moodle&#039;s [[lib/formslib.php|Forms API]]) which allows users to upload or import files as attachments or media embedded into HTML.&lt;br /&gt;
&lt;br /&gt;
Normally this works like this:&lt;br /&gt;
# User starts creation or re-edits an existing item in Moodle (eg forum post, resource, glossary entry etc)&lt;br /&gt;
# User presses some sort of button to browse for new files to attach or embed&lt;br /&gt;
# User sees our &amp;quot;Choose file...&amp;quot; dialog, which contains one or more repository instances. &lt;br /&gt;
# User chooses a file, the corresponding [[Repository plugins|Repository plugin]] takes care of copying the file into a &amp;quot;draft file area&amp;quot; within Moodle&lt;br /&gt;
# File appears in the text or as an attachment in the form.&lt;br /&gt;
# When the user hits save, the [[File API|File API]] is invoked to move the file from the draft file area into a permanent file area associated with that data &lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to interact with users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
If you just want to write code to manipulate Moodle files internally (without user input) then see [[File API]].&lt;br /&gt;
&lt;br /&gt;
==Form elements== &lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 there are three file-related form elements for interacting with users:&lt;br /&gt;
&lt;br /&gt;
# filemanager - the way to attach one or more files as a set&lt;br /&gt;
# editor - the way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML&lt;br /&gt;
# filepicker - a way to specify one file for the case when you want to process the file and throw it away &lt;br /&gt;
&lt;br /&gt;
In Moodle 1.9 there were two other types which are now &#039;&#039;&#039;deprecated&#039;&#039;&#039; (they work, but please do not use these anymore)&lt;br /&gt;
# file - used to just allow a normal file upload from the desktop only.&lt;br /&gt;
# htmleditor - this old method of embedding a HTML editor in a textarea is not able to support repositories etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
&lt;br /&gt;
File picker (&#039;&#039;filepicker&#039;&#039;) is a direct replacement of the older &#039;&#039;file&#039;&#039; formslib element. &lt;br /&gt;
&lt;br /&gt;
It is intended for situations when you want the user to upload &#039;&#039;&#039;one&#039;&#039;&#039; file so you can process it and delete it, such as when you are importing data from a CSV file.&lt;br /&gt;
If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).&lt;br /&gt;
&lt;br /&gt;
==== Using the filepicker element ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null,&lt;br /&gt;
                   array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Obtain the chosen file ====&lt;br /&gt;
&lt;br /&gt;
To get the contents of the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$content = $mform-&amp;gt;get_file_content(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the name of the chosen file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$name = $mform-&amp;gt;get_new_filename(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To save the chosen file to the server filesystem (such as to moodledata folder):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$success = $mform-&amp;gt;save_file(&#039;userfile&#039;, $fullpath, $override);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To store the chosen file in the Moodle files pool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$storedfile = $mform-&amp;gt;save_stored_file(&#039;userfile&#039;, ...);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== filemanager ===&lt;br /&gt;
&lt;br /&gt;
The File Manager element improves on file picker by allowing you to manage more than one file.  It is expected that the files will be stored permanently for future use (such as forum and glossary attachments).&lt;br /&gt;
&lt;br /&gt;
==== Add file manager element ====&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachments&#039;, get_string(&#039;attachment&#039;, &#039;moodle&#039;), null,&lt;br /&gt;
                    array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;areamaxbytes&#039; =&amp;gt; 10485760, &#039;maxfiles&#039; =&amp;gt; 50,&lt;br /&gt;
                          &#039;accepted_types&#039; =&amp;gt; array(&#039;document&#039;), &#039;return_types&#039;=&amp;gt; FILE_INTERNAL | FILE_EXTERNAL));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the fields for filemanager:&lt;br /&gt;
&lt;br /&gt;
;&#039;filemanager&#039;:This is a filemanager element :)&lt;br /&gt;
;elementname:The unique name of the element in the form&lt;br /&gt;
;elementlabel:The label string that users see &lt;br /&gt;
;attributes:(leave it as null)&lt;br /&gt;
;options: an array of further options for the filepicker (see below)&lt;br /&gt;
&lt;br /&gt;
The options array can contain:&lt;br /&gt;
&lt;br /&gt;
;subdirs:(Default 1) Are subdirectories allowed?  (true or false)&lt;br /&gt;
;maxbytes:(Default 0) Restricts the size of each individual file.&lt;br /&gt;
;areamaxbytes:(Default 0) Restricts the total size of all the files.&lt;br /&gt;
;maxfiles:(Default -1) Restricts the total number of files.&lt;br /&gt;
;accepted_types:(Default *) You can specify what file types are accepted by filemanager.  All current file types are listed in this file: [http://cvs.moodle.org/moodle/lib/filestorage/file_types.mm moodle/lib/filestorage/file_types.mm].  This is a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file: if it is edited the changes will be immediately reflected in Moodle.&lt;br /&gt;
&lt;br /&gt;
As of 2.3 file_types.mm is no longer used, instead the file types are listed in &amp;lt;code&amp;gt;get_mimetypes_array()&amp;lt;/code&amp;gt; in lib/classes/filetypes.php (https://github.com/moodle/moodle/blob/master/lib/classes/filetypes.php).&lt;br /&gt;
&lt;br /&gt;
Example usage:  &#039;&#039;&#039;array(&#039;audio&#039;, &#039;video&#039;, &#039;document&#039;)&#039;&#039;&#039;, you can include file extensions as well, for example: &#039;&#039;&#039;array(&#039;.txt&#039;, &#039;.jpg&#039;, &#039;audio&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Load existing files into draft area ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
    $entry = new stdClass;&lt;br /&gt;
    $entry-&amp;gt;id = null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftitemid = file_get_submitted_draft_itemid(&#039;attachments&#039;);&lt;br /&gt;
&lt;br /&gt;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id,&lt;br /&gt;
                        array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
&lt;br /&gt;
$entry-&amp;gt;attachments = $draftitemid;&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Store updated set of files ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // ... store or update $entry&lt;br /&gt;
    file_save_draft_area_files($data-&amp;gt;attachments, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;,&lt;br /&gt;
                   $entry-&amp;gt;id, array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===editor===&lt;br /&gt;
There are two ways of using the editor element in code, the first one is easier but expects some standardized fields. The second method is more low level.&lt;br /&gt;
&lt;br /&gt;
====Simple use====&lt;br /&gt;
# name database fields: &#039;&#039;textfield&#039;&#039;, &#039;&#039;textfieldformat&#039;&#039; (and &#039;&#039;textfieldtrust&#039;&#039; if required)&lt;br /&gt;
# create options array.  note that context is the best, most local context you have available.&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$textfieldoptions = array(&#039;trusttext&#039;=&amp;gt;true, &#039;subdirs&#039;=&amp;gt;true, &#039;maxfiles&#039;=&amp;gt;$maxfiles, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;context&#039;=&amp;gt;$context);&amp;lt;/code&amp;gt;&lt;br /&gt;
# add editor &#039;&#039;textfield_editor&#039;&#039; to moodle form, pass options through custom data in form constructor, set $data-&amp;gt;id to null if data not exist yet&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;textfield_editor&#039;, get_string(&#039;fieldname&#039;, &#039;somemodule&#039;), null, $textfieldoptions);&amp;lt;/code&amp;gt;&lt;br /&gt;
# prepare data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_prepare_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
# get submitted data and after inserting/updating of data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_postupdate_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Real world examples are in mod/glossary/edit.php and mod/glossary/comment.php&lt;br /&gt;
&lt;br /&gt;
====Low level use====&lt;br /&gt;
&lt;br /&gt;
When using editor element you  need to preprocess and postprocess the data:&lt;br /&gt;
# detect if form was already submitted (usually means draft is area already exists) - &#039;&#039;file_get_submitted_draft_itemid()&#039;&#039;&lt;br /&gt;
# prepare draft file area, temporary storage of all files attached to the text - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# convert encoded relative links to absolute links - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# create form and set current data&lt;br /&gt;
# after submission the changed files must be merged back into original area - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
# absolute links have to be replaced by relative links - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====Replace old htmleditor with editor=====&lt;br /&gt;
&lt;br /&gt;
The file picker has been integrated with with TinyMCE to make the editor element. This new element should support all types on editors and should be able to switch them on-the-fly. Instances of the old htmleditor element in your forms should be replaced by the new editor element, this may need adding of new format and trusttext columns. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;entry&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
        array(&#039;maxfiles&#039; =&amp;gt; EDITOR_UNLIMITED_FILES));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The editor element can take following options: maxfiles, maxbytes, subdirs and changeformat. Please note that the embedded files is optional feature and is not expected be used everywhere.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: the editor element now includes text format option. You should no longer use the separate format element type.&lt;br /&gt;
&lt;br /&gt;
=====Prepare current data - text and files=====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
  $entry = new object();&lt;br /&gt;
  $entry-&amp;gt;id = null;&lt;br /&gt;
  $entry-&amp;gt;definition = &#039;&#039;;&lt;br /&gt;
  $entry-&amp;gt;format = FORMAT_HTML;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftid_editor = file_get_submitted_draft_itemid(&#039;entry&#039;);&lt;br /&gt;
$currenttext = file_prepare_draft_area($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                       $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $entry-&amp;gt;definition);&lt;br /&gt;
$entry-&amp;gt;entry = array(&#039;text&#039;=&amp;gt;$currenttext, &#039;format&#039;=&amp;gt;$entry-&amp;gt;format, &#039;itemid&#039;=&amp;gt;$draftid_editor);&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are multiple files, they will share the same itemid.&lt;br /&gt;
&lt;br /&gt;
=====Obtain text, format and save draft files=====&lt;br /&gt;
&lt;br /&gt;
To retrieve editor content, you need to use following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // content of editor&lt;br /&gt;
    $messagetext = $fromform-&amp;gt;entry[&#039;text&#039;];&lt;br /&gt;
    // format of content&lt;br /&gt;
    $messageformat  = $fromform-&amp;gt;entry[&#039;format&#039;];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.&lt;br /&gt;
&lt;br /&gt;
When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call $DB-&amp;gt;update_record(&#039;tablename&#039;, $data); to have the other parts of the form submission stored correctly.)&lt;br /&gt;
&lt;br /&gt;
The save_files_from_draft_area function and replace absolute links with internal relative links do:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_save_draft_area_files($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                          $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $messagetext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $context-&amp;gt;id, &#039;component&#039;, &#039;proper_file_area&#039; and $entry-&amp;gt;id : correspond to the contextid, filearea and itemid columns in the [[File_API#Table:_files|files table]].&lt;br /&gt;
; $messagetext : this is the message text. As the files are saved to the real file area, the URLs in this content are rewritten.&lt;br /&gt;
&lt;br /&gt;
All URLs in content that point to files managed to the File API are converted to a form that starts &#039;@@PLUGINFILE@@/&#039; before the content is stored in the database. That is what we mean by rewriting.&lt;br /&gt;
&lt;br /&gt;
== File serving==&lt;br /&gt;
&lt;br /&gt;
=== Convert internal relative links to absolute links ===&lt;br /&gt;
&lt;br /&gt;
Before text content is displayed to the user, any URLs in the &#039;@@PLUGINFILE@@/&#039; form in the content need to be rewritten to the real URL where the user can access the files. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_rewrite_pluginfile_urls($messagetext, &#039;pluginfile.php&#039;,&lt;br /&gt;
        $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $messagetext : is the content containing the @@PLUGINFILE@@ URLs from the database.&lt;br /&gt;
; &#039;pluginfile.php&#039; : there are a number of different scripts that can serve files with different permissions checks. You need to specify which one to use.&lt;br /&gt;
; $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid : uniquely identifies the file area, as before.&lt;br /&gt;
&lt;br /&gt;
=== Implement file serving access control ===&lt;br /&gt;
&lt;br /&gt;
Attachments and embedded images should have the same access control like the text itself, in majority of cases these files are served using pluginfile.php. Access control is defined in &#039;&#039;module/lib.php&#039;&#039; file in function &#039;&#039;module_pluginfile()&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== File browsing support ==&lt;br /&gt;
Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.&lt;br /&gt;
&lt;br /&gt;
Activities may specify browsing support in own module/lib.php file by implementing functions module_get_file_areas() and module_get_file_info().&lt;br /&gt;
&lt;br /&gt;
== Upgrading your code ==&lt;br /&gt;
Here I will attempt to describe some simple steps you can take to upgrade your file-handling form elements from pre-2.0 code to 2.0. We will use the example of glossary, since it has been used above.&lt;br /&gt;
&lt;br /&gt;
=== Preparing your options ===&lt;br /&gt;
Unless you are happy with the defaults, you will need to define an array of options for each file-handling form element. You could define it at different places, but it&#039;s best to put it in one place and make the array(s) available to other files if they need it. In the majority of cases, this will be in a file like edit.php&lt;br /&gt;
&lt;br /&gt;
Previous code in mod/glossary/edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform =&amp;amp; new mod_glossary_entry_form(null, compact(&#039;cm&#039;, &#039;glossary&#039;, &#039;hook&#039;, &#039;mode&#039;, &#039;e&#039;, &#039;context&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$maxbytes = $course-&amp;gt;maxbytes;&lt;br /&gt;
// Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&lt;br /&gt;
&lt;br /&gt;
$definitionoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;trusttext&#039;=&amp;gt;true,&lt;br /&gt;
                           &#039;context&#039;=&amp;gt;$context);&lt;br /&gt;
$attachmentoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note that the data being passed to the form constructor have changed also, but this is not part of the file API changes, I just include them to avoid confusion.&lt;br /&gt;
&lt;br /&gt;
These options are for the htmleditor (definition field) and the filemanager (attachment field). They are used by a file called edit_form.php.&lt;br /&gt;
&lt;br /&gt;
=== Element preparation ===&lt;br /&gt;
Before we look at this, however, we need to &amp;quot;prepare&amp;quot; the elements so that they can correctly display existing embedded images and attached files when you are editing a record instead of just creating one. So, let&#039;s take the code we&#039;ve got so far in edit.php and add to it:&lt;br /&gt;
&lt;br /&gt;
Currently upgraded code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&lt;br /&gt;
        &#039;current&#039;=&amp;gt;$entry, &lt;br /&gt;
        &#039;cm&#039;=&amp;gt;$cm, &lt;br /&gt;
        &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
        &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
        &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code with element preparation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$entry = new stdClass();&lt;br /&gt;
$entry-&amp;gt;id = 0; \\ See notes!&lt;br /&gt;
$entry = file_prepare_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                      &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_prepare_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                           &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* $entry in this case is simply a stdClass object which may either represent a new glossary entry or an existing one.&lt;br /&gt;
* $entry-&amp;gt;id must be the unique identifier for the current object. If we are creating a new entry, it will be null, but in all cases it must be defined.&lt;br /&gt;
* These two functions (file_prepare_standard_editor and file_prepare_standard_filemanager) are shortcuts functions that take care of some of the tedious setting up for you, but they make a couple of assumptions:&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; name the form element as {element}_editor or {element}_filemanager (see next section)&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; have at least the following fields in the database: {element} and {element}summary, as described earlier in this documentation&lt;br /&gt;
*# $entry will be populated with a field (in this case) &#039;definition_filemanager&#039; having the draftfileid of your files. This needs to get passed to the &#039;value&#039; of the filemanager control in the form. If you get this wrong, existing file will not appear in the control when it is reloaded.&lt;br /&gt;
&lt;br /&gt;
We can now look at the upgrades needed in the form definition file.&lt;br /&gt;
&lt;br /&gt;
=== Form definition ===&lt;br /&gt;
Previous code in mod/glossary/edit_form.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;htmleditor&#039;, &#039;definition&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;),&lt;br /&gt;
                    array(&#039;rows&#039;=&amp;gt;20));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;definition&#039;, array(&#039;writing&#039;, &#039;richtext&#039;), false, &#039;editorhelpbutton&#039;);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;format&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0,&lt;br /&gt;
                                             true, true, false));&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment&#039;, array(&#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                      &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitionoptions = $this-&amp;gt;_customdata[&#039;definitionoptions&#039;];&lt;br /&gt;
$attachmentoptions = $this-&amp;gt;_customdata[&#039;attachmentoptions&#039;];&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;definition_editor&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
                   $definitionoptions);&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition_editor&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition_editor&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachment_filemanager&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                   null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;,&lt;br /&gt;
                      get_string(&#039;attachment&#039;, &#039;glossary&#039;), &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the following:&lt;br /&gt;
* The format element and the help button are no longer required for the HTML editor element&lt;br /&gt;
* The name of the form element needs to be changed by adding &#039;_editor&#039; or &#039;_manager&#039; to the original name. This is a naming convention that is used by a couple of functions we will look at shortly&lt;br /&gt;
* Make sure $definitionoptions has context parameter, else system context is used and editor will not respect filter settings.&lt;br /&gt;
&lt;br /&gt;
=== Handling submitted data ===&lt;br /&gt;
The final step is to handle the submitted data properly, i.e. retrieve the files and save them to disk, associating them with the record we have just created (a glossary entry in our example). This happens in edit.php:&lt;br /&gt;
&lt;br /&gt;
Previous code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Section that updates an entry:&lt;br /&gt;
$todb-&amp;gt;id = $e;&lt;br /&gt;
$dir = glossary_file_area_name($todb);&lt;br /&gt;
if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
    $todb-&amp;gt;attachment = $newfilename;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Section that adds an entry:&lt;br /&gt;
if ($todb-&amp;gt;id = insert_record(&amp;quot;glossary_entries&amp;quot;, $todb)) {&lt;br /&gt;
    $e = $todb-&amp;gt;id;&lt;br /&gt;
    $dir = glossary_file_area_name($todb);&lt;br /&gt;
    if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
        set_field(&amp;quot;glossary_entries&amp;quot;, &amp;quot;attachment&amp;quot;, $newfilename, &amp;quot;id&amp;quot;, $todb-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// $todb was renamed to $entry, and the code was refactored &lt;br /&gt;
// so that the file-handling code is only used once for either an add or an update action.&lt;br /&gt;
// If an entry is being added, $DB-&amp;gt;insert() has already been called, so we have a valid $entry-&amp;gt;id&lt;br /&gt;
$entry = file_postupdate_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                         &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_postupdate_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                              &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
// store the updated value values&lt;br /&gt;
$DB-&amp;gt;update_record(&#039;glossary_entries&#039;, $entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* If you are adding a new record, you will still need to call update_record after calling the file_postupdate* functions&lt;br /&gt;
&lt;br /&gt;
=== Gotchas ===&lt;br /&gt;
A few things to keep in mind:&lt;br /&gt;
* Make sure that you instantiate the moodle form before any call to $OUTPUT-&amp;gt;header()&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Using the file API]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=207748 Adding a text editor to a Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=157953#p692822 Button actions in Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=351013#p1416554 File Picker]&lt;br /&gt;
* [https://github.com/CARLOSEDUARDOVIEIRA/moodle-uploadfile Example of filemanager in an activity module]&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Repositories]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55471</id>
		<title>Using the File API in Moodle forms</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55471"/>
		<updated>2019-01-18T11:41:54Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Element preparation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 all files are stored in a central database accessible via the [[File API|File API]], and every file is associated with a component and a &amp;quot;file area&amp;quot; in Moodle, such as a particular module.&lt;br /&gt;
&lt;br /&gt;
A common use case is to provide a form (using Moodle&#039;s [[lib/formslib.php|Forms API]]) which allows users to upload or import files as attachments or media embedded into HTML.&lt;br /&gt;
&lt;br /&gt;
Normally this works like this:&lt;br /&gt;
# User starts creation or re-edits an existing item in Moodle (eg forum post, resource, glossary entry etc)&lt;br /&gt;
# User presses some sort of button to browse for new files to attach or embed&lt;br /&gt;
# User sees our &amp;quot;Choose file...&amp;quot; dialog, which contains one or more repository instances. &lt;br /&gt;
# User chooses a file, the corresponding [[Repository plugins|Repository plugin]] takes care of copying the file into a &amp;quot;draft file area&amp;quot; within Moodle&lt;br /&gt;
# File appears in the text or as an attachment in the form.&lt;br /&gt;
# When the user hits save, the [[File API|File API]] is invoked to move the file from the draft file area into a permanent file area associated with that data &lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to interact with users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
If you just want to write code to manipulate Moodle files internally (without user input) then see [[File API]].&lt;br /&gt;
&lt;br /&gt;
==Form elements== &lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 there are three file-related form elements for interacting with users:&lt;br /&gt;
&lt;br /&gt;
# filemanager - the way to attach one or more files as a set&lt;br /&gt;
# editor - the way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML&lt;br /&gt;
# filepicker - a way to specify one file for the case when you want to process the file and throw it away &lt;br /&gt;
&lt;br /&gt;
In Moodle 1.9 there were two other types which are now &#039;&#039;&#039;deprecated&#039;&#039;&#039; (they work, but please do not use these anymore)&lt;br /&gt;
# file - used to just allow a normal file upload from the desktop only.&lt;br /&gt;
# htmleditor - this old method of embedding a HTML editor in a textarea is not able to support repositories etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
&lt;br /&gt;
File picker (&#039;&#039;filepicker&#039;&#039;) is a direct replacement of the older &#039;&#039;file&#039;&#039; formslib element. &lt;br /&gt;
&lt;br /&gt;
It is intended for situations when you want the user to upload &#039;&#039;&#039;one&#039;&#039;&#039; file so you can process it and delete it, such as when you are importing data from a CSV file.&lt;br /&gt;
If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).&lt;br /&gt;
&lt;br /&gt;
==== Using the filepicker element ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null,&lt;br /&gt;
                   array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Obtain the chosen file ====&lt;br /&gt;
&lt;br /&gt;
To get the contents of the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$content = $mform-&amp;gt;get_file_content(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the name of the chosen file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$name = $mform-&amp;gt;get_new_filename(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To save the chosen file to the server filesystem (such as to moodledata folder):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$success = $mform-&amp;gt;save_file(&#039;userfile&#039;, $fullpath, $override);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To store the chosen file in the Moodle files pool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$storedfile = $mform-&amp;gt;save_stored_file(&#039;userfile&#039;, ...);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== filemanager ===&lt;br /&gt;
&lt;br /&gt;
The File Manager element improves on file picker by allowing you to manage more than one file.  It is expected that the files will be stored permanently for future use (such as forum and glossary attachments).&lt;br /&gt;
&lt;br /&gt;
==== Add file manager element ====&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachments&#039;, get_string(&#039;attachment&#039;, &#039;moodle&#039;), null,&lt;br /&gt;
                    array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;areamaxbytes&#039; =&amp;gt; 10485760, &#039;maxfiles&#039; =&amp;gt; 50,&lt;br /&gt;
                          &#039;accepted_types&#039; =&amp;gt; array(&#039;document&#039;), &#039;return_types&#039;=&amp;gt; FILE_INTERNAL | FILE_EXTERNAL));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the fields for filemanager:&lt;br /&gt;
&lt;br /&gt;
;&#039;filemanager&#039;:This is a filemanager element :)&lt;br /&gt;
;elementname:The unique name of the element in the form&lt;br /&gt;
;elementlabel:The label string that users see &lt;br /&gt;
;attributes:(leave it as null)&lt;br /&gt;
;options: an array of further options for the filepicker (see below)&lt;br /&gt;
&lt;br /&gt;
The options array can contain:&lt;br /&gt;
&lt;br /&gt;
;subdirs:(Default 1) Are subdirectories allowed?  (true or false)&lt;br /&gt;
;maxbytes:(Default 0) Restricts the size of each individual file.&lt;br /&gt;
;areamaxbytes:(Default 0) Restricts the total size of all the files.&lt;br /&gt;
;maxfiles:(Default -1) Restricts the total number of files.&lt;br /&gt;
;accepted_types:(Default *) You can specify what file types are accepted by filemanager.  All current file types are listed in this file: [http://cvs.moodle.org/moodle/lib/filestorage/file_types.mm moodle/lib/filestorage/file_types.mm].  This is a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file: if it is edited the changes will be immediately reflected in Moodle.&lt;br /&gt;
&lt;br /&gt;
As of 2.3 file_types.mm is no longer used, instead the file types are listed in &amp;lt;code&amp;gt;get_mimetypes_array()&amp;lt;/code&amp;gt; in lib/classes/filetypes.php (https://github.com/moodle/moodle/blob/master/lib/classes/filetypes.php).&lt;br /&gt;
&lt;br /&gt;
Example usage:  &#039;&#039;&#039;array(&#039;audio&#039;, &#039;video&#039;, &#039;document&#039;)&#039;&#039;&#039;, you can include file extensions as well, for example: &#039;&#039;&#039;array(&#039;.txt&#039;, &#039;.jpg&#039;, &#039;audio&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Load existing files into draft area ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
    $entry = new stdClass;&lt;br /&gt;
    $entry-&amp;gt;id = null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftitemid = file_get_submitted_draft_itemid(&#039;attachments&#039;);&lt;br /&gt;
&lt;br /&gt;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id,&lt;br /&gt;
                        array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
&lt;br /&gt;
$entry-&amp;gt;attachments = $draftitemid;&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Store updated set of files ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // ... store or update $entry&lt;br /&gt;
    file_save_draft_area_files($data-&amp;gt;attachments, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;,&lt;br /&gt;
                   $entry-&amp;gt;id, array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===editor===&lt;br /&gt;
There are two ways of using the editor element in code, the first one is easier but expects some standardized fields. The second method is more low level.&lt;br /&gt;
&lt;br /&gt;
====Simple use====&lt;br /&gt;
# name database fields: &#039;&#039;textfield&#039;&#039;, &#039;&#039;textfieldformat&#039;&#039; (and &#039;&#039;textfieldtrust&#039;&#039; if required)&lt;br /&gt;
# create options array.  note that context is the best, most local context you have available.&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$textfieldoptions = array(&#039;trusttext&#039;=&amp;gt;true, &#039;subdirs&#039;=&amp;gt;true, &#039;maxfiles&#039;=&amp;gt;$maxfiles, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;context&#039;=&amp;gt;$context);&amp;lt;/code&amp;gt;&lt;br /&gt;
# add editor &#039;&#039;textfield_editor&#039;&#039; to moodle form, pass options through custom data in form constructor, set $data-&amp;gt;id to null if data not exist yet&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;textfield_editor&#039;, get_string(&#039;fieldname&#039;, &#039;somemodule&#039;), null, $textfieldoptions);&amp;lt;/code&amp;gt;&lt;br /&gt;
# prepare data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_prepare_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
# get submitted data and after inserting/updating of data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_postupdate_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Real world examples are in mod/glossary/edit.php and mod/glossary/comment.php&lt;br /&gt;
&lt;br /&gt;
====Low level use====&lt;br /&gt;
&lt;br /&gt;
When using editor element you  need to preprocess and postprocess the data:&lt;br /&gt;
# detect if form was already submitted (usually means draft is area already exists) - &#039;&#039;file_get_submitted_draft_itemid()&#039;&#039;&lt;br /&gt;
# prepare draft file area, temporary storage of all files attached to the text - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# convert encoded relative links to absolute links - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# create form and set current data&lt;br /&gt;
# after submission the changed files must be merged back into original area - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
# absolute links have to be replaced by relative links - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====Replace old htmleditor with editor=====&lt;br /&gt;
&lt;br /&gt;
The file picker has been integrated with with TinyMCE to make the editor element. This new element should support all types on editors and should be able to switch them on-the-fly. Instances of the old htmleditor element in your forms should be replaced by the new editor element, this may need adding of new format and trusttext columns. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;entry&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
        array(&#039;maxfiles&#039; =&amp;gt; EDITOR_UNLIMITED_FILES));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The editor element can take following options: maxfiles, maxbytes, subdirs and changeformat. Please note that the embedded files is optional feature and is not expected be used everywhere.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: the editor element now includes text format option. You should no longer use the separate format element type.&lt;br /&gt;
&lt;br /&gt;
=====Prepare current data - text and files=====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
  $entry = new object();&lt;br /&gt;
  $entry-&amp;gt;id = null;&lt;br /&gt;
  $entry-&amp;gt;definition = &#039;&#039;;&lt;br /&gt;
  $entry-&amp;gt;format = FORMAT_HTML;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftid_editor = file_get_submitted_draft_itemid(&#039;entry&#039;);&lt;br /&gt;
$currenttext = file_prepare_draft_area($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                       $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $entry-&amp;gt;definition);&lt;br /&gt;
$entry-&amp;gt;entry = array(&#039;text&#039;=&amp;gt;$currenttext, &#039;format&#039;=&amp;gt;$entry-&amp;gt;format, &#039;itemid&#039;=&amp;gt;$draftid_editor);&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are multiple files, they will share the same itemid.&lt;br /&gt;
&lt;br /&gt;
=====Obtain text, format and save draft files=====&lt;br /&gt;
&lt;br /&gt;
To retrieve editor content, you need to use following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // content of editor&lt;br /&gt;
    $messagetext = $fromform-&amp;gt;entry[&#039;text&#039;];&lt;br /&gt;
    // format of content&lt;br /&gt;
    $messageformat  = $fromform-&amp;gt;entry[&#039;format&#039;];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.&lt;br /&gt;
&lt;br /&gt;
When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call $DB-&amp;gt;update_record(&#039;tablename&#039;, $data); to have the other parts of the form submission stored correctly.)&lt;br /&gt;
&lt;br /&gt;
The save_files_from_draft_area function and replace absolute links with internal relative links do:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_save_draft_area_files($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                          $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $messagetext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $context-&amp;gt;id, &#039;component&#039;, &#039;proper_file_area&#039; and $entry-&amp;gt;id : correspond to the contextid, filearea and itemid columns in the [[File_API#Table:_files|files table]].&lt;br /&gt;
; $messagetext : this is the message text. As the files are saved to the real file area, the URLs in this content are rewritten.&lt;br /&gt;
&lt;br /&gt;
All URLs in content that point to files managed to the File API are converted to a form that starts &#039;@@PLUGINFILE@@/&#039; before the content is stored in the database. That is what we mean by rewriting.&lt;br /&gt;
&lt;br /&gt;
== File serving==&lt;br /&gt;
&lt;br /&gt;
=== Convert internal relative links to absolute links ===&lt;br /&gt;
&lt;br /&gt;
Before text content is displayed to the user, any URLs in the &#039;@@PLUGINFILE@@/&#039; form in the content need to be rewritten to the real URL where the user can access the files. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_rewrite_pluginfile_urls($messagetext, &#039;pluginfile.php&#039;,&lt;br /&gt;
        $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $messagetext : is the content containing the @@PLUGINFILE@@ URLs from the database.&lt;br /&gt;
; &#039;pluginfile.php&#039; : there are a number of different scripts that can serve files with different permissions checks. You need to specify which one to use.&lt;br /&gt;
; $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid : uniquely identifies the file area, as before.&lt;br /&gt;
&lt;br /&gt;
=== Implement file serving access control ===&lt;br /&gt;
&lt;br /&gt;
Attachments and embedded images should have the same access control like the text itself, in majority of cases these files are served using pluginfile.php. Access control is defined in &#039;&#039;module/lib.php&#039;&#039; file in function &#039;&#039;module_pluginfile()&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== File browsing support ==&lt;br /&gt;
Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.&lt;br /&gt;
&lt;br /&gt;
Activities may specify browsing support in own module/lib.php file by implementing functions module_get_file_areas() and module_get_file_info().&lt;br /&gt;
&lt;br /&gt;
== Upgrading your code ==&lt;br /&gt;
Here I will attempt to describe some simple steps you can take to upgrade your file-handling form elements from pre-2.0 code to 2.0. We will use the example of glossary, since it has been used above.&lt;br /&gt;
&lt;br /&gt;
=== Preparing your options ===&lt;br /&gt;
Unless you are happy with the defaults, you will need to define an array of options for each file-handling form element. You could define it at different places, but it&#039;s best to put it in one place and make the array(s) available to other files if they need it. In the majority of cases, this will be in a file like edit.php&lt;br /&gt;
&lt;br /&gt;
Previous code in mod/glossary/edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform =&amp;amp; new mod_glossary_entry_form(null, compact(&#039;cm&#039;, &#039;glossary&#039;, &#039;hook&#039;, &#039;mode&#039;, &#039;e&#039;, &#039;context&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$maxbytes = $course-&amp;gt;maxbytes;&lt;br /&gt;
// Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&lt;br /&gt;
&lt;br /&gt;
$definitionoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;trusttext&#039;=&amp;gt;true,&lt;br /&gt;
                           &#039;context&#039;=&amp;gt;$context);&lt;br /&gt;
$attachmentoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note that the data being passed to the form constructor have changed also, but this is not part of the file API changes, I just include them to avoid confusion.&lt;br /&gt;
&lt;br /&gt;
These options are for the htmleditor (definition field) and the filemanager (attachment field). They are used by a file called edit_form.php.&lt;br /&gt;
&lt;br /&gt;
=== Element preparation ===&lt;br /&gt;
Before we look at this, however, we need to &amp;quot;prepare&amp;quot; the elements so that they can correctly display existing embedded images and attached files when you are editing a record instead of just creating one. So, let&#039;s take the code we&#039;ve got so far in edit.php and add to it:&lt;br /&gt;
&lt;br /&gt;
Currently upgraded code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&lt;br /&gt;
        &#039;current&#039;=&amp;gt;$entry, &lt;br /&gt;
        &#039;cm&#039;=&amp;gt;$cm, &lt;br /&gt;
        &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
        &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
        &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code with element preparation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$entry = new stdClass();&lt;br /&gt;
$entry-&amp;gt;id = 0; \\ See notes!&lt;br /&gt;
$entry = file_prepare_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                      &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_prepare_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                           &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* $entry in this case is simply a stdClass object which may either represent a new glossary entry or an existing one.&lt;br /&gt;
* $entry-&amp;gt;id must be the unique identifier for the current object. If we are creating a new entry, it will be null, but in all cases it must be defined.&lt;br /&gt;
* These two functions (file_prepare_standard_editor and file_prepare_standard_filemanager) are shortcuts functions that take care of some of the tedious setting up for you, but they make a couple of assumptions:&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; name the form element as {element}_editor or {element}_filemanager (see next section)&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; have at least the following fields in the database: {element} and {element}summary, as described earlier in this documentation&lt;br /&gt;
&lt;br /&gt;
We can now look at the upgrades needed in the form definition file.&lt;br /&gt;
&lt;br /&gt;
=== Form definition ===&lt;br /&gt;
Previous code in mod/glossary/edit_form.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;htmleditor&#039;, &#039;definition&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;),&lt;br /&gt;
                    array(&#039;rows&#039;=&amp;gt;20));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;definition&#039;, array(&#039;writing&#039;, &#039;richtext&#039;), false, &#039;editorhelpbutton&#039;);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;format&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0,&lt;br /&gt;
                                             true, true, false));&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment&#039;, array(&#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                      &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitionoptions = $this-&amp;gt;_customdata[&#039;definitionoptions&#039;];&lt;br /&gt;
$attachmentoptions = $this-&amp;gt;_customdata[&#039;attachmentoptions&#039;];&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;definition_editor&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
                   $definitionoptions);&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition_editor&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition_editor&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachment_filemanager&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                   null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;,&lt;br /&gt;
                      get_string(&#039;attachment&#039;, &#039;glossary&#039;), &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the following:&lt;br /&gt;
* The format element and the help button are no longer required for the HTML editor element&lt;br /&gt;
* The name of the form element needs to be changed by adding &#039;_editor&#039; or &#039;_manager&#039; to the original name. This is a naming convention that is used by a couple of functions we will look at shortly&lt;br /&gt;
* Make sure $definitionoptions has context parameter, else system context is used and editor will not respect filter settings.&lt;br /&gt;
&lt;br /&gt;
=== Handling submitted data ===&lt;br /&gt;
The final step is to handle the submitted data properly, i.e. retrieve the files and save them to disk, associating them with the record we have just created (a glossary entry in our example). This happens in edit.php:&lt;br /&gt;
&lt;br /&gt;
Previous code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Section that updates an entry:&lt;br /&gt;
$todb-&amp;gt;id = $e;&lt;br /&gt;
$dir = glossary_file_area_name($todb);&lt;br /&gt;
if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
    $todb-&amp;gt;attachment = $newfilename;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Section that adds an entry:&lt;br /&gt;
if ($todb-&amp;gt;id = insert_record(&amp;quot;glossary_entries&amp;quot;, $todb)) {&lt;br /&gt;
    $e = $todb-&amp;gt;id;&lt;br /&gt;
    $dir = glossary_file_area_name($todb);&lt;br /&gt;
    if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
        set_field(&amp;quot;glossary_entries&amp;quot;, &amp;quot;attachment&amp;quot;, $newfilename, &amp;quot;id&amp;quot;, $todb-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// $todb was renamed to $entry, and the code was refactored &lt;br /&gt;
// so that the file-handling code is only used once for either an add or an update action.&lt;br /&gt;
// If an entry is being added, $DB-&amp;gt;insert() has already been called, so we have a valid $entry-&amp;gt;id&lt;br /&gt;
$entry = file_postupdate_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                         &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_postupdate_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                              &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
// store the updated value values&lt;br /&gt;
$DB-&amp;gt;update_record(&#039;glossary_entries&#039;, $entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* If you are adding a new record, you will still need to call update_record after calling the file_postupdate* functions&lt;br /&gt;
&lt;br /&gt;
=== Gotchas ===&lt;br /&gt;
A few things to keep in mind:&lt;br /&gt;
* Make sure that you instantiate the moodle form before any call to $OUTPUT-&amp;gt;header()&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Using the file API]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=207748 Adding a text editor to a Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=157953#p692822 Button actions in Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=351013#p1416554 File Picker]&lt;br /&gt;
* [https://github.com/CARLOSEDUARDOVIEIRA/moodle-uploadfile Example of filemanager in an activity module]&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Repositories]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55470</id>
		<title>Using the File API in Moodle forms</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55470"/>
		<updated>2019-01-18T11:03:17Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Element preparation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 all files are stored in a central database accessible via the [[File API|File API]], and every file is associated with a component and a &amp;quot;file area&amp;quot; in Moodle, such as a particular module.&lt;br /&gt;
&lt;br /&gt;
A common use case is to provide a form (using Moodle&#039;s [[lib/formslib.php|Forms API]]) which allows users to upload or import files as attachments or media embedded into HTML.&lt;br /&gt;
&lt;br /&gt;
Normally this works like this:&lt;br /&gt;
# User starts creation or re-edits an existing item in Moodle (eg forum post, resource, glossary entry etc)&lt;br /&gt;
# User presses some sort of button to browse for new files to attach or embed&lt;br /&gt;
# User sees our &amp;quot;Choose file...&amp;quot; dialog, which contains one or more repository instances. &lt;br /&gt;
# User chooses a file, the corresponding [[Repository plugins|Repository plugin]] takes care of copying the file into a &amp;quot;draft file area&amp;quot; within Moodle&lt;br /&gt;
# File appears in the text or as an attachment in the form.&lt;br /&gt;
# When the user hits save, the [[File API|File API]] is invoked to move the file from the draft file area into a permanent file area associated with that data &lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to interact with users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
If you just want to write code to manipulate Moodle files internally (without user input) then see [[File API]].&lt;br /&gt;
&lt;br /&gt;
==Form elements== &lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 there are three file-related form elements for interacting with users:&lt;br /&gt;
&lt;br /&gt;
# filemanager - the way to attach one or more files as a set&lt;br /&gt;
# editor - the way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML&lt;br /&gt;
# filepicker - a way to specify one file for the case when you want to process the file and throw it away &lt;br /&gt;
&lt;br /&gt;
In Moodle 1.9 there were two other types which are now &#039;&#039;&#039;deprecated&#039;&#039;&#039; (they work, but please do not use these anymore)&lt;br /&gt;
# file - used to just allow a normal file upload from the desktop only.&lt;br /&gt;
# htmleditor - this old method of embedding a HTML editor in a textarea is not able to support repositories etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
&lt;br /&gt;
File picker (&#039;&#039;filepicker&#039;&#039;) is a direct replacement of the older &#039;&#039;file&#039;&#039; formslib element. &lt;br /&gt;
&lt;br /&gt;
It is intended for situations when you want the user to upload &#039;&#039;&#039;one&#039;&#039;&#039; file so you can process it and delete it, such as when you are importing data from a CSV file.&lt;br /&gt;
If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).&lt;br /&gt;
&lt;br /&gt;
==== Using the filepicker element ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null,&lt;br /&gt;
                   array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Obtain the chosen file ====&lt;br /&gt;
&lt;br /&gt;
To get the contents of the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$content = $mform-&amp;gt;get_file_content(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the name of the chosen file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$name = $mform-&amp;gt;get_new_filename(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To save the chosen file to the server filesystem (such as to moodledata folder):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$success = $mform-&amp;gt;save_file(&#039;userfile&#039;, $fullpath, $override);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To store the chosen file in the Moodle files pool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$storedfile = $mform-&amp;gt;save_stored_file(&#039;userfile&#039;, ...);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== filemanager ===&lt;br /&gt;
&lt;br /&gt;
The File Manager element improves on file picker by allowing you to manage more than one file.  It is expected that the files will be stored permanently for future use (such as forum and glossary attachments).&lt;br /&gt;
&lt;br /&gt;
==== Add file manager element ====&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachments&#039;, get_string(&#039;attachment&#039;, &#039;moodle&#039;), null,&lt;br /&gt;
                    array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;areamaxbytes&#039; =&amp;gt; 10485760, &#039;maxfiles&#039; =&amp;gt; 50,&lt;br /&gt;
                          &#039;accepted_types&#039; =&amp;gt; array(&#039;document&#039;), &#039;return_types&#039;=&amp;gt; FILE_INTERNAL | FILE_EXTERNAL));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the fields for filemanager:&lt;br /&gt;
&lt;br /&gt;
;&#039;filemanager&#039;:This is a filemanager element :)&lt;br /&gt;
;elementname:The unique name of the element in the form&lt;br /&gt;
;elementlabel:The label string that users see &lt;br /&gt;
;attributes:(leave it as null)&lt;br /&gt;
;options: an array of further options for the filepicker (see below)&lt;br /&gt;
&lt;br /&gt;
The options array can contain:&lt;br /&gt;
&lt;br /&gt;
;subdirs:(Default 1) Are subdirectories allowed?  (true or false)&lt;br /&gt;
;maxbytes:(Default 0) Restricts the size of each individual file.&lt;br /&gt;
;areamaxbytes:(Default 0) Restricts the total size of all the files.&lt;br /&gt;
;maxfiles:(Default -1) Restricts the total number of files.&lt;br /&gt;
;accepted_types:(Default *) You can specify what file types are accepted by filemanager.  All current file types are listed in this file: [http://cvs.moodle.org/moodle/lib/filestorage/file_types.mm moodle/lib/filestorage/file_types.mm].  This is a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file: if it is edited the changes will be immediately reflected in Moodle.&lt;br /&gt;
&lt;br /&gt;
As of 2.3 file_types.mm is no longer used, instead the file types are listed in &amp;lt;code&amp;gt;get_mimetypes_array()&amp;lt;/code&amp;gt; in lib/classes/filetypes.php (https://github.com/moodle/moodle/blob/master/lib/classes/filetypes.php).&lt;br /&gt;
&lt;br /&gt;
Example usage:  &#039;&#039;&#039;array(&#039;audio&#039;, &#039;video&#039;, &#039;document&#039;)&#039;&#039;&#039;, you can include file extensions as well, for example: &#039;&#039;&#039;array(&#039;.txt&#039;, &#039;.jpg&#039;, &#039;audio&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Load existing files into draft area ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
    $entry = new stdClass;&lt;br /&gt;
    $entry-&amp;gt;id = null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftitemid = file_get_submitted_draft_itemid(&#039;attachments&#039;);&lt;br /&gt;
&lt;br /&gt;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id,&lt;br /&gt;
                        array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
&lt;br /&gt;
$entry-&amp;gt;attachments = $draftitemid;&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Store updated set of files ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // ... store or update $entry&lt;br /&gt;
    file_save_draft_area_files($data-&amp;gt;attachments, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;,&lt;br /&gt;
                   $entry-&amp;gt;id, array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===editor===&lt;br /&gt;
There are two ways of using the editor element in code, the first one is easier but expects some standardized fields. The second method is more low level.&lt;br /&gt;
&lt;br /&gt;
====Simple use====&lt;br /&gt;
# name database fields: &#039;&#039;textfield&#039;&#039;, &#039;&#039;textfieldformat&#039;&#039; (and &#039;&#039;textfieldtrust&#039;&#039; if required)&lt;br /&gt;
# create options array.  note that context is the best, most local context you have available.&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$textfieldoptions = array(&#039;trusttext&#039;=&amp;gt;true, &#039;subdirs&#039;=&amp;gt;true, &#039;maxfiles&#039;=&amp;gt;$maxfiles, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;context&#039;=&amp;gt;$context);&amp;lt;/code&amp;gt;&lt;br /&gt;
# add editor &#039;&#039;textfield_editor&#039;&#039; to moodle form, pass options through custom data in form constructor, set $data-&amp;gt;id to null if data not exist yet&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;textfield_editor&#039;, get_string(&#039;fieldname&#039;, &#039;somemodule&#039;), null, $textfieldoptions);&amp;lt;/code&amp;gt;&lt;br /&gt;
# prepare data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_prepare_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
# get submitted data and after inserting/updating of data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_postupdate_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Real world examples are in mod/glossary/edit.php and mod/glossary/comment.php&lt;br /&gt;
&lt;br /&gt;
====Low level use====&lt;br /&gt;
&lt;br /&gt;
When using editor element you  need to preprocess and postprocess the data:&lt;br /&gt;
# detect if form was already submitted (usually means draft is area already exists) - &#039;&#039;file_get_submitted_draft_itemid()&#039;&#039;&lt;br /&gt;
# prepare draft file area, temporary storage of all files attached to the text - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# convert encoded relative links to absolute links - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# create form and set current data&lt;br /&gt;
# after submission the changed files must be merged back into original area - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
# absolute links have to be replaced by relative links - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====Replace old htmleditor with editor=====&lt;br /&gt;
&lt;br /&gt;
The file picker has been integrated with with TinyMCE to make the editor element. This new element should support all types on editors and should be able to switch them on-the-fly. Instances of the old htmleditor element in your forms should be replaced by the new editor element, this may need adding of new format and trusttext columns. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;entry&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
        array(&#039;maxfiles&#039; =&amp;gt; EDITOR_UNLIMITED_FILES));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The editor element can take following options: maxfiles, maxbytes, subdirs and changeformat. Please note that the embedded files is optional feature and is not expected be used everywhere.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: the editor element now includes text format option. You should no longer use the separate format element type.&lt;br /&gt;
&lt;br /&gt;
=====Prepare current data - text and files=====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
  $entry = new object();&lt;br /&gt;
  $entry-&amp;gt;id = null;&lt;br /&gt;
  $entry-&amp;gt;definition = &#039;&#039;;&lt;br /&gt;
  $entry-&amp;gt;format = FORMAT_HTML;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftid_editor = file_get_submitted_draft_itemid(&#039;entry&#039;);&lt;br /&gt;
$currenttext = file_prepare_draft_area($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                       $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $entry-&amp;gt;definition);&lt;br /&gt;
$entry-&amp;gt;entry = array(&#039;text&#039;=&amp;gt;$currenttext, &#039;format&#039;=&amp;gt;$entry-&amp;gt;format, &#039;itemid&#039;=&amp;gt;$draftid_editor);&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are multiple files, they will share the same itemid.&lt;br /&gt;
&lt;br /&gt;
=====Obtain text, format and save draft files=====&lt;br /&gt;
&lt;br /&gt;
To retrieve editor content, you need to use following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // content of editor&lt;br /&gt;
    $messagetext = $fromform-&amp;gt;entry[&#039;text&#039;];&lt;br /&gt;
    // format of content&lt;br /&gt;
    $messageformat  = $fromform-&amp;gt;entry[&#039;format&#039;];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.&lt;br /&gt;
&lt;br /&gt;
When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call $DB-&amp;gt;update_record(&#039;tablename&#039;, $data); to have the other parts of the form submission stored correctly.)&lt;br /&gt;
&lt;br /&gt;
The save_files_from_draft_area function and replace absolute links with internal relative links do:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_save_draft_area_files($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                          $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $messagetext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $context-&amp;gt;id, &#039;component&#039;, &#039;proper_file_area&#039; and $entry-&amp;gt;id : correspond to the contextid, filearea and itemid columns in the [[File_API#Table:_files|files table]].&lt;br /&gt;
; $messagetext : this is the message text. As the files are saved to the real file area, the URLs in this content are rewritten.&lt;br /&gt;
&lt;br /&gt;
All URLs in content that point to files managed to the File API are converted to a form that starts &#039;@@PLUGINFILE@@/&#039; before the content is stored in the database. That is what we mean by rewriting.&lt;br /&gt;
&lt;br /&gt;
== File serving==&lt;br /&gt;
&lt;br /&gt;
=== Convert internal relative links to absolute links ===&lt;br /&gt;
&lt;br /&gt;
Before text content is displayed to the user, any URLs in the &#039;@@PLUGINFILE@@/&#039; form in the content need to be rewritten to the real URL where the user can access the files. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_rewrite_pluginfile_urls($messagetext, &#039;pluginfile.php&#039;,&lt;br /&gt;
        $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $messagetext : is the content containing the @@PLUGINFILE@@ URLs from the database.&lt;br /&gt;
; &#039;pluginfile.php&#039; : there are a number of different scripts that can serve files with different permissions checks. You need to specify which one to use.&lt;br /&gt;
; $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid : uniquely identifies the file area, as before.&lt;br /&gt;
&lt;br /&gt;
=== Implement file serving access control ===&lt;br /&gt;
&lt;br /&gt;
Attachments and embedded images should have the same access control like the text itself, in majority of cases these files are served using pluginfile.php. Access control is defined in &#039;&#039;module/lib.php&#039;&#039; file in function &#039;&#039;module_pluginfile()&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== File browsing support ==&lt;br /&gt;
Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.&lt;br /&gt;
&lt;br /&gt;
Activities may specify browsing support in own module/lib.php file by implementing functions module_get_file_areas() and module_get_file_info().&lt;br /&gt;
&lt;br /&gt;
== Upgrading your code ==&lt;br /&gt;
Here I will attempt to describe some simple steps you can take to upgrade your file-handling form elements from pre-2.0 code to 2.0. We will use the example of glossary, since it has been used above.&lt;br /&gt;
&lt;br /&gt;
=== Preparing your options ===&lt;br /&gt;
Unless you are happy with the defaults, you will need to define an array of options for each file-handling form element. You could define it at different places, but it&#039;s best to put it in one place and make the array(s) available to other files if they need it. In the majority of cases, this will be in a file like edit.php&lt;br /&gt;
&lt;br /&gt;
Previous code in mod/glossary/edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform =&amp;amp; new mod_glossary_entry_form(null, compact(&#039;cm&#039;, &#039;glossary&#039;, &#039;hook&#039;, &#039;mode&#039;, &#039;e&#039;, &#039;context&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$maxbytes = $course-&amp;gt;maxbytes;&lt;br /&gt;
// Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&lt;br /&gt;
&lt;br /&gt;
$definitionoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;trusttext&#039;=&amp;gt;true,&lt;br /&gt;
                           &#039;context&#039;=&amp;gt;$context);&lt;br /&gt;
$attachmentoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note that the data being passed to the form constructor have changed also, but this is not part of the file API changes, I just include them to avoid confusion.&lt;br /&gt;
&lt;br /&gt;
These options are for the htmleditor (definition field) and the filemanager (attachment field). They are used by a file called edit_form.php.&lt;br /&gt;
&lt;br /&gt;
=== Element preparation ===&lt;br /&gt;
Before we look at this, however, we need to &amp;quot;prepare&amp;quot; the elements so that they can correctly display existing embedded images and attached files when you are editing a record instead of just creating one. So, let&#039;s take the code we&#039;ve got so far in edit.php and add to it:&lt;br /&gt;
&lt;br /&gt;
Currently upgraded code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&lt;br /&gt;
        &#039;current&#039;=&amp;gt;$entry, &lt;br /&gt;
        &#039;cm&#039;=&amp;gt;$cm, &lt;br /&gt;
        &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
        &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
        &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code with element preparation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$entry = new stdClass();&lt;br /&gt;
$entry-&amp;gt;id = 0; \\ See notes!&lt;br /&gt;
$entry = file_prepare_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                      &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_prepare_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                           &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* $entry in this case is simply a stdClass object which may either represent a new glossary entry or an existing one.&lt;br /&gt;
* $entry-&amp;gt;id must be the unique identifier for the current object. If we are creating a new entry, it will be null, but in all cases it must be defined.&lt;br /&gt;
* These two functions (file_prepare_standard_editor and file_prepare_standard_filemanager) are shortcuts functions that take care of some of the tedious setting up for you, but they make a couple of assumptions:&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; name the form element as {element}_editor or {element}_filemanager (see next section)&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; have at least the following fields in the database: {element} and {element}summary, as described earlier in this documentation&lt;br /&gt;
&lt;br /&gt;
We can now look at the upgrades needed in the form definition file.&lt;br /&gt;
&lt;br /&gt;
=== Form definition ===&lt;br /&gt;
Previous code in mod/glossary/edit_form.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;htmleditor&#039;, &#039;definition&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;),&lt;br /&gt;
                    array(&#039;rows&#039;=&amp;gt;20));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;definition&#039;, array(&#039;writing&#039;, &#039;richtext&#039;), false, &#039;editorhelpbutton&#039;);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;format&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0,&lt;br /&gt;
                                             true, true, false));&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment&#039;, array(&#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                      &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitionoptions = $this-&amp;gt;_customdata[&#039;definitionoptions&#039;];&lt;br /&gt;
$attachmentoptions = $this-&amp;gt;_customdata[&#039;attachmentoptions&#039;];&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;definition_editor&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
                   $definitionoptions);&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition_editor&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition_editor&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachment_filemanager&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                   null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;,&lt;br /&gt;
                      get_string(&#039;attachment&#039;, &#039;glossary&#039;), &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the following:&lt;br /&gt;
* The format element and the help button are no longer required for the HTML editor element&lt;br /&gt;
* The name of the form element needs to be changed by adding &#039;_editor&#039; or &#039;_manager&#039; to the original name. This is a naming convention that is used by a couple of functions we will look at shortly&lt;br /&gt;
* Make sure $definitionoptions has context parameter, else system context is used and editor will not respect filter settings.&lt;br /&gt;
&lt;br /&gt;
=== Handling submitted data ===&lt;br /&gt;
The final step is to handle the submitted data properly, i.e. retrieve the files and save them to disk, associating them with the record we have just created (a glossary entry in our example). This happens in edit.php:&lt;br /&gt;
&lt;br /&gt;
Previous code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Section that updates an entry:&lt;br /&gt;
$todb-&amp;gt;id = $e;&lt;br /&gt;
$dir = glossary_file_area_name($todb);&lt;br /&gt;
if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
    $todb-&amp;gt;attachment = $newfilename;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Section that adds an entry:&lt;br /&gt;
if ($todb-&amp;gt;id = insert_record(&amp;quot;glossary_entries&amp;quot;, $todb)) {&lt;br /&gt;
    $e = $todb-&amp;gt;id;&lt;br /&gt;
    $dir = glossary_file_area_name($todb);&lt;br /&gt;
    if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
        set_field(&amp;quot;glossary_entries&amp;quot;, &amp;quot;attachment&amp;quot;, $newfilename, &amp;quot;id&amp;quot;, $todb-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// $todb was renamed to $entry, and the code was refactored &lt;br /&gt;
// so that the file-handling code is only used once for either an add or an update action.&lt;br /&gt;
// If an entry is being added, $DB-&amp;gt;insert() has already been called, so we have a valid $entry-&amp;gt;id&lt;br /&gt;
$entry = file_postupdate_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                         &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_postupdate_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                              &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
// store the updated value values&lt;br /&gt;
$DB-&amp;gt;update_record(&#039;glossary_entries&#039;, $entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* If you are adding a new record, you will still need to call update_record after calling the file_postupdate* functions&lt;br /&gt;
&lt;br /&gt;
=== Gotchas ===&lt;br /&gt;
A few things to keep in mind:&lt;br /&gt;
* Make sure that you instantiate the moodle form before any call to $OUTPUT-&amp;gt;header()&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Using the file API]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=207748 Adding a text editor to a Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=157953#p692822 Button actions in Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=351013#p1416554 File Picker]&lt;br /&gt;
* [https://github.com/CARLOSEDUARDOVIEIRA/moodle-uploadfile Example of filemanager in an activity module]&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Repositories]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55469</id>
		<title>Using the File API in Moodle forms</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=55469"/>
		<updated>2019-01-18T11:02:01Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Element preparation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 all files are stored in a central database accessible via the [[File API|File API]], and every file is associated with a component and a &amp;quot;file area&amp;quot; in Moodle, such as a particular module.&lt;br /&gt;
&lt;br /&gt;
A common use case is to provide a form (using Moodle&#039;s [[lib/formslib.php|Forms API]]) which allows users to upload or import files as attachments or media embedded into HTML.&lt;br /&gt;
&lt;br /&gt;
Normally this works like this:&lt;br /&gt;
# User starts creation or re-edits an existing item in Moodle (eg forum post, resource, glossary entry etc)&lt;br /&gt;
# User presses some sort of button to browse for new files to attach or embed&lt;br /&gt;
# User sees our &amp;quot;Choose file...&amp;quot; dialog, which contains one or more repository instances. &lt;br /&gt;
# User chooses a file, the corresponding [[Repository plugins|Repository plugin]] takes care of copying the file into a &amp;quot;draft file area&amp;quot; within Moodle&lt;br /&gt;
# File appears in the text or as an attachment in the form.&lt;br /&gt;
# When the user hits save, the [[File API|File API]] is invoked to move the file from the draft file area into a permanent file area associated with that data &lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to interact with users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
If you just want to write code to manipulate Moodle files internally (without user input) then see [[File API]].&lt;br /&gt;
&lt;br /&gt;
==Form elements== &lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 there are three file-related form elements for interacting with users:&lt;br /&gt;
&lt;br /&gt;
# filemanager - the way to attach one or more files as a set&lt;br /&gt;
# editor - the way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML&lt;br /&gt;
# filepicker - a way to specify one file for the case when you want to process the file and throw it away &lt;br /&gt;
&lt;br /&gt;
In Moodle 1.9 there were two other types which are now &#039;&#039;&#039;deprecated&#039;&#039;&#039; (they work, but please do not use these anymore)&lt;br /&gt;
# file - used to just allow a normal file upload from the desktop only.&lt;br /&gt;
# htmleditor - this old method of embedding a HTML editor in a textarea is not able to support repositories etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
&lt;br /&gt;
File picker (&#039;&#039;filepicker&#039;&#039;) is a direct replacement of the older &#039;&#039;file&#039;&#039; formslib element. &lt;br /&gt;
&lt;br /&gt;
It is intended for situations when you want the user to upload &#039;&#039;&#039;one&#039;&#039;&#039; file so you can process it and delete it, such as when you are importing data from a CSV file.&lt;br /&gt;
If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).&lt;br /&gt;
&lt;br /&gt;
==== Using the filepicker element ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null,&lt;br /&gt;
                   array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Obtain the chosen file ====&lt;br /&gt;
&lt;br /&gt;
To get the contents of the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$content = $mform-&amp;gt;get_file_content(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the name of the chosen file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$name = $mform-&amp;gt;get_new_filename(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To save the chosen file to the server filesystem (such as to moodledata folder):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$success = $mform-&amp;gt;save_file(&#039;userfile&#039;, $fullpath, $override);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To store the chosen file in the Moodle files pool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$storedfile = $mform-&amp;gt;save_stored_file(&#039;userfile&#039;, ...);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== filemanager ===&lt;br /&gt;
&lt;br /&gt;
The File Manager element improves on file picker by allowing you to manage more than one file.  It is expected that the files will be stored permanently for future use (such as forum and glossary attachments).&lt;br /&gt;
&lt;br /&gt;
==== Add file manager element ====&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachments&#039;, get_string(&#039;attachment&#039;, &#039;moodle&#039;), null,&lt;br /&gt;
                    array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;areamaxbytes&#039; =&amp;gt; 10485760, &#039;maxfiles&#039; =&amp;gt; 50,&lt;br /&gt;
                          &#039;accepted_types&#039; =&amp;gt; array(&#039;document&#039;), &#039;return_types&#039;=&amp;gt; FILE_INTERNAL | FILE_EXTERNAL));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the fields for filemanager:&lt;br /&gt;
&lt;br /&gt;
;&#039;filemanager&#039;:This is a filemanager element :)&lt;br /&gt;
;elementname:The unique name of the element in the form&lt;br /&gt;
;elementlabel:The label string that users see &lt;br /&gt;
;attributes:(leave it as null)&lt;br /&gt;
;options: an array of further options for the filepicker (see below)&lt;br /&gt;
&lt;br /&gt;
The options array can contain:&lt;br /&gt;
&lt;br /&gt;
;subdirs:(Default 1) Are subdirectories allowed?  (true or false)&lt;br /&gt;
;maxbytes:(Default 0) Restricts the size of each individual file.&lt;br /&gt;
;areamaxbytes:(Default 0) Restricts the total size of all the files.&lt;br /&gt;
;maxfiles:(Default -1) Restricts the total number of files.&lt;br /&gt;
;accepted_types:(Default *) You can specify what file types are accepted by filemanager.  All current file types are listed in this file: [http://cvs.moodle.org/moodle/lib/filestorage/file_types.mm moodle/lib/filestorage/file_types.mm].  This is a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file: if it is edited the changes will be immediately reflected in Moodle.&lt;br /&gt;
&lt;br /&gt;
As of 2.3 file_types.mm is no longer used, instead the file types are listed in &amp;lt;code&amp;gt;get_mimetypes_array()&amp;lt;/code&amp;gt; in lib/classes/filetypes.php (https://github.com/moodle/moodle/blob/master/lib/classes/filetypes.php).&lt;br /&gt;
&lt;br /&gt;
Example usage:  &#039;&#039;&#039;array(&#039;audio&#039;, &#039;video&#039;, &#039;document&#039;)&#039;&#039;&#039;, you can include file extensions as well, for example: &#039;&#039;&#039;array(&#039;.txt&#039;, &#039;.jpg&#039;, &#039;audio&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Load existing files into draft area ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
    $entry = new stdClass;&lt;br /&gt;
    $entry-&amp;gt;id = null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftitemid = file_get_submitted_draft_itemid(&#039;attachments&#039;);&lt;br /&gt;
&lt;br /&gt;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id,&lt;br /&gt;
                        array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
&lt;br /&gt;
$entry-&amp;gt;attachments = $draftitemid;&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Store updated set of files ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // ... store or update $entry&lt;br /&gt;
    file_save_draft_area_files($data-&amp;gt;attachments, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;,&lt;br /&gt;
                   $entry-&amp;gt;id, array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===editor===&lt;br /&gt;
There are two ways of using the editor element in code, the first one is easier but expects some standardized fields. The second method is more low level.&lt;br /&gt;
&lt;br /&gt;
====Simple use====&lt;br /&gt;
# name database fields: &#039;&#039;textfield&#039;&#039;, &#039;&#039;textfieldformat&#039;&#039; (and &#039;&#039;textfieldtrust&#039;&#039; if required)&lt;br /&gt;
# create options array.  note that context is the best, most local context you have available.&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$textfieldoptions = array(&#039;trusttext&#039;=&amp;gt;true, &#039;subdirs&#039;=&amp;gt;true, &#039;maxfiles&#039;=&amp;gt;$maxfiles, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;context&#039;=&amp;gt;$context);&amp;lt;/code&amp;gt;&lt;br /&gt;
# add editor &#039;&#039;textfield_editor&#039;&#039; to moodle form, pass options through custom data in form constructor, set $data-&amp;gt;id to null if data not exist yet&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;textfield_editor&#039;, get_string(&#039;fieldname&#039;, &#039;somemodule&#039;), null, $textfieldoptions);&amp;lt;/code&amp;gt;&lt;br /&gt;
# prepare data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_prepare_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
# get submitted data and after inserting/updating of data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_postupdate_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Real world examples are in mod/glossary/edit.php and mod/glossary/comment.php&lt;br /&gt;
&lt;br /&gt;
====Low level use====&lt;br /&gt;
&lt;br /&gt;
When using editor element you  need to preprocess and postprocess the data:&lt;br /&gt;
# detect if form was already submitted (usually means draft is area already exists) - &#039;&#039;file_get_submitted_draft_itemid()&#039;&#039;&lt;br /&gt;
# prepare draft file area, temporary storage of all files attached to the text - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# convert encoded relative links to absolute links - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# create form and set current data&lt;br /&gt;
# after submission the changed files must be merged back into original area - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
# absolute links have to be replaced by relative links - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====Replace old htmleditor with editor=====&lt;br /&gt;
&lt;br /&gt;
The file picker has been integrated with with TinyMCE to make the editor element. This new element should support all types on editors and should be able to switch them on-the-fly. Instances of the old htmleditor element in your forms should be replaced by the new editor element, this may need adding of new format and trusttext columns. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;entry&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
        array(&#039;maxfiles&#039; =&amp;gt; EDITOR_UNLIMITED_FILES));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The editor element can take following options: maxfiles, maxbytes, subdirs and changeformat. Please note that the embedded files is optional feature and is not expected be used everywhere.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: the editor element now includes text format option. You should no longer use the separate format element type.&lt;br /&gt;
&lt;br /&gt;
=====Prepare current data - text and files=====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
  $entry = new object();&lt;br /&gt;
  $entry-&amp;gt;id = null;&lt;br /&gt;
  $entry-&amp;gt;definition = &#039;&#039;;&lt;br /&gt;
  $entry-&amp;gt;format = FORMAT_HTML;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftid_editor = file_get_submitted_draft_itemid(&#039;entry&#039;);&lt;br /&gt;
$currenttext = file_prepare_draft_area($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                       $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $entry-&amp;gt;definition);&lt;br /&gt;
$entry-&amp;gt;entry = array(&#039;text&#039;=&amp;gt;$currenttext, &#039;format&#039;=&amp;gt;$entry-&amp;gt;format, &#039;itemid&#039;=&amp;gt;$draftid_editor);&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are multiple files, they will share the same itemid.&lt;br /&gt;
&lt;br /&gt;
=====Obtain text, format and save draft files=====&lt;br /&gt;
&lt;br /&gt;
To retrieve editor content, you need to use following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // content of editor&lt;br /&gt;
    $messagetext = $fromform-&amp;gt;entry[&#039;text&#039;];&lt;br /&gt;
    // format of content&lt;br /&gt;
    $messageformat  = $fromform-&amp;gt;entry[&#039;format&#039;];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.&lt;br /&gt;
&lt;br /&gt;
When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call $DB-&amp;gt;update_record(&#039;tablename&#039;, $data); to have the other parts of the form submission stored correctly.)&lt;br /&gt;
&lt;br /&gt;
The save_files_from_draft_area function and replace absolute links with internal relative links do:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_save_draft_area_files($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                          $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $messagetext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $context-&amp;gt;id, &#039;component&#039;, &#039;proper_file_area&#039; and $entry-&amp;gt;id : correspond to the contextid, filearea and itemid columns in the [[File_API#Table:_files|files table]].&lt;br /&gt;
; $messagetext : this is the message text. As the files are saved to the real file area, the URLs in this content are rewritten.&lt;br /&gt;
&lt;br /&gt;
All URLs in content that point to files managed to the File API are converted to a form that starts &#039;@@PLUGINFILE@@/&#039; before the content is stored in the database. That is what we mean by rewriting.&lt;br /&gt;
&lt;br /&gt;
== File serving==&lt;br /&gt;
&lt;br /&gt;
=== Convert internal relative links to absolute links ===&lt;br /&gt;
&lt;br /&gt;
Before text content is displayed to the user, any URLs in the &#039;@@PLUGINFILE@@/&#039; form in the content need to be rewritten to the real URL where the user can access the files. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_rewrite_pluginfile_urls($messagetext, &#039;pluginfile.php&#039;,&lt;br /&gt;
        $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $messagetext : is the content containing the @@PLUGINFILE@@ URLs from the database.&lt;br /&gt;
; &#039;pluginfile.php&#039; : there are a number of different scripts that can serve files with different permissions checks. You need to specify which one to use.&lt;br /&gt;
; $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid : uniquely identifies the file area, as before.&lt;br /&gt;
&lt;br /&gt;
=== Implement file serving access control ===&lt;br /&gt;
&lt;br /&gt;
Attachments and embedded images should have the same access control like the text itself, in majority of cases these files are served using pluginfile.php. Access control is defined in &#039;&#039;module/lib.php&#039;&#039; file in function &#039;&#039;module_pluginfile()&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== File browsing support ==&lt;br /&gt;
Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.&lt;br /&gt;
&lt;br /&gt;
Activities may specify browsing support in own module/lib.php file by implementing functions module_get_file_areas() and module_get_file_info().&lt;br /&gt;
&lt;br /&gt;
== Upgrading your code ==&lt;br /&gt;
Here I will attempt to describe some simple steps you can take to upgrade your file-handling form elements from pre-2.0 code to 2.0. We will use the example of glossary, since it has been used above.&lt;br /&gt;
&lt;br /&gt;
=== Preparing your options ===&lt;br /&gt;
Unless you are happy with the defaults, you will need to define an array of options for each file-handling form element. You could define it at different places, but it&#039;s best to put it in one place and make the array(s) available to other files if they need it. In the majority of cases, this will be in a file like edit.php&lt;br /&gt;
&lt;br /&gt;
Previous code in mod/glossary/edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform =&amp;amp; new mod_glossary_entry_form(null, compact(&#039;cm&#039;, &#039;glossary&#039;, &#039;hook&#039;, &#039;mode&#039;, &#039;e&#039;, &#039;context&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$maxbytes = $course-&amp;gt;maxbytes;&lt;br /&gt;
// Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&lt;br /&gt;
&lt;br /&gt;
$definitionoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;trusttext&#039;=&amp;gt;true,&lt;br /&gt;
                           &#039;context&#039;=&amp;gt;$context);&lt;br /&gt;
$attachmentoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note that the data being passed to the form constructor have changed also, but this is not part of the file API changes, I just include them to avoid confusion.&lt;br /&gt;
&lt;br /&gt;
These options are for the htmleditor (definition field) and the filemanager (attachment field). They are used by a file called edit_form.php.&lt;br /&gt;
&lt;br /&gt;
=== Element preparation ===&lt;br /&gt;
Before we look at this, however, we need to &amp;quot;prepare&amp;quot; the elements so that they can correctly display existing embedded images and attached files when you are editing a record instead of just creating one. So, let&#039;s take the code we&#039;ve got so far in edit.php and add to it:&lt;br /&gt;
&lt;br /&gt;
Currently upgraded code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&lt;br /&gt;
        &#039;current&#039;=&amp;gt;$entry, &lt;br /&gt;
        &#039;cm&#039;=&amp;gt;$cm, &lt;br /&gt;
        &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
        &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
        &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code with element preparation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$entry = new stdClass();&lt;br /&gt;
$entry = file_prepare_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                      &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_prepare_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                           &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* $entry in this case is simply a stdClass object which may either represent a new glossary entry or an existing one.&lt;br /&gt;
* $entry-&amp;gt;id must be the unique identifier for the current object. If we are creating a new entry, it will be null, but in all cases it must be defined.&lt;br /&gt;
* These two functions (file_prepare_standard_editor and file_prepare_standard_filemanager) are shortcuts functions that take care of some of the tedious setting up for you, but they make a couple of assumptions:&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; name the form element as {element}_editor or {element}_filemanager (see next section)&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; have at least the following fields in the database: {element} and {element}summary, as described earlier in this documentation&lt;br /&gt;
&lt;br /&gt;
We can now look at the upgrades needed in the form definition file.&lt;br /&gt;
&lt;br /&gt;
=== Form definition ===&lt;br /&gt;
Previous code in mod/glossary/edit_form.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;htmleditor&#039;, &#039;definition&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;),&lt;br /&gt;
                    array(&#039;rows&#039;=&amp;gt;20));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;definition&#039;, array(&#039;writing&#039;, &#039;richtext&#039;), false, &#039;editorhelpbutton&#039;);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;format&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0,&lt;br /&gt;
                                             true, true, false));&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment&#039;, array(&#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                      &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitionoptions = $this-&amp;gt;_customdata[&#039;definitionoptions&#039;];&lt;br /&gt;
$attachmentoptions = $this-&amp;gt;_customdata[&#039;attachmentoptions&#039;];&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;definition_editor&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
                   $definitionoptions);&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition_editor&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition_editor&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachment_filemanager&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                   null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;,&lt;br /&gt;
                      get_string(&#039;attachment&#039;, &#039;glossary&#039;), &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the following:&lt;br /&gt;
* The format element and the help button are no longer required for the HTML editor element&lt;br /&gt;
* The name of the form element needs to be changed by adding &#039;_editor&#039; or &#039;_manager&#039; to the original name. This is a naming convention that is used by a couple of functions we will look at shortly&lt;br /&gt;
* Make sure $definitionoptions has context parameter, else system context is used and editor will not respect filter settings.&lt;br /&gt;
&lt;br /&gt;
=== Handling submitted data ===&lt;br /&gt;
The final step is to handle the submitted data properly, i.e. retrieve the files and save them to disk, associating them with the record we have just created (a glossary entry in our example). This happens in edit.php:&lt;br /&gt;
&lt;br /&gt;
Previous code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Section that updates an entry:&lt;br /&gt;
$todb-&amp;gt;id = $e;&lt;br /&gt;
$dir = glossary_file_area_name($todb);&lt;br /&gt;
if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
    $todb-&amp;gt;attachment = $newfilename;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Section that adds an entry:&lt;br /&gt;
if ($todb-&amp;gt;id = insert_record(&amp;quot;glossary_entries&amp;quot;, $todb)) {&lt;br /&gt;
    $e = $todb-&amp;gt;id;&lt;br /&gt;
    $dir = glossary_file_area_name($todb);&lt;br /&gt;
    if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
        set_field(&amp;quot;glossary_entries&amp;quot;, &amp;quot;attachment&amp;quot;, $newfilename, &amp;quot;id&amp;quot;, $todb-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// $todb was renamed to $entry, and the code was refactored &lt;br /&gt;
// so that the file-handling code is only used once for either an add or an update action.&lt;br /&gt;
// If an entry is being added, $DB-&amp;gt;insert() has already been called, so we have a valid $entry-&amp;gt;id&lt;br /&gt;
$entry = file_postupdate_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                         &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_postupdate_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                              &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
// store the updated value values&lt;br /&gt;
$DB-&amp;gt;update_record(&#039;glossary_entries&#039;, $entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* If you are adding a new record, you will still need to call update_record after calling the file_postupdate* functions&lt;br /&gt;
&lt;br /&gt;
=== Gotchas ===&lt;br /&gt;
A few things to keep in mind:&lt;br /&gt;
* Make sure that you instantiate the moodle form before any call to $OUTPUT-&amp;gt;header()&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Using the file API]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=207748 Adding a text editor to a Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=157953#p692822 Button actions in Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=351013#p1416554 File Picker]&lt;br /&gt;
* [https://github.com/CARLOSEDUARDOVIEIRA/moodle-uploadfile Example of filemanager in an activity module]&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Repositories]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.6_release_notes&amp;diff=55208</id>
		<title>Moodle 3.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.6_release_notes&amp;diff=55208"/>
		<updated>2018-12-04T15:38:30Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* For administrators */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: 3 December 2018&lt;br /&gt;
&lt;br /&gt;
Here is [https://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%223.6%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.6].&lt;br /&gt;
&lt;br /&gt;
See our [https://docs.moodle.org/36/en/New_features New Features page] for a more user-friendly introduction to Moodle 3.6 with screenshots.&lt;br /&gt;
&lt;br /&gt;
{{Note|If you are upgrading from a previous version, please see [[:en:Upgrading|Upgrading]] in the user docs. &#039;&#039;In particular, for sites using a custom theme or login form, from 3.6 onwards, the login form must include a new login token field. See [[Login token]] for details.}}&lt;br /&gt;
&lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just the minimum supported versions. We recommend keeping all of your software and operating systems up-to-date.&lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 3.1 or later&lt;br /&gt;
* PHP version: minimum PHP 7.0.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.3&#039;&#039;. PHP 7.1.x and 7.2.x are supported too. [[Moodle and PHP7|PHP 7.3.x support]] is being implemented (@ MDL-63420) and &#039;&#039;&#039;not ready for production&#039;&#039;&#039; with this release.&lt;br /&gt;
* PHP extension &#039;&#039;&#039;intl&#039;&#039;&#039; is required since Moodle 3.4 (it was recommended in 2.0 onwards) &lt;br /&gt;
&lt;br /&gt;
=== Database requirements ===&lt;br /&gt;
&lt;br /&gt;
Moodle supports the following database servers. Again, version numbers are just the minimum supported version. We recommend running the latest stable version of any software.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Database&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
| 9.4&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.6&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.microsoft.com/en-us/server-cloud/products/sql-server/ Microsoft SQL Server]&lt;br /&gt;
| 2008&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.oracle.com/us/products/database/overview/index.html Oracle Database]&lt;br /&gt;
| 11.2&lt;br /&gt;
| Latest&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Client requirements==&lt;br /&gt;
&lt;br /&gt;
=== Browser support ===&lt;br /&gt;
Moodle is compatible with any standards compliant web browser. We regularly test Moodle with the following browsers:&lt;br /&gt;
&lt;br /&gt;
Desktop:&lt;br /&gt;
* Chrome&lt;br /&gt;
* Firefox&lt;br /&gt;
* Safari&lt;br /&gt;
* Edge&lt;br /&gt;
* Internet Explorer&lt;br /&gt;
&lt;br /&gt;
Mobile:&lt;br /&gt;
* MobileSafari&lt;br /&gt;
* Google Chrome&lt;br /&gt;
&lt;br /&gt;
For the best experience and optimum security, we recommend that you keep your browser up to date. https://whatbrowser.org&lt;br /&gt;
&lt;br /&gt;
Note: Legacy browsers with known compatibility issues with Moodle 3.6:&lt;br /&gt;
* Internet Explorer 10 and below&lt;br /&gt;
* Safari 7 and below&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===Dashboard and Course overview===&lt;br /&gt;
* MDL-63044 and MDL-63337 - New [https://docs.moodle.org/36/en/Course_overview Course overview] and [https://docs.moodle.org/36/en/Timeline_block Timeline block]&lt;br /&gt;
* MDL-63062 - New [https://docs.moodle.org/36/en/Recently_accessed_courses_block Recently accessed courses block]&lt;br /&gt;
* MDL-63063 - New [https://docs.moodle.org/36/en/Recently_accessed_items_block Recently accessed items block]&lt;br /&gt;
* MDL-63457 - Option to hide courses in the course overview block&lt;br /&gt;
* MDL-63058 - Option to star/unstar courses in the course overview block&lt;br /&gt;
* MDL-63064 - New [https://docs.moodle.org/36/en/Starred_courses_block Starred courses block]&lt;br /&gt;
* MDL-63352 - Dashboard retains user preferences for view options&lt;br /&gt;
* MDL-63793 - Course overview block retains user preferences for the number of courses to show&lt;br /&gt;
* MDL-61161 - Grace period when displaying &amp;quot;In progress&amp;quot; courses in course overview block&lt;br /&gt;
* MDL-63040 - Removal of Dashboard page header&lt;br /&gt;
&lt;br /&gt;
===GDPR and Privacy===&lt;br /&gt;
Note that some of these GDPR improvements have also been backported to Moodle 3.5.3,  3.4.6 and 3.3.9.&lt;br /&gt;
&lt;br /&gt;
* MDL-63116 - Data requests bulk actions&lt;br /&gt;
* MDL-62309 - Option to make site policies required or optional&lt;br /&gt;
* MDL-61652 - Capabilities for controlling who can download SAR data&lt;br /&gt;
* MDL-62563 - Data deletion of existing deleted users&lt;br /&gt;
* MDL-63897 - Pre-processing stage removed from data requests process&lt;br /&gt;
* MDL-62558 - Data retention summary (read-only)&lt;br /&gt;
* MDL-63726 - Option to remove the &amp;quot;Data retention summary&amp;quot; link in the footer&lt;br /&gt;
* MDL-62491 - HTML data request export format&lt;br /&gt;
* MDL-63401 - User expiry improvements&lt;br /&gt;
* MDL-63619 - Data purpose and category inheritance improvements&lt;br /&gt;
* MDL-62560 - Different data retention strategies for different roles in a purpose&lt;br /&gt;
* MDL-62554 - Ability to configure data registry to use module type defaults&lt;br /&gt;
* MDL-63009 - Site mentioned in email notifications of data requests&lt;br /&gt;
* MDL-6074 - Option to hide your name in the [https://docs.moodle.org/36/en/Online_users_block online users block]&lt;br /&gt;
&lt;br /&gt;
===Messaging===&lt;br /&gt;
* &#039;&#039;&#039;MDL-57272 and MDL-63280 - Group messaging&#039;&#039;&#039;&lt;br /&gt;
* MDL-63303 - New messaging UI with [https://docs.moodle.org/36/en/Messaging messaging drawer]&lt;br /&gt;
* MDL-63279 - Option to [https://docs.moodle.org/36/en/Messaging_settings disable site-wide messaging]&lt;br /&gt;
* MDL-63214 - Privacy setting for restricting who can message you&lt;br /&gt;
* MDL-63213 - Option to star messaging conversations&lt;br /&gt;
* MDL-63283 - Notifications not sent for group conversations&lt;br /&gt;
* MDL-63281 - Group members synchronised with messaging conversations members&lt;br /&gt;
&lt;br /&gt;
===Assignment===&lt;br /&gt;
* MDL-27520 - Assignment feedback can include media or other files&lt;br /&gt;
&lt;br /&gt;
===Quiz===&lt;br /&gt;
* MDL-62610 - Improved quiz statistics report usability for randomized questions&lt;br /&gt;
* MDL-62708 - Option to add ID numbers to questions and question categories&lt;br /&gt;
* MDL-63738 - Single questions can be exported from the question bank&lt;br /&gt;
&lt;br /&gt;
===Workshop===&lt;br /&gt;
* MDL-60820 - Teachers can specify workshop submission types&lt;br /&gt;
&lt;br /&gt;
===Repositories===&lt;br /&gt;
* MDL-58943 - Nextcloud integration, with a [https://docs.moodle.org/36/en/Nextcloud_repository Nextcloud repository] and [https://docs.moodle.org/36/en/OAuth_2_Nextcloud_service OAuth 2 Nextcloud service]&lt;br /&gt;
&lt;br /&gt;
===Open Badges===&lt;br /&gt;
* MDL-58454 - Support for Open Badges v2.0&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
* MDL-54035 - Performance improvements to cache flags&lt;br /&gt;
* MDL-47962 - Glossary auto-linking filter performance improvements&lt;br /&gt;
&lt;br /&gt;
===Usability improvements===&lt;br /&gt;
* MDL-51177 - atto_htmlplus implemented to improve Atto editor HTML indenting&lt;br /&gt;
* MDL-45170 - Copy and paste of images from one WYSIWYG window to another&lt;br /&gt;
* MDL-61388 - Forum actions announced by screen reader when completed&lt;br /&gt;
* MDL-62899 - Global search displays a relevant icon next to link in results&lt;br /&gt;
* MDL-46415 - SVG/high resolution emoticons&lt;br /&gt;
* MDL-58000 - Larger badge images are used&lt;br /&gt;
&lt;br /&gt;
===Experimental===&lt;br /&gt;
* MDL-53566 - [https://docs.moodle.org/36/en/Context_freezing Context freezing] - setting read-only access for categories, courses, activities and their content&lt;br /&gt;
&lt;br /&gt;
==Other highlights==&lt;br /&gt;
&lt;br /&gt;
===Functional changes===&lt;br /&gt;
* MDL-17943 - &#039;Resend confirmation email&#039; button on login page&lt;br /&gt;
* MDL-14274 - IF conditions in grade calculations&lt;br /&gt;
* MDL-37624 - Calendar entries location support&lt;br /&gt;
* MDL-36754 - Images are displayed in forum notification emails&lt;br /&gt;
* MDL-59259 - Course format options may be specified in upload courses CSV file&lt;br /&gt;
* MDL-41265 - Page resource option to show/hide &amp;quot;Last modified&amp;quot;&lt;br /&gt;
* MDL-61378 - Forum post HTML structure improvements&lt;br /&gt;
* MDL-59454 - Option to download the list of course participants&lt;br /&gt;
* MDL-60520 - Analytics models can use different machine learning backends&lt;br /&gt;
* MDL-61573 - User menu: customusermenuitems map Font Awesome icons for non pix/t folders&lt;br /&gt;
* MDL-62320 - JSON added to the default MIME types list&lt;br /&gt;
* MDL-63431 - Atto media plugin title global attribute support&lt;br /&gt;
* MDL-60435 - Shibboleth authentication identity providers&lt;br /&gt;
* MDL-59169 - Grader report saves after edit with multiple tabs&lt;br /&gt;
* MDL-62960 - Drag and drop of course events respects the course start date&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=378731 MSA-18-0020] Login CSRF vulnerability in login form. Note that this fix has previously been disclosed following the release of Moodle 3.5.3, 3.4.6, 3.3.9 and 3.1.15.&lt;br /&gt;
&lt;br /&gt;
===For administrators===&lt;br /&gt;
* MDL-62334 - &#039;Add a new course&#039; link in Site administration&lt;br /&gt;
* MDL-63253 - Admin search results provide location of the found matching page&lt;br /&gt;
* MDL-63772 - Capability to control use of Atto Record RTC&lt;br /&gt;
* MDL-63708 - New blocks supported by the mobile app can be disabled&lt;br /&gt;
* MDL-52953 - Legacy log store deprecation&lt;br /&gt;
* MDL-59429 - Log changes to site administrators&lt;br /&gt;
* MDL-62651 - adhoc task runner&lt;br /&gt;
* MDL-62777 - Site upgrades via CLI display new default settings&lt;br /&gt;
* MDL-63603 - Indian Rupee added to [https://docs.moodle.org/36/en/PayPal_enrolment PayPal enrolment] currencies&lt;br /&gt;
* MDL-60514 - Set Path to PHP CLI in order to display &#039;Run now&#039; for [https://docs.moodle.org/36/en/Scheduled_tasks Scheduled tasks]&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
* MDL-55188 - Old Events API final deprecation&lt;br /&gt;
* MDL-54741 - Phase 2 of deprecation of functions in lib/deprecatedlib.php&lt;br /&gt;
* MDL-51803 - Reusable element for drag and drop sortable table or list&lt;br /&gt;
* MDL-63329 - memcache session handler removal&lt;br /&gt;
* MDL-63658 - New Favourites subsystem&lt;br /&gt;
* MDL-63729 - Badges web services return new fields and data added by the Open Badges v2.0 specification&lt;br /&gt;
* MDL-50812 - core_useragent::get_browser_version_classes distinguishes between different browsers&lt;br /&gt;
&lt;br /&gt;
===Privacy API update===&lt;br /&gt;
&lt;br /&gt;
In addition to existing requirements, any plugin which implements the plugin provider interface must also implement the &amp;lt;tt&amp;gt;\core_privacy\local\request\core_userlist_provider&amp;lt;/tt&amp;gt; interface. Two new methods need to be implemented:&lt;br /&gt;
&lt;br /&gt;
* [[Privacy API#Retrieving_the_users_in_a_context|get_users_in_context()]]&lt;br /&gt;
* [[Privacy API#Delete_personal_information_for_several_users_in_a_specific_context|delete_data_for_users()]]&lt;br /&gt;
&lt;br /&gt;
However, the two above methods are not required for plugins that [[Privacy_API#Plugins_which_do_not_store_personal_data|implement the &amp;lt;tt&amp;gt;null_provider&amp;lt;/tt&amp;gt;]] only (i.e. which do not store personal data).&lt;br /&gt;
&lt;br /&gt;
Note that these changes are also required for latest Moodle 3.4.6 and 3.5.3 versions.&lt;br /&gt;
&lt;br /&gt;
===Behat scenario files===&lt;br /&gt;
&lt;br /&gt;
* MDL-57281 - The behat step &amp;lt;code&amp;gt;I navigate to &amp;quot;ITEM&amp;quot; node in &amp;quot;MAINNODE &amp;gt; PATH&amp;quot;&amp;lt;/code&amp;gt; has been deprecated and throws an exception with details on how to replace it. The recommended replacement steps work in all recent Moodle versions. The updated Behat will pass with Moodle 3.4 too.&lt;br /&gt;
&lt;br /&gt;
===Login token===&lt;br /&gt;
&lt;br /&gt;
If your plugin provides an alternative login form (e.g. it is a theme replacing the default login form template / renderer), the login form must include a new login token field. For details of required changes, see [[Login token]]. Note that this also affects latest stable branches too.&lt;br /&gt;
&lt;br /&gt;
===New core functions===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tt&amp;gt;userdate_htmltime()&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Component APIs upgrades===&lt;br /&gt;
&lt;br /&gt;
Please refer to the upgrade.txt files in the relevant component directory for changes in this particular Moodle release.&lt;br /&gt;
&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/admin/tool/log/upgrade.txt admin/tool/log/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/admin/tool/upgrade.txt admin/tool/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/auth/shibboleth/upgrade.txt auth/shibboleth/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/auth/upgrade.txt auth/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/badges/upgrade.txt badges/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/blocks/upgrade.txt blocks/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/cache/upgrade.txt cache/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/calendar/upgrade.txt calendar/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/course/format/upgrade.txt course/format/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/course/upgrade.txt course/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/enrol/upgrade.txt enrol/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/filter/upgrade.txt filter/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/grading/form/upgrade.txt grade/grading/form/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/report/upgrade.txt grade/report/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/upgrade.txt grade/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/lib/upgrade.txt lib/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/media/upgrade.txt media/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/message/upgrade.txt message/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/assign/upgrade.txt mod/assign/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/feedback/upgrade.txt mod/feedback/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/forum/upgrade.txt mod/forum/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/quiz/upgrade.txt mod/quiz/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/scorm/report/basic/upgrade.txt mod/scorm/report/basic/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/scorm/upgrade.txt mod/scorm/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/upgrade.txt mod/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/workshop/upgrade.txt mod/workshop/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/question/format/upgrade.txt question/format/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/report/upgrade.txt report/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/tag/upgrade.txt tag/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/theme/upgrade.txt theme/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/user/upgrade.txt user/upgrade.txt]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.5 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.6]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.6]]&lt;br /&gt;
[[es:Notas de Moodle 3.6]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.6_release_notes&amp;diff=55207</id>
		<title>Moodle 3.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.6_release_notes&amp;diff=55207"/>
		<updated>2018-12-04T15:37:29Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* For administrators */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: 3 December 2018&lt;br /&gt;
&lt;br /&gt;
Here is [https://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%223.6%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.6].&lt;br /&gt;
&lt;br /&gt;
See our [https://docs.moodle.org/36/en/New_features New Features page] for a more user-friendly introduction to Moodle 3.6 with screenshots.&lt;br /&gt;
&lt;br /&gt;
{{Note|If you are upgrading from a previous version, please see [[:en:Upgrading|Upgrading]] in the user docs. &#039;&#039;In particular, for sites using a custom theme or login form, from 3.6 onwards, the login form must include a new login token field. See [[Login token]] for details.}}&lt;br /&gt;
&lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just the minimum supported versions. We recommend keeping all of your software and operating systems up-to-date.&lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 3.1 or later&lt;br /&gt;
* PHP version: minimum PHP 7.0.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.3&#039;&#039;. PHP 7.1.x and 7.2.x are supported too. [[Moodle and PHP7|PHP 7.3.x support]] is being implemented (@ MDL-63420) and &#039;&#039;&#039;not ready for production&#039;&#039;&#039; with this release.&lt;br /&gt;
* PHP extension &#039;&#039;&#039;intl&#039;&#039;&#039; is required since Moodle 3.4 (it was recommended in 2.0 onwards) &lt;br /&gt;
&lt;br /&gt;
=== Database requirements ===&lt;br /&gt;
&lt;br /&gt;
Moodle supports the following database servers. Again, version numbers are just the minimum supported version. We recommend running the latest stable version of any software.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Database&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
| 9.4&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.6&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.microsoft.com/en-us/server-cloud/products/sql-server/ Microsoft SQL Server]&lt;br /&gt;
| 2008&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.oracle.com/us/products/database/overview/index.html Oracle Database]&lt;br /&gt;
| 11.2&lt;br /&gt;
| Latest&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Client requirements==&lt;br /&gt;
&lt;br /&gt;
=== Browser support ===&lt;br /&gt;
Moodle is compatible with any standards compliant web browser. We regularly test Moodle with the following browsers:&lt;br /&gt;
&lt;br /&gt;
Desktop:&lt;br /&gt;
* Chrome&lt;br /&gt;
* Firefox&lt;br /&gt;
* Safari&lt;br /&gt;
* Edge&lt;br /&gt;
* Internet Explorer&lt;br /&gt;
&lt;br /&gt;
Mobile:&lt;br /&gt;
* MobileSafari&lt;br /&gt;
* Google Chrome&lt;br /&gt;
&lt;br /&gt;
For the best experience and optimum security, we recommend that you keep your browser up to date. https://whatbrowser.org&lt;br /&gt;
&lt;br /&gt;
Note: Legacy browsers with known compatibility issues with Moodle 3.6:&lt;br /&gt;
* Internet Explorer 10 and below&lt;br /&gt;
* Safari 7 and below&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===Dashboard and Course overview===&lt;br /&gt;
* MDL-63044 and MDL-63337 - New [https://docs.moodle.org/36/en/Course_overview Course overview] and [https://docs.moodle.org/36/en/Timeline_block Timeline block]&lt;br /&gt;
* MDL-63062 - New [https://docs.moodle.org/36/en/Recently_accessed_courses_block Recently accessed courses block]&lt;br /&gt;
* MDL-63063 - New [https://docs.moodle.org/36/en/Recently_accessed_items_block Recently accessed items block]&lt;br /&gt;
* MDL-63457 - Option to hide courses in the course overview block&lt;br /&gt;
* MDL-63058 - Option to star/unstar courses in the course overview block&lt;br /&gt;
* MDL-63064 - New [https://docs.moodle.org/36/en/Starred_courses_block Starred courses block]&lt;br /&gt;
* MDL-63352 - Dashboard retains user preferences for view options&lt;br /&gt;
* MDL-63793 - Course overview block retains user preferences for the number of courses to show&lt;br /&gt;
* MDL-61161 - Grace period when displaying &amp;quot;In progress&amp;quot; courses in course overview block&lt;br /&gt;
* MDL-63040 - Removal of Dashboard page header&lt;br /&gt;
&lt;br /&gt;
===GDPR and Privacy===&lt;br /&gt;
Note that some of these GDPR improvements have also been backported to Moodle 3.5.3,  3.4.6 and 3.3.9.&lt;br /&gt;
&lt;br /&gt;
* MDL-63116 - Data requests bulk actions&lt;br /&gt;
* MDL-62309 - Option to make site policies required or optional&lt;br /&gt;
* MDL-61652 - Capabilities for controlling who can download SAR data&lt;br /&gt;
* MDL-62563 - Data deletion of existing deleted users&lt;br /&gt;
* MDL-63897 - Pre-processing stage removed from data requests process&lt;br /&gt;
* MDL-62558 - Data retention summary (read-only)&lt;br /&gt;
* MDL-63726 - Option to remove the &amp;quot;Data retention summary&amp;quot; link in the footer&lt;br /&gt;
* MDL-62491 - HTML data request export format&lt;br /&gt;
* MDL-63401 - User expiry improvements&lt;br /&gt;
* MDL-63619 - Data purpose and category inheritance improvements&lt;br /&gt;
* MDL-62560 - Different data retention strategies for different roles in a purpose&lt;br /&gt;
* MDL-62554 - Ability to configure data registry to use module type defaults&lt;br /&gt;
* MDL-63009 - Site mentioned in email notifications of data requests&lt;br /&gt;
* MDL-6074 - Option to hide your name in the [https://docs.moodle.org/36/en/Online_users_block online users block]&lt;br /&gt;
&lt;br /&gt;
===Messaging===&lt;br /&gt;
* &#039;&#039;&#039;MDL-57272 and MDL-63280 - Group messaging&#039;&#039;&#039;&lt;br /&gt;
* MDL-63303 - New messaging UI with [https://docs.moodle.org/36/en/Messaging messaging drawer]&lt;br /&gt;
* MDL-63279 - Option to [https://docs.moodle.org/36/en/Messaging_settings disable site-wide messaging]&lt;br /&gt;
* MDL-63214 - Privacy setting for restricting who can message you&lt;br /&gt;
* MDL-63213 - Option to star messaging conversations&lt;br /&gt;
* MDL-63283 - Notifications not sent for group conversations&lt;br /&gt;
* MDL-63281 - Group members synchronised with messaging conversations members&lt;br /&gt;
&lt;br /&gt;
===Assignment===&lt;br /&gt;
* MDL-27520 - Assignment feedback can include media or other files&lt;br /&gt;
&lt;br /&gt;
===Quiz===&lt;br /&gt;
* MDL-62610 - Improved quiz statistics report usability for randomized questions&lt;br /&gt;
* MDL-62708 - Option to add ID numbers to questions and question categories&lt;br /&gt;
* MDL-63738 - Single questions can be exported from the question bank&lt;br /&gt;
&lt;br /&gt;
===Workshop===&lt;br /&gt;
* MDL-60820 - Teachers can specify workshop submission types&lt;br /&gt;
&lt;br /&gt;
===Repositories===&lt;br /&gt;
* MDL-58943 - Nextcloud integration, with a [https://docs.moodle.org/36/en/Nextcloud_repository Nextcloud repository] and [https://docs.moodle.org/36/en/OAuth_2_Nextcloud_service OAuth 2 Nextcloud service]&lt;br /&gt;
&lt;br /&gt;
===Open Badges===&lt;br /&gt;
* MDL-58454 - Support for Open Badges v2.0&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
* MDL-54035 - Performance improvements to cache flags&lt;br /&gt;
* MDL-47962 - Glossary auto-linking filter performance improvements&lt;br /&gt;
&lt;br /&gt;
===Usability improvements===&lt;br /&gt;
* MDL-51177 - atto_htmlplus implemented to improve Atto editor HTML indenting&lt;br /&gt;
* MDL-45170 - Copy and paste of images from one WYSIWYG window to another&lt;br /&gt;
* MDL-61388 - Forum actions announced by screen reader when completed&lt;br /&gt;
* MDL-62899 - Global search displays a relevant icon next to link in results&lt;br /&gt;
* MDL-46415 - SVG/high resolution emoticons&lt;br /&gt;
* MDL-58000 - Larger badge images are used&lt;br /&gt;
&lt;br /&gt;
===Experimental===&lt;br /&gt;
* MDL-53566 - [https://docs.moodle.org/36/en/Context_freezing Context freezing] - setting read-only access for categories, courses, activities and their content&lt;br /&gt;
&lt;br /&gt;
==Other highlights==&lt;br /&gt;
&lt;br /&gt;
===Functional changes===&lt;br /&gt;
* MDL-17943 - &#039;Resend confirmation email&#039; button on login page&lt;br /&gt;
* MDL-14274 - IF conditions in grade calculations&lt;br /&gt;
* MDL-37624 - Calendar entries location support&lt;br /&gt;
* MDL-36754 - Images are displayed in forum notification emails&lt;br /&gt;
* MDL-59259 - Course format options may be specified in upload courses CSV file&lt;br /&gt;
* MDL-41265 - Page resource option to show/hide &amp;quot;Last modified&amp;quot;&lt;br /&gt;
* MDL-61378 - Forum post HTML structure improvements&lt;br /&gt;
* MDL-59454 - Option to download the list of course participants&lt;br /&gt;
* MDL-60520 - Analytics models can use different machine learning backends&lt;br /&gt;
* MDL-61573 - User menu: customusermenuitems map Font Awesome icons for non pix/t folders&lt;br /&gt;
* MDL-62320 - JSON added to the default MIME types list&lt;br /&gt;
* MDL-63431 - Atto media plugin title global attribute support&lt;br /&gt;
* MDL-60435 - Shibboleth authentication identity providers&lt;br /&gt;
* MDL-59169 - Grader report saves after edit with multiple tabs&lt;br /&gt;
* MDL-62960 - Drag and drop of course events respects the course start date&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=378731 MSA-18-0020] Login CSRF vulnerability in login form. Note that this fix has previously been disclosed following the release of Moodle 3.5.3, 3.4.6, 3.3.9 and 3.1.15.&lt;br /&gt;
&lt;br /&gt;
===For administrators===&lt;br /&gt;
* MDL-62334 - &#039;Add a new course&#039; link in Site administration&lt;br /&gt;
* MDL-63253 - Admin search results provide location of the found matching page&lt;br /&gt;
* MDL-63772 - Capability to control use of Atto Record RTC&lt;br /&gt;
* MDL-63708 - New blocks supported by the mobile app can be disabled&lt;br /&gt;
* MDL-52953 - Legacy log store deprecation&lt;br /&gt;
* MDL-59429 - Log changes to site administrators&lt;br /&gt;
* MDL-62651 - adhoc task runner&lt;br /&gt;
* MDL-62777 - Site upgrades via CLI display new default settings&lt;br /&gt;
* MDL-63603 - Indian Rupee added to [https://docs.moodle.org/36/en/PayPal_enrolment PayPal enrolment] currencies&lt;br /&gt;
* MDL-60514 - Set Path to PHP CLI in order to display &#039;Run now&#039; for [Scheduled_tasks]&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
* MDL-55188 - Old Events API final deprecation&lt;br /&gt;
* MDL-54741 - Phase 2 of deprecation of functions in lib/deprecatedlib.php&lt;br /&gt;
* MDL-51803 - Reusable element for drag and drop sortable table or list&lt;br /&gt;
* MDL-63329 - memcache session handler removal&lt;br /&gt;
* MDL-63658 - New Favourites subsystem&lt;br /&gt;
* MDL-63729 - Badges web services return new fields and data added by the Open Badges v2.0 specification&lt;br /&gt;
* MDL-50812 - core_useragent::get_browser_version_classes distinguishes between different browsers&lt;br /&gt;
&lt;br /&gt;
===Privacy API update===&lt;br /&gt;
&lt;br /&gt;
In addition to existing requirements, any plugin which implements the plugin provider interface must also implement the &amp;lt;tt&amp;gt;\core_privacy\local\request\core_userlist_provider&amp;lt;/tt&amp;gt; interface. Two new methods need to be implemented:&lt;br /&gt;
&lt;br /&gt;
* [[Privacy API#Retrieving_the_users_in_a_context|get_users_in_context()]]&lt;br /&gt;
* [[Privacy API#Delete_personal_information_for_several_users_in_a_specific_context|delete_data_for_users()]]&lt;br /&gt;
&lt;br /&gt;
However, the two above methods are not required for plugins that [[Privacy_API#Plugins_which_do_not_store_personal_data|implement the &amp;lt;tt&amp;gt;null_provider&amp;lt;/tt&amp;gt;]] only (i.e. which do not store personal data).&lt;br /&gt;
&lt;br /&gt;
Note that these changes are also required for latest Moodle 3.4.6 and 3.5.3 versions.&lt;br /&gt;
&lt;br /&gt;
===Behat scenario files===&lt;br /&gt;
&lt;br /&gt;
* MDL-57281 - The behat step &amp;lt;code&amp;gt;I navigate to &amp;quot;ITEM&amp;quot; node in &amp;quot;MAINNODE &amp;gt; PATH&amp;quot;&amp;lt;/code&amp;gt; has been deprecated and throws an exception with details on how to replace it. The recommended replacement steps work in all recent Moodle versions. The updated Behat will pass with Moodle 3.4 too.&lt;br /&gt;
&lt;br /&gt;
===Login token===&lt;br /&gt;
&lt;br /&gt;
If your plugin provides an alternative login form (e.g. it is a theme replacing the default login form template / renderer), the login form must include a new login token field. For details of required changes, see [[Login token]]. Note that this also affects latest stable branches too.&lt;br /&gt;
&lt;br /&gt;
===New core functions===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tt&amp;gt;userdate_htmltime()&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Component APIs upgrades===&lt;br /&gt;
&lt;br /&gt;
Please refer to the upgrade.txt files in the relevant component directory for changes in this particular Moodle release.&lt;br /&gt;
&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/admin/tool/log/upgrade.txt admin/tool/log/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/admin/tool/upgrade.txt admin/tool/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/auth/shibboleth/upgrade.txt auth/shibboleth/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/auth/upgrade.txt auth/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/badges/upgrade.txt badges/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/blocks/upgrade.txt blocks/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/cache/upgrade.txt cache/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/calendar/upgrade.txt calendar/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/course/format/upgrade.txt course/format/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/course/upgrade.txt course/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/enrol/upgrade.txt enrol/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/filter/upgrade.txt filter/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/grading/form/upgrade.txt grade/grading/form/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/report/upgrade.txt grade/report/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/upgrade.txt grade/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/lib/upgrade.txt lib/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/media/upgrade.txt media/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/message/upgrade.txt message/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/assign/upgrade.txt mod/assign/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/feedback/upgrade.txt mod/feedback/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/forum/upgrade.txt mod/forum/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/quiz/upgrade.txt mod/quiz/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/scorm/report/basic/upgrade.txt mod/scorm/report/basic/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/scorm/upgrade.txt mod/scorm/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/upgrade.txt mod/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/workshop/upgrade.txt mod/workshop/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/question/format/upgrade.txt question/format/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/report/upgrade.txt report/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/tag/upgrade.txt tag/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/theme/upgrade.txt theme/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/user/upgrade.txt user/upgrade.txt]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.5 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.6]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.6]]&lt;br /&gt;
[[es:Notas de Moodle 3.6]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.6_release_notes&amp;diff=55206</id>
		<title>Moodle 3.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.6_release_notes&amp;diff=55206"/>
		<updated>2018-12-04T15:36:21Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* For administrators */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: 3 December 2018&lt;br /&gt;
&lt;br /&gt;
Here is [https://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%223.6%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.6].&lt;br /&gt;
&lt;br /&gt;
See our [https://docs.moodle.org/36/en/New_features New Features page] for a more user-friendly introduction to Moodle 3.6 with screenshots.&lt;br /&gt;
&lt;br /&gt;
{{Note|If you are upgrading from a previous version, please see [[:en:Upgrading|Upgrading]] in the user docs. &#039;&#039;In particular, for sites using a custom theme or login form, from 3.6 onwards, the login form must include a new login token field. See [[Login token]] for details.}}&lt;br /&gt;
&lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just the minimum supported versions. We recommend keeping all of your software and operating systems up-to-date.&lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 3.1 or later&lt;br /&gt;
* PHP version: minimum PHP 7.0.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.3&#039;&#039;. PHP 7.1.x and 7.2.x are supported too. [[Moodle and PHP7|PHP 7.3.x support]] is being implemented (@ MDL-63420) and &#039;&#039;&#039;not ready for production&#039;&#039;&#039; with this release.&lt;br /&gt;
* PHP extension &#039;&#039;&#039;intl&#039;&#039;&#039; is required since Moodle 3.4 (it was recommended in 2.0 onwards) &lt;br /&gt;
&lt;br /&gt;
=== Database requirements ===&lt;br /&gt;
&lt;br /&gt;
Moodle supports the following database servers. Again, version numbers are just the minimum supported version. We recommend running the latest stable version of any software.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Database&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
| 9.4&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.6&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.microsoft.com/en-us/server-cloud/products/sql-server/ Microsoft SQL Server]&lt;br /&gt;
| 2008&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.oracle.com/us/products/database/overview/index.html Oracle Database]&lt;br /&gt;
| 11.2&lt;br /&gt;
| Latest&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Client requirements==&lt;br /&gt;
&lt;br /&gt;
=== Browser support ===&lt;br /&gt;
Moodle is compatible with any standards compliant web browser. We regularly test Moodle with the following browsers:&lt;br /&gt;
&lt;br /&gt;
Desktop:&lt;br /&gt;
* Chrome&lt;br /&gt;
* Firefox&lt;br /&gt;
* Safari&lt;br /&gt;
* Edge&lt;br /&gt;
* Internet Explorer&lt;br /&gt;
&lt;br /&gt;
Mobile:&lt;br /&gt;
* MobileSafari&lt;br /&gt;
* Google Chrome&lt;br /&gt;
&lt;br /&gt;
For the best experience and optimum security, we recommend that you keep your browser up to date. https://whatbrowser.org&lt;br /&gt;
&lt;br /&gt;
Note: Legacy browsers with known compatibility issues with Moodle 3.6:&lt;br /&gt;
* Internet Explorer 10 and below&lt;br /&gt;
* Safari 7 and below&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===Dashboard and Course overview===&lt;br /&gt;
* MDL-63044 and MDL-63337 - New [https://docs.moodle.org/36/en/Course_overview Course overview] and [https://docs.moodle.org/36/en/Timeline_block Timeline block]&lt;br /&gt;
* MDL-63062 - New [https://docs.moodle.org/36/en/Recently_accessed_courses_block Recently accessed courses block]&lt;br /&gt;
* MDL-63063 - New [https://docs.moodle.org/36/en/Recently_accessed_items_block Recently accessed items block]&lt;br /&gt;
* MDL-63457 - Option to hide courses in the course overview block&lt;br /&gt;
* MDL-63058 - Option to star/unstar courses in the course overview block&lt;br /&gt;
* MDL-63064 - New [https://docs.moodle.org/36/en/Starred_courses_block Starred courses block]&lt;br /&gt;
* MDL-63352 - Dashboard retains user preferences for view options&lt;br /&gt;
* MDL-63793 - Course overview block retains user preferences for the number of courses to show&lt;br /&gt;
* MDL-61161 - Grace period when displaying &amp;quot;In progress&amp;quot; courses in course overview block&lt;br /&gt;
* MDL-63040 - Removal of Dashboard page header&lt;br /&gt;
&lt;br /&gt;
===GDPR and Privacy===&lt;br /&gt;
Note that some of these GDPR improvements have also been backported to Moodle 3.5.3,  3.4.6 and 3.3.9.&lt;br /&gt;
&lt;br /&gt;
* MDL-63116 - Data requests bulk actions&lt;br /&gt;
* MDL-62309 - Option to make site policies required or optional&lt;br /&gt;
* MDL-61652 - Capabilities for controlling who can download SAR data&lt;br /&gt;
* MDL-62563 - Data deletion of existing deleted users&lt;br /&gt;
* MDL-63897 - Pre-processing stage removed from data requests process&lt;br /&gt;
* MDL-62558 - Data retention summary (read-only)&lt;br /&gt;
* MDL-63726 - Option to remove the &amp;quot;Data retention summary&amp;quot; link in the footer&lt;br /&gt;
* MDL-62491 - HTML data request export format&lt;br /&gt;
* MDL-63401 - User expiry improvements&lt;br /&gt;
* MDL-63619 - Data purpose and category inheritance improvements&lt;br /&gt;
* MDL-62560 - Different data retention strategies for different roles in a purpose&lt;br /&gt;
* MDL-62554 - Ability to configure data registry to use module type defaults&lt;br /&gt;
* MDL-63009 - Site mentioned in email notifications of data requests&lt;br /&gt;
* MDL-6074 - Option to hide your name in the [https://docs.moodle.org/36/en/Online_users_block online users block]&lt;br /&gt;
&lt;br /&gt;
===Messaging===&lt;br /&gt;
* &#039;&#039;&#039;MDL-57272 and MDL-63280 - Group messaging&#039;&#039;&#039;&lt;br /&gt;
* MDL-63303 - New messaging UI with [https://docs.moodle.org/36/en/Messaging messaging drawer]&lt;br /&gt;
* MDL-63279 - Option to [https://docs.moodle.org/36/en/Messaging_settings disable site-wide messaging]&lt;br /&gt;
* MDL-63214 - Privacy setting for restricting who can message you&lt;br /&gt;
* MDL-63213 - Option to star messaging conversations&lt;br /&gt;
* MDL-63283 - Notifications not sent for group conversations&lt;br /&gt;
* MDL-63281 - Group members synchronised with messaging conversations members&lt;br /&gt;
&lt;br /&gt;
===Assignment===&lt;br /&gt;
* MDL-27520 - Assignment feedback can include media or other files&lt;br /&gt;
&lt;br /&gt;
===Quiz===&lt;br /&gt;
* MDL-62610 - Improved quiz statistics report usability for randomized questions&lt;br /&gt;
* MDL-62708 - Option to add ID numbers to questions and question categories&lt;br /&gt;
* MDL-63738 - Single questions can be exported from the question bank&lt;br /&gt;
&lt;br /&gt;
===Workshop===&lt;br /&gt;
* MDL-60820 - Teachers can specify workshop submission types&lt;br /&gt;
&lt;br /&gt;
===Repositories===&lt;br /&gt;
* MDL-58943 - Nextcloud integration, with a [https://docs.moodle.org/36/en/Nextcloud_repository Nextcloud repository] and [https://docs.moodle.org/36/en/OAuth_2_Nextcloud_service OAuth 2 Nextcloud service]&lt;br /&gt;
&lt;br /&gt;
===Open Badges===&lt;br /&gt;
* MDL-58454 - Support for Open Badges v2.0&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
* MDL-54035 - Performance improvements to cache flags&lt;br /&gt;
* MDL-47962 - Glossary auto-linking filter performance improvements&lt;br /&gt;
&lt;br /&gt;
===Usability improvements===&lt;br /&gt;
* MDL-51177 - atto_htmlplus implemented to improve Atto editor HTML indenting&lt;br /&gt;
* MDL-45170 - Copy and paste of images from one WYSIWYG window to another&lt;br /&gt;
* MDL-61388 - Forum actions announced by screen reader when completed&lt;br /&gt;
* MDL-62899 - Global search displays a relevant icon next to link in results&lt;br /&gt;
* MDL-46415 - SVG/high resolution emoticons&lt;br /&gt;
* MDL-58000 - Larger badge images are used&lt;br /&gt;
&lt;br /&gt;
===Experimental===&lt;br /&gt;
* MDL-53566 - [https://docs.moodle.org/36/en/Context_freezing Context freezing] - setting read-only access for categories, courses, activities and their content&lt;br /&gt;
&lt;br /&gt;
==Other highlights==&lt;br /&gt;
&lt;br /&gt;
===Functional changes===&lt;br /&gt;
* MDL-17943 - &#039;Resend confirmation email&#039; button on login page&lt;br /&gt;
* MDL-14274 - IF conditions in grade calculations&lt;br /&gt;
* MDL-37624 - Calendar entries location support&lt;br /&gt;
* MDL-36754 - Images are displayed in forum notification emails&lt;br /&gt;
* MDL-59259 - Course format options may be specified in upload courses CSV file&lt;br /&gt;
* MDL-41265 - Page resource option to show/hide &amp;quot;Last modified&amp;quot;&lt;br /&gt;
* MDL-61378 - Forum post HTML structure improvements&lt;br /&gt;
* MDL-59454 - Option to download the list of course participants&lt;br /&gt;
* MDL-60520 - Analytics models can use different machine learning backends&lt;br /&gt;
* MDL-61573 - User menu: customusermenuitems map Font Awesome icons for non pix/t folders&lt;br /&gt;
* MDL-62320 - JSON added to the default MIME types list&lt;br /&gt;
* MDL-63431 - Atto media plugin title global attribute support&lt;br /&gt;
* MDL-60435 - Shibboleth authentication identity providers&lt;br /&gt;
* MDL-59169 - Grader report saves after edit with multiple tabs&lt;br /&gt;
* MDL-62960 - Drag and drop of course events respects the course start date&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=378731 MSA-18-0020] Login CSRF vulnerability in login form. Note that this fix has previously been disclosed following the release of Moodle 3.5.3, 3.4.6, 3.3.9 and 3.1.15.&lt;br /&gt;
&lt;br /&gt;
===For administrators===&lt;br /&gt;
* MDL-62334 - &#039;Add a new course&#039; link in Site administration&lt;br /&gt;
* MDL-63253 - Admin search results provide location of the found matching page&lt;br /&gt;
* MDL-63772 - Capability to control use of Atto Record RTC&lt;br /&gt;
* MDL-63708 - New blocks supported by the mobile app can be disabled&lt;br /&gt;
* MDL-52953 - Legacy log store deprecation&lt;br /&gt;
* MDL-59429 - Log changes to site administrators&lt;br /&gt;
* MDL-62651 - adhoc task runner&lt;br /&gt;
* MDL-62777 - Site upgrades via CLI display new default settings&lt;br /&gt;
* MDL-63603 - Indian Rupee added to [https://docs.moodle.org/36/en/PayPal_enrolment PayPal enrolment] currencies&lt;br /&gt;
* MDL-60514 - Set Path to PHP CLI in order to display &#039;Run now&#039; for [[Scheduled_tasks]]&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
* MDL-55188 - Old Events API final deprecation&lt;br /&gt;
* MDL-54741 - Phase 2 of deprecation of functions in lib/deprecatedlib.php&lt;br /&gt;
* MDL-51803 - Reusable element for drag and drop sortable table or list&lt;br /&gt;
* MDL-63329 - memcache session handler removal&lt;br /&gt;
* MDL-63658 - New Favourites subsystem&lt;br /&gt;
* MDL-63729 - Badges web services return new fields and data added by the Open Badges v2.0 specification&lt;br /&gt;
* MDL-50812 - core_useragent::get_browser_version_classes distinguishes between different browsers&lt;br /&gt;
&lt;br /&gt;
===Privacy API update===&lt;br /&gt;
&lt;br /&gt;
In addition to existing requirements, any plugin which implements the plugin provider interface must also implement the &amp;lt;tt&amp;gt;\core_privacy\local\request\core_userlist_provider&amp;lt;/tt&amp;gt; interface. Two new methods need to be implemented:&lt;br /&gt;
&lt;br /&gt;
* [[Privacy API#Retrieving_the_users_in_a_context|get_users_in_context()]]&lt;br /&gt;
* [[Privacy API#Delete_personal_information_for_several_users_in_a_specific_context|delete_data_for_users()]]&lt;br /&gt;
&lt;br /&gt;
However, the two above methods are not required for plugins that [[Privacy_API#Plugins_which_do_not_store_personal_data|implement the &amp;lt;tt&amp;gt;null_provider&amp;lt;/tt&amp;gt;]] only (i.e. which do not store personal data).&lt;br /&gt;
&lt;br /&gt;
Note that these changes are also required for latest Moodle 3.4.6 and 3.5.3 versions.&lt;br /&gt;
&lt;br /&gt;
===Behat scenario files===&lt;br /&gt;
&lt;br /&gt;
* MDL-57281 - The behat step &amp;lt;code&amp;gt;I navigate to &amp;quot;ITEM&amp;quot; node in &amp;quot;MAINNODE &amp;gt; PATH&amp;quot;&amp;lt;/code&amp;gt; has been deprecated and throws an exception with details on how to replace it. The recommended replacement steps work in all recent Moodle versions. The updated Behat will pass with Moodle 3.4 too.&lt;br /&gt;
&lt;br /&gt;
===Login token===&lt;br /&gt;
&lt;br /&gt;
If your plugin provides an alternative login form (e.g. it is a theme replacing the default login form template / renderer), the login form must include a new login token field. For details of required changes, see [[Login token]]. Note that this also affects latest stable branches too.&lt;br /&gt;
&lt;br /&gt;
===New core functions===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tt&amp;gt;userdate_htmltime()&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Component APIs upgrades===&lt;br /&gt;
&lt;br /&gt;
Please refer to the upgrade.txt files in the relevant component directory for changes in this particular Moodle release.&lt;br /&gt;
&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/admin/tool/log/upgrade.txt admin/tool/log/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/admin/tool/upgrade.txt admin/tool/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/auth/shibboleth/upgrade.txt auth/shibboleth/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/auth/upgrade.txt auth/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/badges/upgrade.txt badges/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/blocks/upgrade.txt blocks/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/cache/upgrade.txt cache/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/calendar/upgrade.txt calendar/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/course/format/upgrade.txt course/format/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/course/upgrade.txt course/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/enrol/upgrade.txt enrol/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/filter/upgrade.txt filter/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/grading/form/upgrade.txt grade/grading/form/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/report/upgrade.txt grade/report/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/grade/upgrade.txt grade/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/lib/upgrade.txt lib/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/media/upgrade.txt media/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/message/upgrade.txt message/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/assign/upgrade.txt mod/assign/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/feedback/upgrade.txt mod/feedback/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/forum/upgrade.txt mod/forum/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/quiz/upgrade.txt mod/quiz/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/scorm/report/basic/upgrade.txt mod/scorm/report/basic/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/scorm/upgrade.txt mod/scorm/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/upgrade.txt mod/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/workshop/upgrade.txt mod/workshop/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/question/format/upgrade.txt question/format/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/report/upgrade.txt report/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/tag/upgrade.txt tag/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/theme/upgrade.txt theme/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/user/upgrade.txt user/upgrade.txt]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.5 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.6]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.6]]&lt;br /&gt;
[[es:Notas de Moodle 3.6]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Git_for_developers&amp;diff=54975</id>
		<title>Git for developers</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Git_for_developers&amp;diff=54975"/>
		<updated>2018-11-09T14:43:34Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* See also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This document is for helping you get started on Moodle development with Git. For further details of Git, see [[:Category:Git]].&lt;br /&gt;
&lt;br /&gt;
== General workflow ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;A reasonable knowledge of the Git basics is a good idea before you start to use it for development. If you are new to Git, you are encouraged to go to &#039;See also&#039; for some more general reading.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[image:git-pushpull-model.png|right|thumb|400px|Moodle development workflow with Git]]&lt;br /&gt;
Detailed explanation of the workflow can be found in the [[Process]] page. In short, the Moodle development with Git looks like this:&lt;br /&gt;
&lt;br /&gt;
* You as the contributor commit changes into your personal repository at your computer&lt;br /&gt;
* You push the changes into your public repository and publish links to your changes in the Moodle Tracker&lt;br /&gt;
* You request a peer review of your code from another developer&lt;br /&gt;
* When peer reviewer is happy they submit issue for integration&lt;br /&gt;
* Moodle integrators pull the changes from your public repository and if they like them, they put them into Moodle integration repository&lt;br /&gt;
* The integrated change is tested and finally pushed into Moodle production repository&lt;br /&gt;
* You update your local repository with all changes from the production repository and the next cycle may start again&lt;br /&gt;
&lt;br /&gt;
This workflow runs in roughly weekly cycles. The integration happens on Monday and Tuesday and the testing on Wednesday. On Thursday (or Friday if testing takes too long), the production repository moodle.git is usually updated with changes from the last development week.&lt;br /&gt;
&lt;br /&gt;
Most Moodle developers have their public repositories hosted at [http://github.com/ Github]. Alternatively you may want to try [http://gitorious.org Gitorious] or the legendary [http://repo.or.cz repo.or.cz]. In the examples in this guide we assume you&#039;ll set up your public repository at Github.&lt;br /&gt;
&lt;br /&gt;
When you first register on tracker you can not assign issues to yourself or send them for peer review. You will be added to the developers group after your first bug fix is integrated. Before that just comment on the issue with a link to your branch and component lead or another developer will send issue for peer review for you.&lt;br /&gt;
&lt;br /&gt;
== Installing Git on your computer ==&lt;br /&gt;
&lt;br /&gt;
Install Git on your computer. Most Linux distributions have Git available as a package to install. On Debian/Ubuntu, type &#039;&#039;&#039;&#039;sudo apt-get install git&#039;&#039;&#039;&#039; on the terminal. If you are on Mac, [http://code.google.com/p/git-osx-installer/ git-osx-installer] installs it in a few clicks. &lt;br /&gt;
&lt;br /&gt;
Immediately after the installation, set your name and contact e-mail. The name and e-mail will become part of your commits and they can&#039;t be changed later once your commits are accepted into the Moodle code. Therefore we ask contributors to use their real names written in capital letters, eg &amp;quot;John Smith&amp;quot; and not &amp;quot;john smith&amp;quot; or even &amp;quot;john5677&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    git config --global user.name &amp;quot;Your Name&amp;quot;&lt;br /&gt;
    git config --global user.email yourmail@domain.tld&lt;br /&gt;
&lt;br /&gt;
Unless you are the repository maintainer, it is wise to set your Git to not push changes in file permissions:&lt;br /&gt;
&lt;br /&gt;
    git config --global core.filemode false&lt;br /&gt;
&lt;br /&gt;
Also, it&#039;s recommended to verify that the your git installation is not performing any transformation between LFs and CRLFs. All Moodle &#039;&#039;&#039;uses only LFs&#039;&#039;&#039; and you should &#039;&#039;&#039;fetch/edit and push&#039;&#039;&#039; it that way (may need to configure your editor/IDE too). Note that having any &amp;quot;magic&amp;quot; enabled is known to cause [[Common unit test problems#The_test_file_.22evolution.test.22_should_not_contain_section_named_.22.5Blots_of_content.5D.22|problems with unit tests]] execution. So we recommend you to set:&lt;br /&gt;
&lt;br /&gt;
    git config --global core.autocrlf false&lt;br /&gt;
&lt;br /&gt;
== Setting-up the public repository ==&lt;br /&gt;
&lt;br /&gt;
1. Go to [http://github.com/ Github] and create an account.&lt;br /&gt;
&lt;br /&gt;
2. Go to the [http://github.com/moodle/moodle official Moodle Github repository] and click on the Fork button. You now have your own Github Moodle repository.&lt;br /&gt;
&lt;br /&gt;
3. Now you need to set up your SSH public key, so you can push to your Github Moodle repository from your local Moodle repository. On Mac you can go on this [http://help.github.com/mac-key-setup/ Github help page]. If you are on another system, go to your Github administration page, to the section SSH Public Keys, and you should see a link to a help page. Done? Good! That was the most difficult part!&lt;br /&gt;
&lt;br /&gt;
== Setting-up the local repository at your computer  ==&lt;br /&gt;
&lt;br /&gt;
Create a local clone repository of your Github repository. In a terminal:&lt;br /&gt;
&lt;br /&gt;
    git clone git://github.com/YOUR_GITHUB_USERNAME/moodle.git LOCALDIR&lt;br /&gt;
&lt;br /&gt;
    (or:  git clone git@github.com:YOUR_GITHUB_USERNAME/moodle.git LOCALDIR)&lt;br /&gt;
&lt;br /&gt;
This command does several jobs for you. It creates a new folder, initializes an empty Git repository in it, sets your Github repository as the remote repository called &#039;origin&#039; and makes a local checkout of the branch &#039;master&#039; from it. The important point to remember now is that your Github repository is aliased as &#039;origin&#039; for your local clone.&lt;br /&gt;
&lt;br /&gt;
Note that the format of the URL here is important. In the first example, the URL starts &amp;quot;git://github.com&amp;quot; and this will give read-only access to the repository at github.com. If you use this URL, the &amp;quot;git push origin&amp;quot; command that appears later in this document will not work. Therefore, if you want to be able to update the &amp;quot;origin&amp;quot; repository, you should use the URL that starts &amp;quot;git@github.com&amp;quot;, i.e. the second of the two &amp;quot;git clone&amp;quot; commands given above. This will give you read and write access to the repository on github.com.&lt;br /&gt;
&lt;br /&gt;
== Keeping your public repository up-to-date ==&lt;br /&gt;
&lt;br /&gt;
[[image:git-sync-github.png|right|thumb|400px|Fetching changes from upstream and pushing them to github]]&lt;br /&gt;
Your fork at Github is not updated automatically. To keep it synced with the upstream Moodle repository, you have to fetch the recent changes from the official moodle.git and push them to your public repository. To avoid problems with this it is strongly recommended that you never modify the standard Moodle branches. &#039;&#039;Remember: never commit directly into master and MOODLE_xx_STABLE branches.&#039;&#039; In other words, always create topic branches to work on. In Gitspeak, the master branch and MOODLE_xx_STABLE branches should be always fast-forwardable.&lt;br /&gt;
&lt;br /&gt;
To keep your public repository up-to-date, we will register remote repository git://git.moodle.org/moodle.git under &#039;upstream&#039; alias. Then we create a script to be run regularly that fetches changes from the upstream repository and pushes them to your public repository. Note that this procedure will not affect your local working directory.&lt;br /&gt;
&lt;br /&gt;
To register the upstream remote:&lt;br /&gt;
&lt;br /&gt;
    cd moodle&lt;br /&gt;
    git remote add upstream git://git.moodle.org/moodle.git&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to keep the standard Moodle branches at your Github repository synced with the upstream repository. You may wish to store them in a script so that you can run it every week after the upstream repository is updated.&lt;br /&gt;
&lt;br /&gt;
    #!/bin/bash&lt;br /&gt;
    git fetch upstream&lt;br /&gt;
    for BRANCH in MOODLE_{19..35}_STABLE master; do&lt;br /&gt;
        git push origin refs/remotes/upstream/$BRANCH:refs/heads/$BRANCH&lt;br /&gt;
    done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== How it works ===&lt;br /&gt;
&lt;br /&gt;
The git-fetch command does not modify your current working dir (your checkout). It just downloads all recent changes from a remote repository and stores them into so called remote-tracking branches. The git-push command takes these remote-tracking branches from upstream and pushes them to Github under the same name. Understanding this fully requires a bit knowledge of Git internals - see gitrevisions(7) man page.&lt;br /&gt;
&lt;br /&gt;
Note there is no need to switch the local branch during this. You can even execute this via cron at your machine. Just note that the upstream repository updates typically just once a week.&lt;br /&gt;
&lt;br /&gt;
=== New branches ===&lt;br /&gt;
&lt;br /&gt;
Occasionally, moodle.org will create a new branch that does not exist in your public (e.g. Github.com) repository. If you try to push this new branch, you will see an error such as the following:&lt;br /&gt;
&lt;br /&gt;
    error: unable to push to unqualified destination: MOODLE_99_STABLE&lt;br /&gt;
    The destination refspec neither matches an existing ref on the remote&lt;br /&gt;
    nor begins with refs/, and we are unable to guess a prefix based on the source ref.&lt;br /&gt;
    error: failed to push some refs to &#039;git@github.com:YOUR_GITHUB_USERNAME/moodle.git&#039;&lt;br /&gt;
&lt;br /&gt;
In the above example, &amp;quot;MOODLE_99_STABLE&amp;quot;, is the name of the new branch that does not exist in your public repository. To fix the error, you need to create the new branch on your public repository, using the following commands, replacing &amp;quot;MOODLE_99_STABLE&amp;quot; with the name of the new branch you wish to create:&lt;br /&gt;
&lt;br /&gt;
    git checkout MOODLE_99_STABLE&lt;br /&gt;
    git push origin MOODLE_99_STABLE:MOODLE_99_STABLE&lt;br /&gt;
&lt;br /&gt;
The above code will create a new copy of the &amp;quot;MOODLE_99_STABLE&amp;quot; branch in your local repository. If you do not need to keep a local copy of the new branch - and probably you do not need it, you then can remove it from your local repository as follows:&lt;br /&gt;
&lt;br /&gt;
    git checkout master&lt;br /&gt;
    git branch -D MOODLE_99_STABLE&lt;br /&gt;
&lt;br /&gt;
== Preparing a patch ==&lt;br /&gt;
&lt;br /&gt;
As said earlier at this page, you never work on standard Moodle branches directly. Every time you are going to edit something, switch to a local branch. Fork the local branch off the standard branch you think it should be merged to. So if you are working on a patch for 1.9 or 2.0, fork the branch off MOODLE_19_STABLE or MOODLE_20_STABLE, respectively. Patches for the next [[Moodle versions|major version]] should be based on the master branch.&lt;br /&gt;
&lt;br /&gt;
    git checkout -b MDL-xxxxx-master_brief_name origin/master&lt;br /&gt;
&lt;br /&gt;
Note that if you forget to specify the starting point, the branch is based on the currently checked-out branch. It may not be what you want. It is recommended to always specify the starting point.&lt;br /&gt;
&lt;br /&gt;
To check the current branch, run&lt;br /&gt;
&lt;br /&gt;
    git branch&lt;br /&gt;
&lt;br /&gt;
The current branch is highlighted.&lt;br /&gt;
&lt;br /&gt;
Now go and fix the issue with your favorite IDE. Check the status of the files, view the change to be committed and finally commit the change:&lt;br /&gt;
&lt;br /&gt;
    vim filename.php&lt;br /&gt;
    git status&lt;br /&gt;
    git diff&lt;br /&gt;
    git commit -a&lt;br /&gt;
&lt;br /&gt;
For the commit message, please do respect the guidelines given on in the article [[Coding style#Git_commits|Coding_style]].&lt;br /&gt;
&lt;br /&gt;
Note that this is safe as the commit is recorded just locally, nothing is sent to any server yet (as it would in CVS). To see history of the commits, use&lt;br /&gt;
&lt;br /&gt;
    git log&lt;br /&gt;
&lt;br /&gt;
Once your local branch contains the change (note that it may consists of several patches) and you are happy with it, publish the branch at your public repository:&lt;br /&gt;
&lt;br /&gt;
    git push origin MDL-xxxxx-master_brief_name&lt;br /&gt;
&lt;br /&gt;
Now as your branch is published, you can ask Moodle core developers to review it and eventually integrate it into the standard Moodle repository.&lt;br /&gt;
&lt;br /&gt;
=== Changing commit message, reordering and squashing commits ===&lt;br /&gt;
&lt;br /&gt;
It often happens that you made a mistake in your patch or in the commit message and helpful CiBot pointed it out for you. You can &amp;quot;rewrite the history&amp;quot; and change the existing commits.&lt;br /&gt;
&lt;br /&gt;
Option 1. Reset all the changes in the branch and commit again. &lt;br /&gt;
&lt;br /&gt;
    git reset --mixed origin/master&lt;br /&gt;
&lt;br /&gt;
Now all your changes are still present but all commits on top of &amp;quot;master&amp;quot; branch are gone. You can create a new commit&lt;br /&gt;
&lt;br /&gt;
Option 2. Discover &#039;&#039;&#039;git rebase --interactive&#039;&#039;&#039; - this is a powerful tool to change the sequence of commit, change the commit messages, squash commits, etc. We will not cover it here, there are many articles in the Internet about it, for example: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History.&lt;br /&gt;
&lt;br /&gt;
Whatever option you chose, you have &amp;quot;rewritten the history&amp;quot; and you can not simply push the changes to github again because they would need to overwrite the commits that were already pushed. If you try &amp;quot;git push MDL-xxxxx-master_brief_name&amp;quot; you will get an error message suggesting you to force push. &lt;br /&gt;
To force push the changed commits use:&lt;br /&gt;
&lt;br /&gt;
    git push -f origin MDL-xxxxx-master_brief_name&lt;br /&gt;
&lt;br /&gt;
If an error occurs because you are still using the git protocol (read only), use this command : &lt;br /&gt;
&lt;br /&gt;
    git remote set-url origin https://github.com/&amp;lt;user.name&amp;gt;/moodle.git&lt;br /&gt;
&lt;br /&gt;
A prompt will ask for your credentials, if you previously setup your SSH public key you can also use this one : &lt;br /&gt;
&lt;br /&gt;
    git remote set-url origin git@github.com:&amp;lt;user.name&amp;gt;/moodle.git&lt;br /&gt;
&lt;br /&gt;
=== Checking if a branch has already been merged ===&lt;br /&gt;
&lt;br /&gt;
After some time contributing to Moodle you would have a lot of branches both in your local repository and in your public repository. To prune their list and delete those that were accepted by upstream, use the following&lt;br /&gt;
&lt;br /&gt;
    git fetch upstream                                      (1)&lt;br /&gt;
    git branch --merged upstream/master                     (2)&lt;br /&gt;
    git branch --merged upstream/MOODLE_20_STABLE           (3)&lt;br /&gt;
&lt;br /&gt;
The command (1) fetches the changes from your upstream repository at git.moodle.org (remember that git-fetch does not modify your working dir so it is safe to run it whenever). Command (2) and (3) print all branches that are merged into the upstream master branch and MOODLE_20_STABLE branch, respectively. To delete these local branches, use&lt;br /&gt;
&lt;br /&gt;
    git branch -d MDL-xxxxx-master_accepted_branch&lt;br /&gt;
&lt;br /&gt;
The similar approach can be used to check the branches published at your origin repository at github.com&lt;br /&gt;
&lt;br /&gt;
    git fetch origin                                        (1)&lt;br /&gt;
    git fetch upstream&lt;br /&gt;
    git branch -r --merged upstream/master                  (2)&lt;br /&gt;
    git branch -r --merged upstream/MOODLE_20_STABLE        (3)&lt;br /&gt;
&lt;br /&gt;
The command (1) makes sure that you have all your branches from github.com recorded as the remote tracking branch locally. Commands (2) and (3) work the same as in the previous example but they list remote tracking branches only ([http://www.kernel.org/pub/software/scm/git/docs/git-branch.html see -r param]). To delete a branch at github.com, use&lt;br /&gt;
&lt;br /&gt;
    git push origin :MDL-xxxxx-master_branch_to_delete&lt;br /&gt;
&lt;br /&gt;
This syntax may look weird to you. However it is pretty logical. The general syntax of the git-push command is&lt;br /&gt;
&lt;br /&gt;
    git push &amp;lt;repository&amp;gt; &amp;lt;source ref&amp;gt;:&amp;lt;target ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so deleting a remote branch can be understood as pushing an &amp;quot;empty (null) reference&amp;quot; to it.&lt;br /&gt;
&lt;br /&gt;
== Peer-reviewing someone else&#039;s code ==&lt;br /&gt;
&lt;br /&gt;
To review a branch that someone else pushed into their public repository, you do not need to register a new remote (unless you work with such repository frequently, of course). Let us imagine your friend Alice pushed a work-in-progress branch called &#039;wip-feature&#039; into her Github repository and asked you to review it. You need to know the read-only address of the repository and the name of the branch.&lt;br /&gt;
&lt;br /&gt;
    git fetch git://github.com/alice/moodle.git wip-feature&lt;br /&gt;
&lt;br /&gt;
This will download all required data and will keep the pointer to the tip of the wip-feature branch in a local symbolic reference FETCH_HEAD. To see what&#039;s there on that branch, use&lt;br /&gt;
&lt;br /&gt;
    git log -p FETCH_HEAD&lt;br /&gt;
&lt;br /&gt;
To see how a particular file looks at Alice&#039;s branch&lt;br /&gt;
&lt;br /&gt;
    git show FETCH_HEAD:admin/blocks.php&lt;br /&gt;
&lt;br /&gt;
To create a new local branch called &#039;alice-wip-feature&#039; containing the work by Alice, use&lt;br /&gt;
&lt;br /&gt;
    git checkout -b alice-wip-feature FETCH_HEAD&lt;br /&gt;
&lt;br /&gt;
To merge Alice&#039;s work into your current branch:&lt;br /&gt;
&lt;br /&gt;
    git merge FETCH_HEAD&lt;br /&gt;
&lt;br /&gt;
To see what would be merged into the current branch without actually modifying anything:&lt;br /&gt;
&lt;br /&gt;
    git diff ...FETCH_HEAD&lt;br /&gt;
&lt;br /&gt;
Once you are all set and reviewing code, this [[Peer_reviewing_checklist|checklist]] should prove to be useful.&lt;br /&gt;
&lt;br /&gt;
== Rebasing a branch ==&lt;br /&gt;
&lt;br /&gt;
Rebasing is a process when you cut off the branch from its current start point and transplant it to another point. Let us assume the following history exists:&lt;br /&gt;
&lt;br /&gt;
          A---B---C topic&lt;br /&gt;
         /&lt;br /&gt;
    D---E---F---G master&lt;br /&gt;
&lt;br /&gt;
From this point, the result of the command:&lt;br /&gt;
&lt;br /&gt;
    git rebase master topic&lt;br /&gt;
&lt;br /&gt;
would be:&lt;br /&gt;
&lt;br /&gt;
                  A&#039;--B&#039;--C&#039; topic&lt;br /&gt;
                 /&lt;br /&gt;
    D---E---F---G master&lt;br /&gt;
&lt;br /&gt;
and would end with &#039;topic&#039; being your current branch.&lt;br /&gt;
&lt;br /&gt;
You may be asked to rebase your branch submitted for the integration if the submitted branch was based on an outdated commit. The typical case is if you create a new branch as a fork off the upstream master branch on Tuesday. Then on Wednesday, the upstream master branch grows as all changes from the last integration cycle are merged to it. To make diff easy on Github for next weekly pull request review, you want to rebase your branch against the updated master.&lt;br /&gt;
&lt;br /&gt;
    git rebase master MDL-xxxxx-master_topic_branch&lt;br /&gt;
&lt;br /&gt;
Note that rebasing effectively rewrites the history of the branch. &#039;&#039;&#039;Do not rebase the branch if there is a chance that somebody has already forked it and based their own branch on it.&#039;&#039;&#039; For this reason, many Git tutorials discourage from rebasing any branch that has been published. However in Moodle, all branches submitted for integration are potential subject of rebase (even though we try to not to do it often) and you should not base your own branches on them.&lt;br /&gt;
&lt;br /&gt;
=== Conflicts during rebase ===&lt;br /&gt;
&lt;br /&gt;
During the rebase procedure, conflicts may appear. git-status commands reports the conflicted files. Explore them carefully and fix them in your editor (like you would do with CVS). Then add the files with &#039;git add&#039; command and continue.&lt;br /&gt;
&lt;br /&gt;
    vim conflicted.php&lt;br /&gt;
    git add conflicted.php&lt;br /&gt;
    git rebase --continue&lt;br /&gt;
&lt;br /&gt;
== Applying changes from one branch to another ==&lt;br /&gt;
&lt;br /&gt;
Most bugs are fixed at a stable branch (like MOODLE_20_STABLE) and the fix must be prepared for other branches, too (like MOODLE_21_STABLE and the main development branch - master). In Moodle, we do not merge stable branches into the master one. So usually the contributor prepares at least two branches - with the fix for the stable branch(es) and with the fix for the master branch.&lt;br /&gt;
&lt;br /&gt;
If you have a patch prepared on a local branch (let us say MDL-xxxxx-20_brief_name), it is possible to re-apply it to another branch.&lt;br /&gt;
&lt;br /&gt;
=== Cherry-picking a single commit ===&lt;br /&gt;
&lt;br /&gt;
Let us have two local Git repositories ~/public_html/moodle21 containing local installation of Moodle 2.1 and ~/public_html/moodledev with the local installation of most recent development version of Moodle. They both use your public repository at github.com as the origin. You have a branch in moodle21 called MDL-xxxxx-21_topic that was forked off MOODLE_21_STABLE. It contains one commit. Now you want to re-apply this commit to a branch MDL-xxxxx-master_topic in moodledev.&lt;br /&gt;
&lt;br /&gt;
    cd ~/public_html/moodledev&lt;br /&gt;
    git checkout -b MDL-xxxxx-master_topic origin/master    (1)&lt;br /&gt;
    git fetch ../moodle21 MDL-xxxxx-21_topic                (2)&lt;br /&gt;
    git cherry-pick FETCH_HEAD                              (3)&lt;br /&gt;
&lt;br /&gt;
The command (1) creates new local branch forked off the master. The command (2) fetches all data needed to re-apply the topic branch and stores the pointer to the tip of that branch to FETCH_HEAD symbolic reference. The command (3) picks the tip of the branch (the top-most commit on it) and tries to apply it on the current branch.&lt;br /&gt;
There is also a variant of the cherry-pick command that supports multiple commits, shortly (see its man page for details): &amp;lt;code bash&amp;gt;$ git cherry-pick A^..B&amp;lt;/code&amp;gt; if you want to include from A - see &#039;&#039;&#039;^&#039;&#039;&#039; - to B, A should be older than B. We will use another approach for cherry-picking multiple commits.&lt;br /&gt;
&lt;br /&gt;
=== Applying a set of patches ===&lt;br /&gt;
&lt;br /&gt;
If the branch MDL-xxxxx-21_topic from the previous example consists of several commits, it may be easier to use git-format-patch and git-am combo to re-apply the whole set of patches (aka patchset). Firstly you will export all commits from the topic branch to files.&lt;br /&gt;
&lt;br /&gt;
    cd ~/public_html/moodle21&lt;br /&gt;
    mkdir .patches&lt;br /&gt;
    git format-patch -o .patches MOODLE_21_STABLE..MDL-xxxxx-21_topic         (1)&lt;br /&gt;
&lt;br /&gt;
The command (1) takes all commits from the topic branch that are not in MOODLE_21_STABLE and exports them one by one to the output directory .patches. Look at the generated files. They contain the patch itself (in diff format) and additional information about the commit. You could eg send these files by email to a friend of yours for peer-review. We will use them in another repository.&lt;br /&gt;
&lt;br /&gt;
    cd ~/public_html/moodledev&lt;br /&gt;
    git checkout -b MDL-xxxxx-master_topic origin/master&lt;br /&gt;
    git am -3 ../moodle21/.patches/*                        (1)&lt;br /&gt;
&lt;br /&gt;
The command (1) applies all the files from the .patches directory. When a patch does not apply cleanly, the command tries fall back on 3-way merge (see the -3 parameter). If conflicts occur during the procedure, you can either deal with them and then use `git am --continue` or abort the whole procedure with `git am --abort`.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Git tips]]&lt;br /&gt;
&lt;br /&gt;
; Moodle forum discussions&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=168094 GIT help needed]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=165236 Best way to manage CONTRIB code with GIT]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=167063 Handy Git tip for tracking 3rd-party modules and plugins]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=167730 Moodle Git repositories]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=183409 Git help!! I don&#039;t understand rebase enough...]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=217617 add MOODLE_24_STABLE to github.com repository]&lt;br /&gt;
&lt;br /&gt;
; External resources &lt;br /&gt;
* [https://mirrors.edge.kernel.org/pub/software/scm/git/docs/giteveryday.html Everyday GIT With 20 Commands Or So]&lt;br /&gt;
* [https://git-scm.com/book/en/v2 &#039;Pro Git&#039; complete book]&lt;br /&gt;
* [http://vimeo.com/14629850 Getting git by Scott Chacon] - an recording of an excellent 1-hour presentation that introducing git, including a simple introduction to what is going on under the hood.&lt;br /&gt;
* [http://tjhunt.blogspot.co.uk/2012/03/fixing-bug-in-moodle-core-mechanics.html Tim Hunt&#039;s blog: Fixing a bug in Moodle core: the mechanics]&lt;br /&gt;
* [https://github.com/k88hudson/git-flight-rules/blob/master/README.md#flight-rules-for-git Flight rules for Git]&lt;br /&gt;
&lt;br /&gt;
[[Category:Git]]&lt;br /&gt;
&lt;br /&gt;
[[ja:開発者用Git]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.5_release_notes&amp;diff=54529</id>
		<title>Moodle 3.5 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.5_release_notes&amp;diff=54529"/>
		<updated>2018-07-17T08:45:51Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Server requirements */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
Release date: 17 May 2018&lt;br /&gt;
&lt;br /&gt;
Here is [https://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%223.5%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.5].&lt;br /&gt;
&lt;br /&gt;
See our [https://docs.moodle.org/35/en/New_features New Features page] for a more user-friendly introduction to Moodle 3.5 with screenshots.&lt;br /&gt;
&lt;br /&gt;
If you are upgrading from previous version, make sure you read the [https://docs.moodle.org/35/en/Upgrading Upgrading] documentation.&lt;br /&gt;
&lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just the minimum supported versions. We recommend keeping all of your software and operating systems up-to-date.&lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 3.1 or later&lt;br /&gt;
* PHP version: minimum PHP 7.0.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.3&#039;&#039;. PHP 7.1.x and 7.2.x are supported too. PHP 7.x could have some [https://docs.moodle.org/dev/Moodle_and_PHP7#Can_I_use_PHP7_yet.3F engine limitations]. &lt;br /&gt;
* PHP extension &#039;&#039;&#039;intl&#039;&#039;&#039; is required since Moodle 3.4 (it was recommended in 2.0 onwards) &lt;br /&gt;
* (Recommendation only) If you use MySQL or MariaDB, make sure your database supports full UTF-8 (utf8mb4) if you install a new instance of Moodle. CLI script may be used to convert to utf8mb4 if you&#039;re upgrading. You may choose to keep using &#039;utf8_*&#039;, but then a warning will show that the database isn&#039;t using full UTF-8 support and suggest moving to &#039;utf8mb4_unicode_ci&#039;. See [[:en:MySQL full unicode support|MySQL full unicode support]] for details. If you do enable utf8mb4 you *must* use the Barracuda file format. &lt;br /&gt;
&lt;br /&gt;
=== Database requirements ===&lt;br /&gt;
&lt;br /&gt;
Moodle supports the following database servers. Again, version numbers are just the minimum supported version. We recommend running the latest stable version of any software.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Database&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
| 9.3&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.microsoft.com/en-us/server-cloud/products/sql-server/ Microsoft SQL Server]&lt;br /&gt;
| 2008&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.oracle.com/us/products/database/overview/index.html Oracle Database]&lt;br /&gt;
| 10.2&lt;br /&gt;
| Latest&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Client requirements==&lt;br /&gt;
&lt;br /&gt;
=== Browser support ===&lt;br /&gt;
Moodle is compatible with any standards compliant web browser. We regularly test Moodle with the following browsers:&lt;br /&gt;
&lt;br /&gt;
Desktop:&lt;br /&gt;
* Chrome&lt;br /&gt;
* Firefox&lt;br /&gt;
* Safari&lt;br /&gt;
* Edge&lt;br /&gt;
* Internet Explorer&lt;br /&gt;
&lt;br /&gt;
Mobile:&lt;br /&gt;
* MobileSafari&lt;br /&gt;
* Google Chrome&lt;br /&gt;
&lt;br /&gt;
For the best experience and optimum security, we recommend that you keep your browser up to date. https://whatbrowser.org&lt;br /&gt;
&lt;br /&gt;
Note: Legacy browsers with known compatibility issues with Moodle 3.5:&lt;br /&gt;
* Internet Explorer 10 and below&lt;br /&gt;
* Safari 7 and below&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===GDPR===&lt;br /&gt;
* &#039;&#039;&#039;MDL-61275 - GDPR Consenting of Minors and Managing, Versioning and Tracking Privacy Policies and User Consents&#039;&#039;&#039;&lt;br /&gt;
* MDL-61292 - A new admin tool to manage policy documents&lt;br /&gt;
* MDL-61423 - Add age and location verification to identify minors&lt;br /&gt;
* MDL-61302 - Workflow to allow users to agree to all policies&lt;br /&gt;
* MDL-61301 - Report of user agreed policies and their versions&lt;br /&gt;
* MDL-61705 - Bulk accept of policies on behalf of users&lt;br /&gt;
* MDL-61864 - Include policy tool in core&lt;br /&gt;
* MDL-62286 - Add policy link to the site footer&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;MDL-61306 - GDPR Data Requests and Data Registry&#039;&#039;&#039;&lt;br /&gt;
* MDL-59718 - A process to send a request to the data protection officer&lt;br /&gt;
* MDL-59720 - Delete personal data when it is no longer required&lt;br /&gt;
* MDL-61307 - Create a new privacy subsystem&lt;br /&gt;
* MDL-61362 - Ability to create data categories and purposes&lt;br /&gt;
* MDL-61486 - Data registry with purpose and retention period&lt;br /&gt;
* MDL-61489 - Report of plugin/components implementing the Privacy API&lt;br /&gt;
* MDL-61499 - Ability to set default purpose and retention periods for context levels&lt;br /&gt;
* MDL-61785 - Ability to review and confirm which expired data can be deleted&lt;br /&gt;
* MDL-61899 - Include data privacy tool in core&lt;br /&gt;
* MDL-61935 - Ability to specify the lawful bases for the collection of personal data&lt;br /&gt;
&lt;br /&gt;
===Question bank tagging improvements===&lt;br /&gt;
* MDL-61066 - Expanded tagging functionality for question bank&lt;br /&gt;
* MDL-61133 - New modal to add/edit/remove tags on questions&lt;br /&gt;
* MDL-61135 - Filter questions by tag&lt;br /&gt;
* MDL-61138 - Show the list of questions in the &#039;Add a random question&#039; dialog&lt;br /&gt;
* MDL-61363 - Ability to add question tags at a course level in the edit question form&lt;br /&gt;
* MDL-61364 - Manage tags at a question and course context level&lt;br /&gt;
* MDL-61380 - Allow filtering/adding random questions by tag for quizzes&lt;br /&gt;
* MDL-61410 - Add import/export support for course level question tags&lt;br /&gt;
* MDL-61444 - New capabilities for tagging questions&lt;br /&gt;
&lt;br /&gt;
===UX: Usability improvements===&lt;br /&gt;
* MDL-62021 - Boost 4.0 Migration&lt;br /&gt;
* MDL-56511 - Update bootstrap 4 to final release&lt;br /&gt;
* MDL-61657 - Add images to the course cards on the dashboard&lt;br /&gt;
&lt;br /&gt;
===LTI Advantage support===&lt;br /&gt;
* MDL-60416 - Add support for LTI Advantage 1.1&lt;br /&gt;
&lt;br /&gt;
===RecordRTC for Atto===&lt;br /&gt;
* MDL-60848 - Implement RecordRTC Atto plugin as core feature&lt;br /&gt;
* MDL-61973 - Update RecordRTC Atto plugin buttons&lt;br /&gt;
&lt;br /&gt;
===Messaging database tables===&lt;br /&gt;
* MDL-61254 - Merge messaging database tables&lt;br /&gt;
* MDL-36941 - Create new tables for messaging&lt;br /&gt;
* MDL-61255 - Ad-hoc task to upgrade messages to merged table &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Other Highlights==&lt;br /&gt;
&lt;br /&gt;
===Global search===&lt;br /&gt;
* MDL-58885 - Add group support&lt;br /&gt;
* MDL-59434 - Content aware searching / alternate results sort orders&lt;br /&gt;
* MDL-60981 - Reindex a single area&lt;br /&gt;
* MDL-61028 - Allow filtering search by user&lt;br /&gt;
* MDL-61256 - Search of section titles, summaries&lt;br /&gt;
&lt;br /&gt;
===Functional changes===&lt;br /&gt;
* MDL-2051 - Inform student whether and how their selected choice will display&lt;br /&gt;
* MDL-32585 - SCORM: option to force new attempts&lt;br /&gt;
* MDL-53226 - Add Moodle DB search engine&lt;br /&gt;
* MDL-55491 - Use cohort as badge criteria&lt;br /&gt;
* MDL-56246 - Add site wide default for grade export: include feedback&lt;br /&gt;
* MDL-59875 - Allow badges as criteria for other badges&lt;br /&gt;
* MDL-60119 - Feedback - Multiple choice (rated) - remove weights from answer&lt;br /&gt;
* MDL-61203 - Allow uploading of profile picture to be used as badge criteria&lt;br /&gt;
* MDL-61601 - Allow cohort themes&lt;br /&gt;
* MDL-61651 - LTI: line item definition within link to return gradable LTI links&lt;br /&gt;
* MDL-60811 - Bulk delete self-registered enrolments on participants page&lt;br /&gt;
* MDL-60682 - Ability to set date/time to nearest minute&lt;br /&gt;
* MDL-60441 - Ability to add a link to glossary entries&lt;br /&gt;
* MDL-58411 - Ability to apply file type restrictions for essay question type&lt;br /&gt;
* MDL-56945 - Add easy return path from PDF grading screen to list of submissions&lt;br /&gt;
* MDL-52811 - Add force language capability to course settings&lt;br /&gt;
* MDL-41090 - Allow teachers to embed files when manually grading questions&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
 &lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=371199 MSA-18-0007] Calculated question type allows remote code execution by Question authors&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=371200 MSA-18-0008] Users can download any file via portfolio assignment caller class&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=371201 MSA-18-0009] Portfolio forum caller class allows a user to download any file&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=371202 MSA-18-0010] User can shift a block from Dashboard to any page&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=371203 MSA-18-0011] User who did not agree to the site policies can see the site homepage as if they had full site access&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=371204 MSA-18-0012] Portfolio script allows instantiation of class chosen by user&lt;br /&gt;
&lt;br /&gt;
===For developers===&lt;br /&gt;
&lt;br /&gt;
* MDL-61307 - All plugins must implement [https://docs.moodle.org/dev/Privacy_API Privacy API] to be compliant with GDPR requirements. They must implement the API to report on, export and delete stored user data&lt;br /&gt;
* MDL-56511 - Bootstrap is upgraded to final release of version 4&lt;br /&gt;
* MDL-61869 - Infer rendering of templatables with no render method&lt;br /&gt;
* MDL-61298 - Boost: use navigation node icon&lt;br /&gt;
&lt;br /&gt;
==== Upgrading plugins ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1. Check for changes in core APIs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Read lib/upgrade.txt to check for the deprecations and core API changes, make sure you applied them to your plugin. Note that entries there are not sorted by priority but rather by integration time. Below is the list of upgrade.txt files that contain information about upgrading from Moodle 3.4 to Moodle 3.5 (note that if you upgrade from earlier versions there may be more files):&lt;br /&gt;
&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/lib/upgrade.txt lib/upgrade.txt] changes to various core APIs, deprecations, functions removal&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/calendar/upgrade.txt calendar/upgrade.txt] changes to Calendar API&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/search/upgrade.txt search/upgrade.txt] changes to Global search API&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/message/upgrade.txt message/upgrade.txt] changes to Messages API&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/course/upgrade.txt course/upgrade.txt] changes to Course API&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2. Check for changes in the API of your plugin type&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below is the list of plugin types that had API changes between Moodle 3.4 and 3.5:&lt;br /&gt;
&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/enrol/upgrade.txt enrol/upgrade.txt] Enrolment method plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/mod/upgrade.txt mod/upgrade.txt] Activity module plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/auth/upgrade.txt auth/upgrade.txt] Authentication plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/course/format/upgrade.txt course/format/upgrade.txt] Course format plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/question/type/upgrade.txt question/type/upgrade.txt] Question type plugins&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3. Check for changes in the depended plugins&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If your plugin depends on another plugin or calls methods from another plugin, read upgrade.txt in this plugin directory (if it exists). Below is the list of standard plugins that had changes between Moodle 3.4 and 3.5:&lt;br /&gt;
&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/admin/tool/mobile/upgrade.txt tool_mobile],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/admin/tool/usertours/upgrade.txt tool_usertours],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/assign/upgrade.txt mod_assign],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/feedback/upgrade.txt mod_feedback],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/quiz/upgrade.txt mod_quiz],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/scorm/upgrade.txt mod_scorm],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/theme/boost/upgrade.txt theme_boost]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4. Do a smoke test of your plugin with developer debugging mode&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Make sure to check on both Boost and Clean themes. Bootstrap was upgraded in Moodle 3.5&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;5. Run all behat and phpunit tests&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.4 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.5]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.5]]&lt;br /&gt;
[[es:Notas de Moodle 3.5]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54166</id>
		<title>Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54166"/>
		<updated>2018-05-14T21:21:20Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Retrieving the list of contexts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The [https://en.wikipedia.org/wiki/General_Data_Protection_Regulation General Data Protection Regulation] (GDPR) is an EU directive that looks at providing users with more control over their data and how it is processed. This regulation will come into effect on 25th of May 2018 and covers any citizen or permanent resident of the European Union. The directive will be respected by a number of other countries outside of the European Union.&lt;br /&gt;
&lt;br /&gt;
To help institutions become compliant with this new regulation we are adding functionality to Moodle. This includes a number of components, amongst others these include a user’s right to:&lt;br /&gt;
&lt;br /&gt;
* request information on the types of personal data held, the instances of that data, and the deletion policy for each;&lt;br /&gt;
* access all of their data; and&lt;br /&gt;
* be forgotten.&lt;br /&gt;
&lt;br /&gt;
The compliance requirements also extend to installed plugins (including third party plugins). These need to also be able to report what information they store or process regarding users, and have the ability to provide and delete data for a user request.&lt;br /&gt;
&lt;br /&gt;
This document describes the proposed API changes required for plugins which will allow a Moodle installation to become GDPR compliant.&lt;br /&gt;
&lt;br /&gt;
Target Audience: The intended audience for this document is Moodle plugin developers, who are aiming to ensure their plugins are updated to comply with GDPR requirements coming into effect in the EU in May, 2018.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Personal data in Moodle==&lt;br /&gt;
&lt;br /&gt;
From the GDPR Spec, Article 4:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;‘personal data’ means any information relating to an identified or identifiable natural person (‘data subject’); an identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In Moodle, we need to consider two main types of personal data; information entered by the user and information stored about the user. The key difference being that information stored about the user will have come from a source other than the user themselves. Both types of data can be used to form a profile of the individual.&lt;br /&gt;
&lt;br /&gt;
The most obvious clue to finding personal data entered by the user is the presence of a userid on a database field. Any data on the record (or linked records) pertaining to that user may be deemed personal data for that user, including things like timestamps and record identification numbers. Additionally, any free text field which allows the user to enter information must also be considered to be the personal data of that user.&lt;br /&gt;
&lt;br /&gt;
Data stored about the user includes things like ratings and comments made on a student submission. These may have been made by an assessor or teacher, but are considered the personal data of the student, as they are considered a reflection of the user’s competency in the subject matter and can be used to form a profile of that individual. &lt;br /&gt;
&lt;br /&gt;
The sections that follow outline what you need to do as a plugin developer to ensure any personal data is advertised and can be accessed and deleted according to the GDPR requirements.&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
===Architecture overview===&lt;br /&gt;
&lt;br /&gt;
[[File:MoodlePrivacyMetadataUML.png|thumb|UML diagram of the metadata part of the privacy subsystem]]&lt;br /&gt;
[[File:MoodlePrivacyRequestUML.png|thumb|UML diagram of the request providers part of the privacy subsystem]]&lt;br /&gt;
&lt;br /&gt;
A new system for Privacy has been created within Moodle. This is broken down into several main parts and forms the &#039;&#039;core_privacy&#039;&#039; subsystem:&lt;br /&gt;
&lt;br /&gt;
* Some metadata providers - a set of PHP interfaces to be implemented by components for that component to describe the kind of data that it stores, and the purpose for its storage;&lt;br /&gt;
* Some request providers - a set of PHP interfaces to be implemented by components to allow that component to act upon user requests such as the Right to be Forgotten, and a Subject Access Request; and&lt;br /&gt;
* A manager - a concrete class used to bridge components which implement the providers with tools which request their data.&lt;br /&gt;
&lt;br /&gt;
All plugins will implement one metadata provider, and zero, one or two request providers.&lt;br /&gt;
&lt;br /&gt;
The fetching of data is broken into two separate steps:&lt;br /&gt;
&lt;br /&gt;
# Detecting in which Moodle contexts the user has any data; and&lt;br /&gt;
# Exporting all data from each of those contexts.&lt;br /&gt;
&lt;br /&gt;
This has been broken into two steps to later allow administrators to exclude certain contexts from an export - e.g. for courses currently in progress.&lt;br /&gt;
&lt;br /&gt;
A third component will later be added to facilitate the deletion of data within these contexts which will help to satisfy the Right to be Forgotten. This will also use the first step.&lt;br /&gt;
&lt;br /&gt;
Please refer to the inline phpdocs of the [https://github.com/moodle/moodle/blob/v3.5.0-beta/privacy/classes/manager.php#L31 core_privacy::manager class] for detailed description of the interfaces, their hierarchy and meaning.&lt;br /&gt;
&lt;br /&gt;
===Implementing a provider===&lt;br /&gt;
&lt;br /&gt;
All plugins will need to create a concrete class which implements the relevant metadata and request providers. The exact providers you need to implement will depend on what data you store, and the type of plugin. This is covered in more detail in the following sections of the document.&lt;br /&gt;
&lt;br /&gt;
In order to do so:&lt;br /&gt;
&lt;br /&gt;
# You must create a class called &#039;&#039;provider&#039;&#039; within the namespace &#039;&#039;\your_pluginname\privacy&#039;&#039;.&lt;br /&gt;
# This class must be created at &#039;&#039;path/to/your/plugin/classes/privacy/provider.php&#039;&#039;.&lt;br /&gt;
# You must have your class implement the relevant metadata and request interfaces.&lt;br /&gt;
&lt;br /&gt;
==Plugins which do not store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do not store any personal data. This is usually the case for plugins which just add functionality, or which display the data already stored elsewhere in Moodle.&lt;br /&gt;
&lt;br /&gt;
Some examples of plugin types which might fit this criteria include themes, blocks, filters, editor plugins, etc.&lt;br /&gt;
&lt;br /&gt;
Plugins which cause data to be stored elsewhere in Moodle (e.g. via a subsystem call) are considered to store data.&lt;br /&gt;
&lt;br /&gt;
One examples of a plugin which does not store any data would be the Calendar month block which just displays a view of the user’s calendar. It does not store any data itself.&lt;br /&gt;
&lt;br /&gt;
An example of a plugin which must not use the null provider is the Comments block. The comments block is responsible for data subsequently being stored within Moodle. Although the block doesn’t store anything itself, it interacts with the comments subsystem and is the only component which knows how that data maps to a user.&lt;br /&gt;
&lt;br /&gt;
===Implementation requirements===&lt;br /&gt;
&lt;br /&gt;
In order to let Moodle know that you have audited your plugin, and that you do not store any personal user data, you must implement the &#039;&#039;\core_privacy\local\metadata\null_provider&#039;&#039; interface in your plugin’s provider.&lt;br /&gt;
&lt;br /&gt;
These null providers can only be implemented where a plugin has:&lt;br /&gt;
&lt;br /&gt;
* no external links (e.g. sends data to an external service like an LTI provider, repository plugin which you can search on)&lt;br /&gt;
* no database tables which store user data (including IP addresses)&lt;br /&gt;
* no user preferences&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;null_provider&#039;&#039; requires you to define one function &#039;&#039;get_reason()&#039;&#039; which returns the language string identifier within your component.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 &amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace block_calendar_month\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
    // This plugin does not store any personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\null_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the language string identifier with the component&#039;s language&lt;br /&gt;
     * file to explain why this plugin stores no data.&lt;br /&gt;
     *&lt;br /&gt;
     * @return  string&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_reason() : string {&lt;br /&gt;
        return &#039;privacy:metadata&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/lang/en/block_calendar_month.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
$string[&#039;privacy:metadata&#039;] = &#039;The Calendar block only displays existing calendar data.&#039;;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That’s it. Congratulations, your plugin now implements the Privacy API.&lt;br /&gt;
&lt;br /&gt;
==Plugins which store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do store some form of personal data.&lt;br /&gt;
&lt;br /&gt;
In some cases this will be stored within database tables in your plugin, and in other cases this will be in one of Moodle’s core subsystems - for example your plugin may store files, ratings, comments, or tags.&lt;br /&gt;
&lt;br /&gt;
Plugins which do store data will need to:&lt;br /&gt;
&lt;br /&gt;
* Describe the type of data that they store; &lt;br /&gt;
* Provide a way to export that data; and&lt;br /&gt;
* Provide a way to delete that data.&lt;br /&gt;
&lt;br /&gt;
Data is described via a &#039;&#039;metadata&#039;&#039; provider, and it is both exported and deleted via an implementation of a &#039;&#039;request&#039;&#039; provider.&lt;br /&gt;
&lt;br /&gt;
These are both explained in the sections below.&lt;br /&gt;
&lt;br /&gt;
===Describing the type of data you store===&lt;br /&gt;
&lt;br /&gt;
In order to describe the type of data that you store, you must implement the &#039;&#039;\core_privacy\local\metadata\provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
This interfaces requires that you define one function: &#039;&#039;get_metadata&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There are several types of item to describe the data that you store. These are for:&lt;br /&gt;
&lt;br /&gt;
* Items in the Moodle database;&lt;br /&gt;
* Items stored by you in a Moodle subsystem - for example files, and ratings; and&lt;br /&gt;
* User preferences stored site-wide within Moodle for your plugin&lt;br /&gt;
&lt;br /&gt;
Note: All fields should include a description from a language string within your plugin.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_forum\privacy;&lt;br /&gt;
use core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
        // This plugin does store personal user data.&lt;br /&gt;
        \core_privacy\local\metadata\provider {&lt;br /&gt;
&lt;br /&gt;
    public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
        // Here you will add more items into the collection.&lt;br /&gt;
&lt;br /&gt;
        return $collection;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store content in a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
Many plugins will use one of the core Moodle subsystems to store data.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer we do not expect you to describe those subsystems in detail, but we do need to know that you use them and to know what you use them for.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_subsystem_link()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_subsystem_link(&lt;br /&gt;
        &#039;core_files&#039;,&lt;br /&gt;
        [],&lt;br /&gt;
        &#039;privacy:metadata:core_files&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:core_files&#039;] = &#039;The forum stores files which have been uploaded by the user to form part of a forum post.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Describing data stored in database tables====&lt;br /&gt;
&lt;br /&gt;
Most Moodle plugins will store some form of user data in their own database tables.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each database table, and each field which includes user data.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_database_table(&lt;br /&gt;
        &#039;forum_discussion_subs&#039;,&lt;br /&gt;
         [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:userid&#039;,&lt;br /&gt;
            &#039;discussionid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:discussionid&#039;,&lt;br /&gt;
            &#039;preference&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:preference&#039;,&lt;br /&gt;
&lt;br /&gt;
         ],&lt;br /&gt;
        &#039;privacy:metadata:forum_discussion_subs&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs&#039;] = &#039;Information about the subscriptions to individual forum discussions. This includes when a user has chosen to subscribe to a discussion, or to unsubscribe from one where they would otherwise be subscribed.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:userid&#039;] = &#039;The ID of the user with this subscription preference.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:discussionid&#039;] = &#039;The ID of the discussion that was subscribed to.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:preference&#039;] = &#039;The start time of the subscription.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store site-wide user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins will include one or more user preferences. Unfortunately this is one of Moodle’s older components and many of the values stored are not pure user preferences. Each plugin should be aware of how it handles its own preferences and is best placed to determine whether they are site-wide preferences, or per-instance preferences.&lt;br /&gt;
&lt;br /&gt;
Whilst most of these will have a fixed name (e.g. &#039;&#039;filepicker_recentrepository&#039;&#039;), some will include a variable of some kind (e.g. &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039;). Only the general name needs to be indicated rather than one copy for each preference.&lt;br /&gt;
&lt;br /&gt;
Also, these should only be &#039;&#039;site-wide&#039;&#039; user preferences which do not belong to a specific Moodle context.&lt;br /&gt;
&lt;br /&gt;
In the above examples:&lt;br /&gt;
&lt;br /&gt;
* Preference &#039;&#039;filepicker_recentrepository&#039;&#039; belongs to the file subsystem, and is a site-wide preference affecting the user anywhere that they view the filepicker.&lt;br /&gt;
* Preference &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039; belongs to user tours. User tours are a site-wide feature which can affect many parts of Moodle and cross multiple contexts.&lt;br /&gt;
&lt;br /&gt;
In some cases a value may be stored in the preferences table but is known to belong to a specific context within Moodle. In these cases they should be stored as metadata against that context rather than as a site-wide user preference.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_user_preference()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Any plugin providing user preferences must also implement the &#039;&#039;\core_privacy\local\request\preference_provider&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_user_preference(&#039;tool_usertours_tour_completion_time&#039;,&lt;br /&gt;
        &#039;privacy:metadata:preference:tool_usertours_tour_completion_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/lang/en/tool_usertours.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:tool_usertours_tour_completion_time&#039;] = &#039;The time that a specific user tour was last completed by a user.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you export data to an external location====&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with external systems - for example cloud-based services. Often this external location is configurable within the plugin either at the site or the instance level.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each &#039;&#039;type&#039;&#039; of target destination, alongside a list of each exported field which includes user data.&lt;br /&gt;
The &#039;&#039;actual&#039;&#039; destination does not need to be described as this can change based on configuration.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_external_location_link()&#039;&#039; method on the collection.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_external_location_link(&#039;lti_client&#039;, [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:lti_client:userid&#039;,&lt;br /&gt;
            &#039;fullname&#039; =&amp;gt; &#039;privacy:metadata:lti_client:fullname&#039;,&lt;br /&gt;
        ], &#039;privacy:metadata:lti_client&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/lang/en/lti.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client&#039;] = &#039;In order to integrate with a remote LTI service, user data needs to be exchanged with that service.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:userid&#039;] = &#039;The userid is sent from Moodle to allow you to access your data on the remote system.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:fullname&#039;] = &#039;Your full name is sent to the remote system to allow a better user experience.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to export user data===&lt;br /&gt;
&lt;br /&gt;
In order to export the user data that you store, you must implement the relevant request provider.&lt;br /&gt;
&lt;br /&gt;
We have named these request providers because they are called in response to a specific request from a user to access their information.&lt;br /&gt;
&lt;br /&gt;
There are several different types of request provider, and you may need to implement several of these, depending on the type and nature of your plugin.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking plugins will fit into one of the following categories:&lt;br /&gt;
&lt;br /&gt;
* Plugins which are a subplugin of another plugin. Examples include &#039;&#039;assignsubmission&#039;&#039;, &#039;&#039;atto&#039;&#039;, and &#039;&#039;datafield&#039;&#039;;&lt;br /&gt;
* Plugins which are typically called by a Moodle subsystem. Examples include &#039;&#039;qtype&#039;&#039;, and &#039;&#039;profilefield&#039;&#039;;&lt;br /&gt;
* All other plugins which store data.&lt;br /&gt;
&lt;br /&gt;
Most plugins will fit into this final category, whilst other plugins may fall into several categories.&lt;br /&gt;
Plugins which &#039;&#039;define&#039;&#039; a subplugin will also be responsible for  collecting this data from their subplugins.&lt;br /&gt;
&lt;br /&gt;
A final category exists - plugins which store user preferences. In some cases this may be the &#039;&#039;only&#039;&#039; provider implemented.&lt;br /&gt;
&lt;br /&gt;
====Standard plugins which store data====&lt;br /&gt;
&lt;br /&gt;
A majority of Moodle plugins will fit into this category and will be required to implement the &#039;&#039;\core_privacy\local\request\plugin\provider&#039;&#039; interface. This interface requires that you define four functions (the first two of which are dealt with in this section):&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;get_contexts_for_userid&#039;&#039; - to explain where data is held within Moodle for your plugin; and&lt;br /&gt;
* &#039;&#039;export_user_data&#039;&#039; - to export a user’s personal data from your plugin.&lt;br /&gt;
* &#039;&#039;delete_data_for_all_users_in_context&#039;&#039; - to delete all data for all users in the specified context.&lt;br /&gt;
* &#039;&#039;delete_data_for_user&#039;&#039; - to delete all user data for the specified user, in the specified contexts.&lt;br /&gt;
&lt;br /&gt;
These APIs make use of the Moodle &#039;&#039;context&#039;&#039; system to hierarchically store this data.&lt;br /&gt;
&lt;br /&gt;
=====Retrieving the list of contexts=====&lt;br /&gt;
&lt;br /&gt;
You are required to return the list of contexts for which the plugin stores data about the user. These are the standard Moodle contexts - CONTEXT_COURSE, CONTEXT_USER, CONTEXT_MODULE and so on. In many cases the link between the module type and the context is self-evident (e.g. activity modules). In some cases it may be less so. For example, an enrolment plugin (that stores user data, which most don&#039;t) would link to course context (users enrol in courses). Other types of plugins may be less obvious but you need to pick something. One way might to consider what context you would use when checking role capabilities. Consider that this will be used to structure the exported data and define what data is deleted when a context is expired. &lt;br /&gt;
&lt;br /&gt;
Contexts are retrieved using the &#039;&#039;get_contexts_for_userid&#039;&#039; function which takes the ID of the user being fetched, and returns a list of contexts in which the user has any data.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function returns a &#039;&#039;\core_privacy\local\request\contextlist&#039;&#039; which is used to keep a set of contexts together in a fixed fashion.&lt;br /&gt;
&lt;br /&gt;
Because a Subject Access Request covers &#039;&#039;every&#039;&#039; piece of data that is held for a user within Moodle, efficiency and performance is highly important. As a result, contexts are added to the &#039;&#039;contextlist&#039;&#039; by defining one or more SQL queries which return just the contextid. Multiple SQL queries can be added as required. &lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with specific subsystems and store data within them.&lt;br /&gt;
These subsystems will also provide a way in which to link the data that you have stored with your own database tables.&lt;br /&gt;
At present these are still a work in progress and only the &#039;&#039;core_ratings&#039;&#039; subsystem includes this.&lt;br /&gt;
&lt;br /&gt;
======Basic example======&lt;br /&gt;
&lt;br /&gt;
The following example simply fetches the contextid for all forums where a user has a single discussion (note: this is an incomplete example):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
        $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
&lt;br /&gt;
        $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                 FROM {context} c&lt;br /&gt;
           INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
           INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
           INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
                WHERE (&lt;br /&gt;
                d.userid        = :discussionuserid&lt;br /&gt;
                )&lt;br /&gt;
        &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        $params = [&lt;br /&gt;
            &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
            &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
            &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
        return $contextlist;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
======More complete example======&lt;br /&gt;
&lt;br /&gt;
The following example includes a link to core_rating. &lt;br /&gt;
It will find any forum, forum discussion, or forum post where the user has any data, including:&lt;br /&gt;
&lt;br /&gt;
* Per-forum digest preferences;&lt;br /&gt;
* Per-forum subscription preferences;&lt;br /&gt;
* Per-forum read tracking preferences;&lt;br /&gt;
* Per-discussion subscription preferences;&lt;br /&gt;
* Per-post read data (if a user has read a post or not); and&lt;br /&gt;
* Per-post rating data.&lt;br /&gt;
&lt;br /&gt;
In the case of the rating data, this will include any post where the user has rated the post of another user.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int           $userid       The user to search.&lt;br /&gt;
 * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
 */&lt;br /&gt;
public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
    $ratingsql = \core_rating\privacy\provider::get_sql_join(&#039;rat&#039;, &#039;mod_forum&#039;, &#039;post&#039;, &#039;p.id&#039;, $userid);&lt;br /&gt;
    // Fetch all forum discussions, and forum posts.&lt;br /&gt;
    $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                FROM {context} c&lt;br /&gt;
        INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
        INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
        INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_posts} p ON p.discussion = d.id&lt;br /&gt;
            LEFT JOIN {forum_digests} dig ON dig.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_read} hasread ON hasread.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id&lt;br /&gt;
            {$ratingsql-&amp;gt;join}&lt;br /&gt;
                WHERE (&lt;br /&gt;
                p.userid        = :postuserid OR&lt;br /&gt;
                d.userid        = :discussionuserid OR&lt;br /&gt;
                dig.userid      = :digestuserid OR&lt;br /&gt;
                sub.userid      = :subuserid OR&lt;br /&gt;
                pref.userid     = :prefuserid OR&lt;br /&gt;
                hasread.userid  = :hasreaduserid OR&lt;br /&gt;
                dsub.userid     = :dsubuserid OR&lt;br /&gt;
                {$ratingsql-&amp;gt;userwhere}&lt;br /&gt;
            )&lt;br /&gt;
    &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    $params = [&lt;br /&gt;
        &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
        &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
        &#039;postuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        &#039;digestuserid&#039;      =&amp;gt; $userid,&lt;br /&gt;
        &#039;subuserid&#039;         =&amp;gt; $userid,&lt;br /&gt;
        &#039;prefuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;hasreaduserid&#039;     =&amp;gt; $userid,&lt;br /&gt;
        &#039;dsubuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
    ];&lt;br /&gt;
    $params += $ratingsql-&amp;gt;params;&lt;br /&gt;
&lt;br /&gt;
    $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
    $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
    return $contextlist;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Exporting user data=====&lt;br /&gt;
&lt;br /&gt;
After determining where in Moodle your plugin holds data about a user, the &#039;&#039;\core_privacy\manager&#039;&#039; will then ask your plugin to export all user data for a subset of those locations.&lt;br /&gt;
&lt;br /&gt;
This is achieved through use of the &#039;&#039;export_user_data&#039;&#039; function which takes the list of approved contexts in a &#039;&#039;\core_privacy\local\request\approved_contextlist&#039;&#039; object.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   approved_contextlist    $contextlist    The approved contexts to export information for.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_data(approved_contextlist $contextlist) {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;approved_contextlist&#039;&#039; includes both the user record, and a list of contexts, which can be retrieved by either processing it as an Iterator, or by calling &#039;&#039;get_contextids()&#039;&#039; or &#039;&#039;get_contexts()&#039;&#039; as required.&lt;br /&gt;
&lt;br /&gt;
Data is exported using a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which is described in further detail below.&lt;br /&gt;
&lt;br /&gt;
====Plugins which store user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins store a variety of user preferences, and must therefore export them.&lt;br /&gt;
&lt;br /&gt;
Since user preferences are a site-wide preference, these are exported separately to other user data.&lt;br /&gt;
In some cases the only data present is user preference data, whilst in others there is a combination of user-provided data, and user preferences.&lt;br /&gt;
&lt;br /&gt;
Storing of user preferences is achieved through implementation of the &#039;&#039;\core_privacy\local\request\user_preference_provider&#039;&#039; interface which defines one required function -- &#039;&#039;export_user_preferences&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user preferences for the plugin.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int         $userid The userid of the user whose data is to be exported.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_preferences(int $userid) {&lt;br /&gt;
    $markasreadonnotification = get_user_preference(&#039;markasreadonnotification&#039;, null, $userid);&lt;br /&gt;
    if (null !== $markasreadonnotification) {&lt;br /&gt;
        switch ($markasreadonnotification) {&lt;br /&gt;
            case 0:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationno&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
            case 1:&lt;br /&gt;
            default:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationyes&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
        writer::export_user_preference(&#039;mod_forum&#039;, &#039;markasreadonnotification&#039;, $markasreadonnotification, $markasreadonnotificationdescription);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Plugins which can have own subplugins ====&lt;br /&gt;
&lt;br /&gt;
Many plugin types are also able to define their own subplugins and will need to define a contract between themselves and their subplugins in order to fetch their data.&lt;br /&gt;
&lt;br /&gt;
This is required as the parent plugin and the child subplugin should be separate entities and the parent plugin must be able to function if one or more of its subplugins are uninstalled.&lt;br /&gt;
&lt;br /&gt;
The parent plugin is responsible for defining the contract,  and for interacting with its subplugins, though we intend to create helpers to make this easier.&lt;br /&gt;
&lt;br /&gt;
The parent plugin should define a new interface for each type of subplugin that it defines. This interface should extend the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
===== When a parent plugin should and should not provide the interface for its subplugins =====&lt;br /&gt;
&lt;br /&gt;
There can be cases when there is no point for a plugin to provide the &amp;quot;subplugin_provider&amp;quot; based interface, even if it has own subplugins. See the Atto or TinyMCE editors as real examples.&lt;br /&gt;
&lt;br /&gt;
If the parent plugin has no data passed through to the subplugins, there is no benefit in defining a subplugin provider. For example, Atto subplugins are just used to enhance the functionality and they never receive anything like a context. Most of the time we need to define a subplugin provider, but in cases where there is no data passed from the plugin to its subplugins, there is no need to define the subplugin provider. If the subplugins still do store personal data that are not related to the parent plugin in any way, then subplugins should define their own standard provider.&lt;br /&gt;
&lt;br /&gt;
Compare with something like mod_assign where the subplugins store data for the parent and that data is contextually relevant to the parent plugin. In those cases the subplugin stores data for the plugin and it only makes sense to do so in the context of its parent plugin.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
The following example defines the contract that assign submission subplugins may be required to implement.&lt;br /&gt;
&lt;br /&gt;
The assignment module is responsible for returning the contexts of all assignments where a user has data, but in some cases it is unaware of all of those cases - for example if a Teacher comments on a student submission it may not be aware of these as the information about this interaction may not be stored within its own tables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/assign/privacy/assignsubmission_provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_assign\privacy;&lt;br /&gt;
use \core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
interface assignsubmission_provider extends&lt;br /&gt;
    // This Interface defines a subplugin.&lt;br /&gt;
    \core_privacy\local\request\plugin\subplugin_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the SQL required to find all submission items where this user has had any involvements. &lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  \stdClass                   Object containing the join, params, and where used to select a these records from the database.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_items_with_user_interaction(int $userid) : \stdClass ;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Export all relevant user submissions information which match the combination of userid and attemptid.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @param   \context      $context      The context to export this submission against.&lt;br /&gt;
     * @param   array         $subcontext   The subcontext within the context to export this information&lt;br /&gt;
     * @param   int           $attid        The id of the submission to export.&lt;br /&gt;
     */&lt;br /&gt;
    public static function export_user_submissions(int $userid, \context $context, array $subcontext, int $attid) ;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are subplugins to another plugin====&lt;br /&gt;
&lt;br /&gt;
If you are developing a sub-plugin of another plugin, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subplugin type should define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface and it is up to the parent plugin to define how they will interact with their children.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/pluginname/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace assignsubmission\onlinetext;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does store personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is a subplugin of assign and must meet that contract.&lt;br /&gt;
    \mod_assign\privacy\assignsubmission_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are typically called by a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
There are a number of plugintypes in Moodle which are typically called by a specific Moodle subsystem.&lt;br /&gt;
&lt;br /&gt;
Some of these are &#039;&#039;only&#039;&#039; called by that subsystem, for example plugins which are of the &#039;&#039;plagiarism&#039;&#039; plugintype should never be called directly, but are always invoked via the &#039;&#039;core_plagiarism&#039;&#039; subsystem.&lt;br /&gt;
&lt;br /&gt;
Conversely, there maybe other plugintypes which can be called both via a subsystem, and in some other fashion. We are still determining whether any plugintypes currently fit this pattern.&lt;br /&gt;
&lt;br /&gt;
If you are developing a plugin which belongs to a specific subsystem, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subsystem will define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subsystem_provider&#039;&#039; interface and it is up to that subsystem to define how they will interact with those plugins.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;plagiarism/detectorator/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace plagiarism_detectorator\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does export personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is always linked against another activity module via the Plagiarism API.&lt;br /&gt;
    \core_plagiarism\privacy\plugin_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Exporting data====&lt;br /&gt;
&lt;br /&gt;
Any plugin which stores data must also export it.&lt;br /&gt;
&lt;br /&gt;
To cater for this the privacy API includes a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which defines a set of functions to store different types of data.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking data is broken into the following types:&lt;br /&gt;
&lt;br /&gt;
* Data - this is the object being described. For example the post content in a forum post;&lt;br /&gt;
* Related data - this is data related to the object being stored. For example, ratings of a forum post;&lt;br /&gt;
* Metadata - This is metadata about the main object. For example whether you are subscribed to a forum discussion;&lt;br /&gt;
* User preferences - this is data about a site-wide preference;&lt;br /&gt;
* Files - Any files that you are stored within Moodle on behalf of this plugin; and&lt;br /&gt;
* Custom files - For custom file formats - e.g. a calendar feed for calendar data. These should be used sparingly.&lt;br /&gt;
&lt;br /&gt;
Each piece of data is stored against a specific Moodle &#039;&#039;context&#039;&#039;, which will define how the data is structured within the exporter.&lt;br /&gt;
Data, and Related data only accept the &#039;&#039;stdClass&#039;&#039; object, whilst metadata should be stored as a set of key/value pairs which include a description.&lt;br /&gt;
&lt;br /&gt;
In some cases the data being stored belongs within an implicit structure. For example, one forum has many forum discussions, which each have a number of forum posts. This structure is represented by an &#039;&#039;array&#039;&#039; referred to as a &#039;&#039;subcontext&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;content_writer&#039;&#039; must &#039;&#039;always&#039;&#039; be called with a specific context, and can be called as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
writer::with_context($context)&lt;br /&gt;
    -&amp;gt;export_data($subcontext, $post)&lt;br /&gt;
    -&amp;gt;export_area_files($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id)&lt;br /&gt;
    -&amp;gt;export_metadata($subcontext, &#039;postread&#039;, (object) [&#039;firstread&#039; =&amp;gt; $firstread], new \lang_string(&#039;privacy:export:post:postread&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any text field which supports Moodle files must also be rewritten:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
$post-&amp;gt;message = writer::with_context($context)&lt;br /&gt;
    -&amp;gt;rewrite_pluginfile_urls($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id, $post-&amp;gt;message);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to delete user data===&lt;br /&gt;
&lt;br /&gt;
Deleting user data is also implemented in the request interface. There are two methods that need to be created. The first one to remove all user data from a context, the other to remove user data for a specific user in a list of contexts.&lt;br /&gt;
&lt;br /&gt;
====Delete for a context====&lt;br /&gt;
&lt;br /&gt;
A context is given and all user data (for all users) is to be deleted from the plugin. This will be called when the retention period for the context has expired to adhere to the privacy by design requirement. Retention periods are set in the Data registry.&lt;br /&gt;
&lt;br /&gt;
Note that this will be called for &#039;&#039;&#039;any&#039;&#039;&#039; context being expired, not only those the plugin holds data for (as returned by get_contexts_for_userid()), so you must carefully check the contexts given.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Delete all personal data for all users in the specified context.&lt;br /&gt;
 *&lt;br /&gt;
 * @param context $context Context to delete data from.&lt;br /&gt;
 */&lt;br /&gt;
public static function delete_data_for_all_users_in_context(\context $context) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;choice&#039;, $context-&amp;gt;instanceid);&lt;br /&gt;
    if (!$cm) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Delete personal information for a specific user and context(s)====&lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;approved_contextlist&#039;&#039; is given and user data related to that user should either be completely deleted, or overwritten if a structure needs to be maintained. This will be called when a user has requested the right to be forgotten. All attempts should be made to delete this data where practical while still allowing the plugin to be used by other users.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/choice/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function delete_data_for_user(approved_contextlist $contextlist) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
    &lt;br /&gt;
    if (empty($contextlist-&amp;gt;count())) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    $userid = $contextlist-&amp;gt;get_user()-&amp;gt;id;&lt;br /&gt;
    foreach ($contextlist-&amp;gt;get_contexts() as $context) {&lt;br /&gt;
        $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid, &#039;userid&#039; =&amp;gt; $userid]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Difference between Moodle 3.3 and more recent versions==&lt;br /&gt;
Moodle 3.3 has a minimum requirement of php 5.6 and so type hinting and return type declarations are not supported in this version. &lt;br /&gt;
Consequently the privacy API for this version does not have these features.&lt;br /&gt;
==Common Questions==&lt;br /&gt;
===What to do if you have one plugin that supports multiple branches===&lt;br /&gt;
This is something that we have considered and we have put in place a polyfill. This gets around the restrictions of one version having type hinting and return type declarations while another does not.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
To use the polyfill include the legacy polyfill trait and create the necessary static methods but with an underscore (shown below).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
    \core_privacy\local\request\plugin\provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===What to do if your plugin must implement a subplugin or subsystem plugin provider===&lt;br /&gt;
For subplugins (e.g. assignsubmission, assignfeedback, quiz report, quiz access rules), or subsystems which have a plugintype relationship (portfolio, plagiarism, and others), they will also define their own legacy polyfill.&lt;br /&gt;
&lt;br /&gt;
In this instance you will need to include the trait for both the core polyfill, and the provider polyfill as appropriate.&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin has data and must therefore define the metadata provider in order to describe it.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This is a plagiarism plugin. It interacts with the plagiarism subsystem rather than with core.&lt;br /&gt;
    \core_plagiarism\privacy\plagiarism_provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the metadata provider.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the plagirism provider.&lt;br /&gt;
    use \core_plagiarism\privacy\plagiarism_provider\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // This is one of the polyfilled methods from the plagiarism provider.&lt;br /&gt;
    public static function _export_plagiarism_user_data($userid, \context $context, array $subcontext, array $linkarray) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips for development ==&lt;br /&gt;
&lt;br /&gt;
* While implementing the privacy API into your plugin, there are CLI scripts that can help you to test things on the fly. Just don&#039;t forget these are not supposed to replace proper unit tests. See [[Privacy API/Utilities]] for details.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Subject Access Request FAQ]]&lt;br /&gt;
* [[:en:GDPR|GDPR]] in the user documentation&lt;br /&gt;
&lt;br /&gt;
[[Category:Privacy]]&lt;br /&gt;
[[Category:GDPR]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54165</id>
		<title>Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54165"/>
		<updated>2018-05-14T21:20:52Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Retrieving the list of contexts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The [https://en.wikipedia.org/wiki/General_Data_Protection_Regulation General Data Protection Regulation] (GDPR) is an EU directive that looks at providing users with more control over their data and how it is processed. This regulation will come into effect on 25th of May 2018 and covers any citizen or permanent resident of the European Union. The directive will be respected by a number of other countries outside of the European Union.&lt;br /&gt;
&lt;br /&gt;
To help institutions become compliant with this new regulation we are adding functionality to Moodle. This includes a number of components, amongst others these include a user’s right to:&lt;br /&gt;
&lt;br /&gt;
* request information on the types of personal data held, the instances of that data, and the deletion policy for each;&lt;br /&gt;
* access all of their data; and&lt;br /&gt;
* be forgotten.&lt;br /&gt;
&lt;br /&gt;
The compliance requirements also extend to installed plugins (including third party plugins). These need to also be able to report what information they store or process regarding users, and have the ability to provide and delete data for a user request.&lt;br /&gt;
&lt;br /&gt;
This document describes the proposed API changes required for plugins which will allow a Moodle installation to become GDPR compliant.&lt;br /&gt;
&lt;br /&gt;
Target Audience: The intended audience for this document is Moodle plugin developers, who are aiming to ensure their plugins are updated to comply with GDPR requirements coming into effect in the EU in May, 2018.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Personal data in Moodle==&lt;br /&gt;
&lt;br /&gt;
From the GDPR Spec, Article 4:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;‘personal data’ means any information relating to an identified or identifiable natural person (‘data subject’); an identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In Moodle, we need to consider two main types of personal data; information entered by the user and information stored about the user. The key difference being that information stored about the user will have come from a source other than the user themselves. Both types of data can be used to form a profile of the individual.&lt;br /&gt;
&lt;br /&gt;
The most obvious clue to finding personal data entered by the user is the presence of a userid on a database field. Any data on the record (or linked records) pertaining to that user may be deemed personal data for that user, including things like timestamps and record identification numbers. Additionally, any free text field which allows the user to enter information must also be considered to be the personal data of that user.&lt;br /&gt;
&lt;br /&gt;
Data stored about the user includes things like ratings and comments made on a student submission. These may have been made by an assessor or teacher, but are considered the personal data of the student, as they are considered a reflection of the user’s competency in the subject matter and can be used to form a profile of that individual. &lt;br /&gt;
&lt;br /&gt;
The sections that follow outline what you need to do as a plugin developer to ensure any personal data is advertised and can be accessed and deleted according to the GDPR requirements.&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
===Architecture overview===&lt;br /&gt;
&lt;br /&gt;
[[File:MoodlePrivacyMetadataUML.png|thumb|UML diagram of the metadata part of the privacy subsystem]]&lt;br /&gt;
[[File:MoodlePrivacyRequestUML.png|thumb|UML diagram of the request providers part of the privacy subsystem]]&lt;br /&gt;
&lt;br /&gt;
A new system for Privacy has been created within Moodle. This is broken down into several main parts and forms the &#039;&#039;core_privacy&#039;&#039; subsystem:&lt;br /&gt;
&lt;br /&gt;
* Some metadata providers - a set of PHP interfaces to be implemented by components for that component to describe the kind of data that it stores, and the purpose for its storage;&lt;br /&gt;
* Some request providers - a set of PHP interfaces to be implemented by components to allow that component to act upon user requests such as the Right to be Forgotten, and a Subject Access Request; and&lt;br /&gt;
* A manager - a concrete class used to bridge components which implement the providers with tools which request their data.&lt;br /&gt;
&lt;br /&gt;
All plugins will implement one metadata provider, and zero, one or two request providers.&lt;br /&gt;
&lt;br /&gt;
The fetching of data is broken into two separate steps:&lt;br /&gt;
&lt;br /&gt;
# Detecting in which Moodle contexts the user has any data; and&lt;br /&gt;
# Exporting all data from each of those contexts.&lt;br /&gt;
&lt;br /&gt;
This has been broken into two steps to later allow administrators to exclude certain contexts from an export - e.g. for courses currently in progress.&lt;br /&gt;
&lt;br /&gt;
A third component will later be added to facilitate the deletion of data within these contexts which will help to satisfy the Right to be Forgotten. This will also use the first step.&lt;br /&gt;
&lt;br /&gt;
Please refer to the inline phpdocs of the [https://github.com/moodle/moodle/blob/v3.5.0-beta/privacy/classes/manager.php#L31 core_privacy::manager class] for detailed description of the interfaces, their hierarchy and meaning.&lt;br /&gt;
&lt;br /&gt;
===Implementing a provider===&lt;br /&gt;
&lt;br /&gt;
All plugins will need to create a concrete class which implements the relevant metadata and request providers. The exact providers you need to implement will depend on what data you store, and the type of plugin. This is covered in more detail in the following sections of the document.&lt;br /&gt;
&lt;br /&gt;
In order to do so:&lt;br /&gt;
&lt;br /&gt;
# You must create a class called &#039;&#039;provider&#039;&#039; within the namespace &#039;&#039;\your_pluginname\privacy&#039;&#039;.&lt;br /&gt;
# This class must be created at &#039;&#039;path/to/your/plugin/classes/privacy/provider.php&#039;&#039;.&lt;br /&gt;
# You must have your class implement the relevant metadata and request interfaces.&lt;br /&gt;
&lt;br /&gt;
==Plugins which do not store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do not store any personal data. This is usually the case for plugins which just add functionality, or which display the data already stored elsewhere in Moodle.&lt;br /&gt;
&lt;br /&gt;
Some examples of plugin types which might fit this criteria include themes, blocks, filters, editor plugins, etc.&lt;br /&gt;
&lt;br /&gt;
Plugins which cause data to be stored elsewhere in Moodle (e.g. via a subsystem call) are considered to store data.&lt;br /&gt;
&lt;br /&gt;
One examples of a plugin which does not store any data would be the Calendar month block which just displays a view of the user’s calendar. It does not store any data itself.&lt;br /&gt;
&lt;br /&gt;
An example of a plugin which must not use the null provider is the Comments block. The comments block is responsible for data subsequently being stored within Moodle. Although the block doesn’t store anything itself, it interacts with the comments subsystem and is the only component which knows how that data maps to a user.&lt;br /&gt;
&lt;br /&gt;
===Implementation requirements===&lt;br /&gt;
&lt;br /&gt;
In order to let Moodle know that you have audited your plugin, and that you do not store any personal user data, you must implement the &#039;&#039;\core_privacy\local\metadata\null_provider&#039;&#039; interface in your plugin’s provider.&lt;br /&gt;
&lt;br /&gt;
These null providers can only be implemented where a plugin has:&lt;br /&gt;
&lt;br /&gt;
* no external links (e.g. sends data to an external service like an LTI provider, repository plugin which you can search on)&lt;br /&gt;
* no database tables which store user data (including IP addresses)&lt;br /&gt;
* no user preferences&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;null_provider&#039;&#039; requires you to define one function &#039;&#039;get_reason()&#039;&#039; which returns the language string identifier within your component.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 &amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace block_calendar_month\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
    // This plugin does not store any personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\null_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the language string identifier with the component&#039;s language&lt;br /&gt;
     * file to explain why this plugin stores no data.&lt;br /&gt;
     *&lt;br /&gt;
     * @return  string&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_reason() : string {&lt;br /&gt;
        return &#039;privacy:metadata&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/lang/en/block_calendar_month.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
$string[&#039;privacy:metadata&#039;] = &#039;The Calendar block only displays existing calendar data.&#039;;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That’s it. Congratulations, your plugin now implements the Privacy API.&lt;br /&gt;
&lt;br /&gt;
==Plugins which store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do store some form of personal data.&lt;br /&gt;
&lt;br /&gt;
In some cases this will be stored within database tables in your plugin, and in other cases this will be in one of Moodle’s core subsystems - for example your plugin may store files, ratings, comments, or tags.&lt;br /&gt;
&lt;br /&gt;
Plugins which do store data will need to:&lt;br /&gt;
&lt;br /&gt;
* Describe the type of data that they store; &lt;br /&gt;
* Provide a way to export that data; and&lt;br /&gt;
* Provide a way to delete that data.&lt;br /&gt;
&lt;br /&gt;
Data is described via a &#039;&#039;metadata&#039;&#039; provider, and it is both exported and deleted via an implementation of a &#039;&#039;request&#039;&#039; provider.&lt;br /&gt;
&lt;br /&gt;
These are both explained in the sections below.&lt;br /&gt;
&lt;br /&gt;
===Describing the type of data you store===&lt;br /&gt;
&lt;br /&gt;
In order to describe the type of data that you store, you must implement the &#039;&#039;\core_privacy\local\metadata\provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
This interfaces requires that you define one function: &#039;&#039;get_metadata&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There are several types of item to describe the data that you store. These are for:&lt;br /&gt;
&lt;br /&gt;
* Items in the Moodle database;&lt;br /&gt;
* Items stored by you in a Moodle subsystem - for example files, and ratings; and&lt;br /&gt;
* User preferences stored site-wide within Moodle for your plugin&lt;br /&gt;
&lt;br /&gt;
Note: All fields should include a description from a language string within your plugin.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_forum\privacy;&lt;br /&gt;
use core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
        // This plugin does store personal user data.&lt;br /&gt;
        \core_privacy\local\metadata\provider {&lt;br /&gt;
&lt;br /&gt;
    public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
        // Here you will add more items into the collection.&lt;br /&gt;
&lt;br /&gt;
        return $collection;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store content in a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
Many plugins will use one of the core Moodle subsystems to store data.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer we do not expect you to describe those subsystems in detail, but we do need to know that you use them and to know what you use them for.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_subsystem_link()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_subsystem_link(&lt;br /&gt;
        &#039;core_files&#039;,&lt;br /&gt;
        [],&lt;br /&gt;
        &#039;privacy:metadata:core_files&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:core_files&#039;] = &#039;The forum stores files which have been uploaded by the user to form part of a forum post.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Describing data stored in database tables====&lt;br /&gt;
&lt;br /&gt;
Most Moodle plugins will store some form of user data in their own database tables.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each database table, and each field which includes user data.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_database_table(&lt;br /&gt;
        &#039;forum_discussion_subs&#039;,&lt;br /&gt;
         [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:userid&#039;,&lt;br /&gt;
            &#039;discussionid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:discussionid&#039;,&lt;br /&gt;
            &#039;preference&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:preference&#039;,&lt;br /&gt;
&lt;br /&gt;
         ],&lt;br /&gt;
        &#039;privacy:metadata:forum_discussion_subs&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs&#039;] = &#039;Information about the subscriptions to individual forum discussions. This includes when a user has chosen to subscribe to a discussion, or to unsubscribe from one where they would otherwise be subscribed.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:userid&#039;] = &#039;The ID of the user with this subscription preference.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:discussionid&#039;] = &#039;The ID of the discussion that was subscribed to.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:preference&#039;] = &#039;The start time of the subscription.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store site-wide user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins will include one or more user preferences. Unfortunately this is one of Moodle’s older components and many of the values stored are not pure user preferences. Each plugin should be aware of how it handles its own preferences and is best placed to determine whether they are site-wide preferences, or per-instance preferences.&lt;br /&gt;
&lt;br /&gt;
Whilst most of these will have a fixed name (e.g. &#039;&#039;filepicker_recentrepository&#039;&#039;), some will include a variable of some kind (e.g. &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039;). Only the general name needs to be indicated rather than one copy for each preference.&lt;br /&gt;
&lt;br /&gt;
Also, these should only be &#039;&#039;site-wide&#039;&#039; user preferences which do not belong to a specific Moodle context.&lt;br /&gt;
&lt;br /&gt;
In the above examples:&lt;br /&gt;
&lt;br /&gt;
* Preference &#039;&#039;filepicker_recentrepository&#039;&#039; belongs to the file subsystem, and is a site-wide preference affecting the user anywhere that they view the filepicker.&lt;br /&gt;
* Preference &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039; belongs to user tours. User tours are a site-wide feature which can affect many parts of Moodle and cross multiple contexts.&lt;br /&gt;
&lt;br /&gt;
In some cases a value may be stored in the preferences table but is known to belong to a specific context within Moodle. In these cases they should be stored as metadata against that context rather than as a site-wide user preference.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_user_preference()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Any plugin providing user preferences must also implement the &#039;&#039;\core_privacy\local\request\preference_provider&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_user_preference(&#039;tool_usertours_tour_completion_time&#039;,&lt;br /&gt;
        &#039;privacy:metadata:preference:tool_usertours_tour_completion_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/lang/en/tool_usertours.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:tool_usertours_tour_completion_time&#039;] = &#039;The time that a specific user tour was last completed by a user.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you export data to an external location====&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with external systems - for example cloud-based services. Often this external location is configurable within the plugin either at the site or the instance level.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each &#039;&#039;type&#039;&#039; of target destination, alongside a list of each exported field which includes user data.&lt;br /&gt;
The &#039;&#039;actual&#039;&#039; destination does not need to be described as this can change based on configuration.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_external_location_link()&#039;&#039; method on the collection.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_external_location_link(&#039;lti_client&#039;, [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:lti_client:userid&#039;,&lt;br /&gt;
            &#039;fullname&#039; =&amp;gt; &#039;privacy:metadata:lti_client:fullname&#039;,&lt;br /&gt;
        ], &#039;privacy:metadata:lti_client&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/lang/en/lti.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client&#039;] = &#039;In order to integrate with a remote LTI service, user data needs to be exchanged with that service.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:userid&#039;] = &#039;The userid is sent from Moodle to allow you to access your data on the remote system.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:fullname&#039;] = &#039;Your full name is sent to the remote system to allow a better user experience.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to export user data===&lt;br /&gt;
&lt;br /&gt;
In order to export the user data that you store, you must implement the relevant request provider.&lt;br /&gt;
&lt;br /&gt;
We have named these request providers because they are called in response to a specific request from a user to access their information.&lt;br /&gt;
&lt;br /&gt;
There are several different types of request provider, and you may need to implement several of these, depending on the type and nature of your plugin.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking plugins will fit into one of the following categories:&lt;br /&gt;
&lt;br /&gt;
* Plugins which are a subplugin of another plugin. Examples include &#039;&#039;assignsubmission&#039;&#039;, &#039;&#039;atto&#039;&#039;, and &#039;&#039;datafield&#039;&#039;;&lt;br /&gt;
* Plugins which are typically called by a Moodle subsystem. Examples include &#039;&#039;qtype&#039;&#039;, and &#039;&#039;profilefield&#039;&#039;;&lt;br /&gt;
* All other plugins which store data.&lt;br /&gt;
&lt;br /&gt;
Most plugins will fit into this final category, whilst other plugins may fall into several categories.&lt;br /&gt;
Plugins which &#039;&#039;define&#039;&#039; a subplugin will also be responsible for  collecting this data from their subplugins.&lt;br /&gt;
&lt;br /&gt;
A final category exists - plugins which store user preferences. In some cases this may be the &#039;&#039;only&#039;&#039; provider implemented.&lt;br /&gt;
&lt;br /&gt;
====Standard plugins which store data====&lt;br /&gt;
&lt;br /&gt;
A majority of Moodle plugins will fit into this category and will be required to implement the &#039;&#039;\core_privacy\local\request\plugin\provider&#039;&#039; interface. This interface requires that you define four functions (the first two of which are dealt with in this section):&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;get_contexts_for_userid&#039;&#039; - to explain where data is held within Moodle for your plugin; and&lt;br /&gt;
* &#039;&#039;export_user_data&#039;&#039; - to export a user’s personal data from your plugin.&lt;br /&gt;
* &#039;&#039;delete_data_for_all_users_in_context&#039;&#039; - to delete all data for all users in the specified context.&lt;br /&gt;
* &#039;&#039;delete_data_for_user&#039;&#039; - to delete all user data for the specified user, in the specified contexts.&lt;br /&gt;
&lt;br /&gt;
These APIs make use of the Moodle &#039;&#039;context&#039;&#039; system to hierarchically store this data.&lt;br /&gt;
&lt;br /&gt;
=====Retrieving the list of contexts=====&lt;br /&gt;
&lt;br /&gt;
You are required to return the list of contexts for which the plugin stores data about the user. These are the standard Moodle contexts - CONTEXT_COURSE, CONTEXT_USER, CONTEXT_MODULE and so on. In may cases the link between the module type and the context is self-evident (e.g. activity modules). In some cases it may be less so. For example, an enrolment plugin (that stores user data, which most don&#039;t) would link to course context (users enrol in courses). Other types of plugins may be less obvious but you need to pick something. One way might to consider what context you would use when checking role capabilities. Consider that this will be used to structure the exported data and define what data is deleted when a context is expired. &lt;br /&gt;
&lt;br /&gt;
Contexts are retrieved using the &#039;&#039;get_contexts_for_userid&#039;&#039; function which takes the ID of the user being fetched, and returns a list of contexts in which the user has any data.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function returns a &#039;&#039;\core_privacy\local\request\contextlist&#039;&#039; which is used to keep a set of contexts together in a fixed fashion.&lt;br /&gt;
&lt;br /&gt;
Because a Subject Access Request covers &#039;&#039;every&#039;&#039; piece of data that is held for a user within Moodle, efficiency and performance is highly important. As a result, contexts are added to the &#039;&#039;contextlist&#039;&#039; by defining one or more SQL queries which return just the contextid. Multiple SQL queries can be added as required. &lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with specific subsystems and store data within them.&lt;br /&gt;
These subsystems will also provide a way in which to link the data that you have stored with your own database tables.&lt;br /&gt;
At present these are still a work in progress and only the &#039;&#039;core_ratings&#039;&#039; subsystem includes this.&lt;br /&gt;
&lt;br /&gt;
======Basic example======&lt;br /&gt;
&lt;br /&gt;
The following example simply fetches the contextid for all forums where a user has a single discussion (note: this is an incomplete example):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
        $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
&lt;br /&gt;
        $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                 FROM {context} c&lt;br /&gt;
           INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
           INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
           INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
                WHERE (&lt;br /&gt;
                d.userid        = :discussionuserid&lt;br /&gt;
                )&lt;br /&gt;
        &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        $params = [&lt;br /&gt;
            &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
            &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
            &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
        return $contextlist;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
======More complete example======&lt;br /&gt;
&lt;br /&gt;
The following example includes a link to core_rating. &lt;br /&gt;
It will find any forum, forum discussion, or forum post where the user has any data, including:&lt;br /&gt;
&lt;br /&gt;
* Per-forum digest preferences;&lt;br /&gt;
* Per-forum subscription preferences;&lt;br /&gt;
* Per-forum read tracking preferences;&lt;br /&gt;
* Per-discussion subscription preferences;&lt;br /&gt;
* Per-post read data (if a user has read a post or not); and&lt;br /&gt;
* Per-post rating data.&lt;br /&gt;
&lt;br /&gt;
In the case of the rating data, this will include any post where the user has rated the post of another user.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int           $userid       The user to search.&lt;br /&gt;
 * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
 */&lt;br /&gt;
public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
    $ratingsql = \core_rating\privacy\provider::get_sql_join(&#039;rat&#039;, &#039;mod_forum&#039;, &#039;post&#039;, &#039;p.id&#039;, $userid);&lt;br /&gt;
    // Fetch all forum discussions, and forum posts.&lt;br /&gt;
    $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                FROM {context} c&lt;br /&gt;
        INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
        INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
        INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_posts} p ON p.discussion = d.id&lt;br /&gt;
            LEFT JOIN {forum_digests} dig ON dig.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_read} hasread ON hasread.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id&lt;br /&gt;
            {$ratingsql-&amp;gt;join}&lt;br /&gt;
                WHERE (&lt;br /&gt;
                p.userid        = :postuserid OR&lt;br /&gt;
                d.userid        = :discussionuserid OR&lt;br /&gt;
                dig.userid      = :digestuserid OR&lt;br /&gt;
                sub.userid      = :subuserid OR&lt;br /&gt;
                pref.userid     = :prefuserid OR&lt;br /&gt;
                hasread.userid  = :hasreaduserid OR&lt;br /&gt;
                dsub.userid     = :dsubuserid OR&lt;br /&gt;
                {$ratingsql-&amp;gt;userwhere}&lt;br /&gt;
            )&lt;br /&gt;
    &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    $params = [&lt;br /&gt;
        &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
        &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
        &#039;postuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        &#039;digestuserid&#039;      =&amp;gt; $userid,&lt;br /&gt;
        &#039;subuserid&#039;         =&amp;gt; $userid,&lt;br /&gt;
        &#039;prefuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;hasreaduserid&#039;     =&amp;gt; $userid,&lt;br /&gt;
        &#039;dsubuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
    ];&lt;br /&gt;
    $params += $ratingsql-&amp;gt;params;&lt;br /&gt;
&lt;br /&gt;
    $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
    $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
    return $contextlist;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Exporting user data=====&lt;br /&gt;
&lt;br /&gt;
After determining where in Moodle your plugin holds data about a user, the &#039;&#039;\core_privacy\manager&#039;&#039; will then ask your plugin to export all user data for a subset of those locations.&lt;br /&gt;
&lt;br /&gt;
This is achieved through use of the &#039;&#039;export_user_data&#039;&#039; function which takes the list of approved contexts in a &#039;&#039;\core_privacy\local\request\approved_contextlist&#039;&#039; object.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   approved_contextlist    $contextlist    The approved contexts to export information for.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_data(approved_contextlist $contextlist) {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;approved_contextlist&#039;&#039; includes both the user record, and a list of contexts, which can be retrieved by either processing it as an Iterator, or by calling &#039;&#039;get_contextids()&#039;&#039; or &#039;&#039;get_contexts()&#039;&#039; as required.&lt;br /&gt;
&lt;br /&gt;
Data is exported using a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which is described in further detail below.&lt;br /&gt;
&lt;br /&gt;
====Plugins which store user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins store a variety of user preferences, and must therefore export them.&lt;br /&gt;
&lt;br /&gt;
Since user preferences are a site-wide preference, these are exported separately to other user data.&lt;br /&gt;
In some cases the only data present is user preference data, whilst in others there is a combination of user-provided data, and user preferences.&lt;br /&gt;
&lt;br /&gt;
Storing of user preferences is achieved through implementation of the &#039;&#039;\core_privacy\local\request\user_preference_provider&#039;&#039; interface which defines one required function -- &#039;&#039;export_user_preferences&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user preferences for the plugin.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int         $userid The userid of the user whose data is to be exported.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_preferences(int $userid) {&lt;br /&gt;
    $markasreadonnotification = get_user_preference(&#039;markasreadonnotification&#039;, null, $userid);&lt;br /&gt;
    if (null !== $markasreadonnotification) {&lt;br /&gt;
        switch ($markasreadonnotification) {&lt;br /&gt;
            case 0:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationno&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
            case 1:&lt;br /&gt;
            default:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationyes&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
        writer::export_user_preference(&#039;mod_forum&#039;, &#039;markasreadonnotification&#039;, $markasreadonnotification, $markasreadonnotificationdescription);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Plugins which can have own subplugins ====&lt;br /&gt;
&lt;br /&gt;
Many plugin types are also able to define their own subplugins and will need to define a contract between themselves and their subplugins in order to fetch their data.&lt;br /&gt;
&lt;br /&gt;
This is required as the parent plugin and the child subplugin should be separate entities and the parent plugin must be able to function if one or more of its subplugins are uninstalled.&lt;br /&gt;
&lt;br /&gt;
The parent plugin is responsible for defining the contract,  and for interacting with its subplugins, though we intend to create helpers to make this easier.&lt;br /&gt;
&lt;br /&gt;
The parent plugin should define a new interface for each type of subplugin that it defines. This interface should extend the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
===== When a parent plugin should and should not provide the interface for its subplugins =====&lt;br /&gt;
&lt;br /&gt;
There can be cases when there is no point for a plugin to provide the &amp;quot;subplugin_provider&amp;quot; based interface, even if it has own subplugins. See the Atto or TinyMCE editors as real examples.&lt;br /&gt;
&lt;br /&gt;
If the parent plugin has no data passed through to the subplugins, there is no benefit in defining a subplugin provider. For example, Atto subplugins are just used to enhance the functionality and they never receive anything like a context. Most of the time we need to define a subplugin provider, but in cases where there is no data passed from the plugin to its subplugins, there is no need to define the subplugin provider. If the subplugins still do store personal data that are not related to the parent plugin in any way, then subplugins should define their own standard provider.&lt;br /&gt;
&lt;br /&gt;
Compare with something like mod_assign where the subplugins store data for the parent and that data is contextually relevant to the parent plugin. In those cases the subplugin stores data for the plugin and it only makes sense to do so in the context of its parent plugin.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
The following example defines the contract that assign submission subplugins may be required to implement.&lt;br /&gt;
&lt;br /&gt;
The assignment module is responsible for returning the contexts of all assignments where a user has data, but in some cases it is unaware of all of those cases - for example if a Teacher comments on a student submission it may not be aware of these as the information about this interaction may not be stored within its own tables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/assign/privacy/assignsubmission_provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_assign\privacy;&lt;br /&gt;
use \core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
interface assignsubmission_provider extends&lt;br /&gt;
    // This Interface defines a subplugin.&lt;br /&gt;
    \core_privacy\local\request\plugin\subplugin_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the SQL required to find all submission items where this user has had any involvements. &lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  \stdClass                   Object containing the join, params, and where used to select a these records from the database.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_items_with_user_interaction(int $userid) : \stdClass ;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Export all relevant user submissions information which match the combination of userid and attemptid.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @param   \context      $context      The context to export this submission against.&lt;br /&gt;
     * @param   array         $subcontext   The subcontext within the context to export this information&lt;br /&gt;
     * @param   int           $attid        The id of the submission to export.&lt;br /&gt;
     */&lt;br /&gt;
    public static function export_user_submissions(int $userid, \context $context, array $subcontext, int $attid) ;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are subplugins to another plugin====&lt;br /&gt;
&lt;br /&gt;
If you are developing a sub-plugin of another plugin, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subplugin type should define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface and it is up to the parent plugin to define how they will interact with their children.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/pluginname/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace assignsubmission\onlinetext;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does store personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is a subplugin of assign and must meet that contract.&lt;br /&gt;
    \mod_assign\privacy\assignsubmission_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are typically called by a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
There are a number of plugintypes in Moodle which are typically called by a specific Moodle subsystem.&lt;br /&gt;
&lt;br /&gt;
Some of these are &#039;&#039;only&#039;&#039; called by that subsystem, for example plugins which are of the &#039;&#039;plagiarism&#039;&#039; plugintype should never be called directly, but are always invoked via the &#039;&#039;core_plagiarism&#039;&#039; subsystem.&lt;br /&gt;
&lt;br /&gt;
Conversely, there maybe other plugintypes which can be called both via a subsystem, and in some other fashion. We are still determining whether any plugintypes currently fit this pattern.&lt;br /&gt;
&lt;br /&gt;
If you are developing a plugin which belongs to a specific subsystem, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subsystem will define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subsystem_provider&#039;&#039; interface and it is up to that subsystem to define how they will interact with those plugins.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;plagiarism/detectorator/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace plagiarism_detectorator\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does export personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is always linked against another activity module via the Plagiarism API.&lt;br /&gt;
    \core_plagiarism\privacy\plugin_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Exporting data====&lt;br /&gt;
&lt;br /&gt;
Any plugin which stores data must also export it.&lt;br /&gt;
&lt;br /&gt;
To cater for this the privacy API includes a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which defines a set of functions to store different types of data.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking data is broken into the following types:&lt;br /&gt;
&lt;br /&gt;
* Data - this is the object being described. For example the post content in a forum post;&lt;br /&gt;
* Related data - this is data related to the object being stored. For example, ratings of a forum post;&lt;br /&gt;
* Metadata - This is metadata about the main object. For example whether you are subscribed to a forum discussion;&lt;br /&gt;
* User preferences - this is data about a site-wide preference;&lt;br /&gt;
* Files - Any files that you are stored within Moodle on behalf of this plugin; and&lt;br /&gt;
* Custom files - For custom file formats - e.g. a calendar feed for calendar data. These should be used sparingly.&lt;br /&gt;
&lt;br /&gt;
Each piece of data is stored against a specific Moodle &#039;&#039;context&#039;&#039;, which will define how the data is structured within the exporter.&lt;br /&gt;
Data, and Related data only accept the &#039;&#039;stdClass&#039;&#039; object, whilst metadata should be stored as a set of key/value pairs which include a description.&lt;br /&gt;
&lt;br /&gt;
In some cases the data being stored belongs within an implicit structure. For example, one forum has many forum discussions, which each have a number of forum posts. This structure is represented by an &#039;&#039;array&#039;&#039; referred to as a &#039;&#039;subcontext&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;content_writer&#039;&#039; must &#039;&#039;always&#039;&#039; be called with a specific context, and can be called as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
writer::with_context($context)&lt;br /&gt;
    -&amp;gt;export_data($subcontext, $post)&lt;br /&gt;
    -&amp;gt;export_area_files($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id)&lt;br /&gt;
    -&amp;gt;export_metadata($subcontext, &#039;postread&#039;, (object) [&#039;firstread&#039; =&amp;gt; $firstread], new \lang_string(&#039;privacy:export:post:postread&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any text field which supports Moodle files must also be rewritten:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
$post-&amp;gt;message = writer::with_context($context)&lt;br /&gt;
    -&amp;gt;rewrite_pluginfile_urls($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id, $post-&amp;gt;message);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to delete user data===&lt;br /&gt;
&lt;br /&gt;
Deleting user data is also implemented in the request interface. There are two methods that need to be created. The first one to remove all user data from a context, the other to remove user data for a specific user in a list of contexts.&lt;br /&gt;
&lt;br /&gt;
====Delete for a context====&lt;br /&gt;
&lt;br /&gt;
A context is given and all user data (for all users) is to be deleted from the plugin. This will be called when the retention period for the context has expired to adhere to the privacy by design requirement. Retention periods are set in the Data registry.&lt;br /&gt;
&lt;br /&gt;
Note that this will be called for &#039;&#039;&#039;any&#039;&#039;&#039; context being expired, not only those the plugin holds data for (as returned by get_contexts_for_userid()), so you must carefully check the contexts given.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Delete all personal data for all users in the specified context.&lt;br /&gt;
 *&lt;br /&gt;
 * @param context $context Context to delete data from.&lt;br /&gt;
 */&lt;br /&gt;
public static function delete_data_for_all_users_in_context(\context $context) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;choice&#039;, $context-&amp;gt;instanceid);&lt;br /&gt;
    if (!$cm) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Delete personal information for a specific user and context(s)====&lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;approved_contextlist&#039;&#039; is given and user data related to that user should either be completely deleted, or overwritten if a structure needs to be maintained. This will be called when a user has requested the right to be forgotten. All attempts should be made to delete this data where practical while still allowing the plugin to be used by other users.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/choice/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function delete_data_for_user(approved_contextlist $contextlist) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
    &lt;br /&gt;
    if (empty($contextlist-&amp;gt;count())) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    $userid = $contextlist-&amp;gt;get_user()-&amp;gt;id;&lt;br /&gt;
    foreach ($contextlist-&amp;gt;get_contexts() as $context) {&lt;br /&gt;
        $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid, &#039;userid&#039; =&amp;gt; $userid]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Difference between Moodle 3.3 and more recent versions==&lt;br /&gt;
Moodle 3.3 has a minimum requirement of php 5.6 and so type hinting and return type declarations are not supported in this version. &lt;br /&gt;
Consequently the privacy API for this version does not have these features.&lt;br /&gt;
==Common Questions==&lt;br /&gt;
===What to do if you have one plugin that supports multiple branches===&lt;br /&gt;
This is something that we have considered and we have put in place a polyfill. This gets around the restrictions of one version having type hinting and return type declarations while another does not.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
To use the polyfill include the legacy polyfill trait and create the necessary static methods but with an underscore (shown below).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
    \core_privacy\local\request\plugin\provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===What to do if your plugin must implement a subplugin or subsystem plugin provider===&lt;br /&gt;
For subplugins (e.g. assignsubmission, assignfeedback, quiz report, quiz access rules), or subsystems which have a plugintype relationship (portfolio, plagiarism, and others), they will also define their own legacy polyfill.&lt;br /&gt;
&lt;br /&gt;
In this instance you will need to include the trait for both the core polyfill, and the provider polyfill as appropriate.&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin has data and must therefore define the metadata provider in order to describe it.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This is a plagiarism plugin. It interacts with the plagiarism subsystem rather than with core.&lt;br /&gt;
    \core_plagiarism\privacy\plagiarism_provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the metadata provider.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the plagirism provider.&lt;br /&gt;
    use \core_plagiarism\privacy\plagiarism_provider\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // This is one of the polyfilled methods from the plagiarism provider.&lt;br /&gt;
    public static function _export_plagiarism_user_data($userid, \context $context, array $subcontext, array $linkarray) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips for development ==&lt;br /&gt;
&lt;br /&gt;
* While implementing the privacy API into your plugin, there are CLI scripts that can help you to test things on the fly. Just don&#039;t forget these are not supposed to replace proper unit tests. See [[Privacy API/Utilities]] for details.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Subject Access Request FAQ]]&lt;br /&gt;
* [[:en:GDPR|GDPR]] in the user documentation&lt;br /&gt;
&lt;br /&gt;
[[Category:Privacy]]&lt;br /&gt;
[[Category:GDPR]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54134</id>
		<title>Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54134"/>
		<updated>2018-05-09T12:25:45Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Retrieving the list of contexts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The [https://en.wikipedia.org/wiki/General_Data_Protection_Regulation General Data Protection Regulation] (GDPR) is an EU directive that looks at providing users with more control over their data and how it is processed. This regulation will come into effect on 25th of May 2018 and covers any citizen or permanent resident of the European Union. The directive will be respected by a number of other countries outside of the European Union.&lt;br /&gt;
&lt;br /&gt;
To help institutions become compliant with this new regulation we are adding functionality to Moodle. This includes a number of components, amongst others these include a user’s right to:&lt;br /&gt;
&lt;br /&gt;
* request information on the types of personal data held, the instances of that data, and the deletion policy for each;&lt;br /&gt;
* access all of their data; and&lt;br /&gt;
* be forgotten.&lt;br /&gt;
&lt;br /&gt;
The compliance requirements also extend to installed plugins (including third party plugins). These need to also be able to report what information they store or process regarding users, and have the ability to provide and delete data for a user request.&lt;br /&gt;
&lt;br /&gt;
This document describes the proposed API changes required for plugins which will allow a Moodle installation to become GDPR compliant.&lt;br /&gt;
&lt;br /&gt;
Target Audience: The intended audience for this document is Moodle plugin developers, who are aiming to ensure their plugins are updated to comply with GDPR requirements coming into effect in the EU in May, 2018.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Personal data in Moodle==&lt;br /&gt;
&lt;br /&gt;
From the GDPR Spec, Article 4:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;‘personal data’ means any information relating to an identified or identifiable natural person (‘data subject’); an identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In Moodle, we need to consider two main types of personal data; information entered by the user and information stored about the user. The key difference being that information stored about the user will have come from a source other than the user themselves. Both types of data can be used to form a profile of the individual.&lt;br /&gt;
&lt;br /&gt;
The most obvious clue to finding personal data entered by the user is the presence of a userid on a database field. Any data on the record (or linked records) pertaining to that user may be deemed personal data for that user, including things like timestamps and record identification numbers. Additionally, any free text field which allows the user to enter information must also be considered to be the personal data of that user.&lt;br /&gt;
&lt;br /&gt;
Data stored about the user includes things like ratings and comments made on a student submission. These may have been made by an assessor or teacher, but are considered the personal data of the student, as they are considered a reflection of the user’s competency in the subject matter and can be used to form a profile of that individual. &lt;br /&gt;
&lt;br /&gt;
The sections that follow outline what you need to do as a plugin developer to ensure any personal data is advertised and can be accessed and deleted according to the GDPR requirements.&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
===Architecture overview===&lt;br /&gt;
&lt;br /&gt;
[[File:MoodlePrivacyMetadataUML.png|thumb|UML diagram of the metadata part of the privacy subsystem]]&lt;br /&gt;
[[File:MoodlePrivacyRequestUML.png|thumb|UML diagram of the request providers part of the privacy subsystem]]&lt;br /&gt;
&lt;br /&gt;
A new system for Privacy has been created within Moodle. This is broken down into several main parts and forms the &#039;&#039;core_privacy&#039;&#039; subsystem:&lt;br /&gt;
&lt;br /&gt;
* Some metadata providers - a set of PHP interfaces to be implemented by components for that component to describe the kind of data that it stores, and the purpose for its storage;&lt;br /&gt;
* Some request providers - a set of PHP interfaces to be implemented by components to allow that component to act upon user requests such as the Right to be Forgotten, and a Subject Access Request; and&lt;br /&gt;
* A manager - a concrete class used to bridge components which implement the providers with tools which request their data.&lt;br /&gt;
&lt;br /&gt;
All plugins will implement one metadata provider, and zero, one or two request providers.&lt;br /&gt;
&lt;br /&gt;
The fetching of data is broken into two separate steps:&lt;br /&gt;
&lt;br /&gt;
# Detecting in which Moodle contexts the user has any data; and&lt;br /&gt;
# Exporting all data from each of those contexts.&lt;br /&gt;
&lt;br /&gt;
This has been broken into two steps to later allow administrators to exclude certain contexts from an export - e.g. for courses currently in progress.&lt;br /&gt;
&lt;br /&gt;
A third component will later be added to facilitate the deletion of data within these contexts which will help to satisfy the Right to be Forgotten. This will also use the first step.&lt;br /&gt;
&lt;br /&gt;
Please refer to the inline phpdocs of the [https://github.com/moodle/moodle/blob/v3.5.0-beta/privacy/classes/manager.php#L31 core_privacy::manager class] for detailed description of the interfaces, their hierarchy and meaning.&lt;br /&gt;
&lt;br /&gt;
===Implementing a provider===&lt;br /&gt;
&lt;br /&gt;
All plugins will need to create a concrete class which implements the relevant metadata and request providers. The exact providers you need to implement will depend on what data you store, and the type of plugin. This is covered in more detail in the following sections of the document.&lt;br /&gt;
&lt;br /&gt;
In order to do so:&lt;br /&gt;
&lt;br /&gt;
# You must create a class called &#039;&#039;provider&#039;&#039; within the namespace &#039;&#039;\your_pluginname\privacy&#039;&#039;.&lt;br /&gt;
# This class must be created at &#039;&#039;path/to/your/plugin/classes/privacy/provider.php&#039;&#039;.&lt;br /&gt;
# You must have your class implement the relevant metadata and request interfaces.&lt;br /&gt;
&lt;br /&gt;
==Plugins which do not store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do not store any personal data. This is usually the case for plugins which just add functionality, or which display the data already stored elsewhere in Moodle.&lt;br /&gt;
&lt;br /&gt;
Some examples of plugin types which might fit this criteria include themes, blocks, filters, editor plugins, etc.&lt;br /&gt;
&lt;br /&gt;
Plugins which cause data to be stored elsewhere in Moodle (e.g. via a subsystem call) are considered to store data.&lt;br /&gt;
&lt;br /&gt;
One examples of a plugin which does not store any data would be the Calendar month block which just displays a view of the user’s calendar. It does not store any data itself.&lt;br /&gt;
&lt;br /&gt;
An example of a plugin which must not use the null provider is the Comments block. The comments block is responsible for data subsequently being stored within Moodle. Although the block doesn’t store anything itself, it interacts with the comments subsystem and is the only component which knows how that data maps to a user.&lt;br /&gt;
&lt;br /&gt;
===Implementation requirements===&lt;br /&gt;
&lt;br /&gt;
In order to let Moodle know that you have audited your plugin, and that you do not store any personal user data, you must implement the &#039;&#039;\core_privacy\local\metadata\null_provider&#039;&#039; interface in your plugin’s provider.&lt;br /&gt;
&lt;br /&gt;
These null providers can only be implemented where a plugin has:&lt;br /&gt;
&lt;br /&gt;
* no external links (e.g. sends data to an external service like an LTI provider, repository plugin which you can search on)&lt;br /&gt;
* no database tables which store user data (including IP addresses)&lt;br /&gt;
* no user preferences&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;null_provider&#039;&#039; requires you to define one function &#039;&#039;get_reason()&#039;&#039; which returns the language string identifier within your component.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 &amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace block_calendar_month\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
    // This plugin does not store any personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\null_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the language string identifier with the component&#039;s language&lt;br /&gt;
     * file to explain why this plugin stores no data.&lt;br /&gt;
     *&lt;br /&gt;
     * @return  string&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_reason() : string {&lt;br /&gt;
        return &#039;privacy:metadata&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/lang/en/block_calendar_month.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
$string[&#039;privacy:metadata&#039;] = &#039;The Calendar block only displays existing calendar data.&#039;;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That’s it. Congratulations, your plugin now implements the Privacy API.&lt;br /&gt;
&lt;br /&gt;
==Plugins which store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do store some form of personal data.&lt;br /&gt;
&lt;br /&gt;
In some cases this will be stored within database tables in your plugin, and in other cases this will be in one of Moodle’s core subsystems - for example your plugin may store files, ratings, comments, or tags.&lt;br /&gt;
&lt;br /&gt;
Plugins which do store data will need to:&lt;br /&gt;
&lt;br /&gt;
* Describe the type of data that they store; &lt;br /&gt;
* Provide a way to export that data; and&lt;br /&gt;
* Provide a way to delete that data.&lt;br /&gt;
&lt;br /&gt;
Data is described via a &#039;&#039;metadata&#039;&#039; provider, and it is both exported and deleted via an implementation of a &#039;&#039;request&#039;&#039; provider.&lt;br /&gt;
&lt;br /&gt;
These are both explained in the sections below.&lt;br /&gt;
&lt;br /&gt;
===Describing the type of data you store===&lt;br /&gt;
&lt;br /&gt;
In order to describe the type of data that you store, you must implement the &#039;&#039;\core_privacy\local\metadata\provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
This interfaces requires that you define one function: &#039;&#039;get_metadata&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There are several types of item to describe the data that you store. These are for:&lt;br /&gt;
&lt;br /&gt;
* Items in the Moodle database;&lt;br /&gt;
* Items stored by you in a Moodle subsystem - for example files, and ratings; and&lt;br /&gt;
* User preferences stored site-wide within Moodle for your plugin&lt;br /&gt;
&lt;br /&gt;
Note: All fields should include a description from a language string within your plugin.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_forum\privacy;&lt;br /&gt;
use core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
        // This plugin does store personal user data.&lt;br /&gt;
        \core_privacy\local\metadata\provider {&lt;br /&gt;
&lt;br /&gt;
    public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
        // Here you will add more items into the collection.&lt;br /&gt;
&lt;br /&gt;
        return $collection;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store content in a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
Many plugins will use one of the core Moodle subsystems to store data.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer we do not expect you to describe those subsystems in detail, but we do need to know that you use them and to know what you use them for.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_subsystem_link()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_subsystem_link(&lt;br /&gt;
        &#039;core_files&#039;,&lt;br /&gt;
        [],&lt;br /&gt;
        &#039;privacy:metadata:core_files&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:core_files&#039;] = &#039;The forum stores files which have been uploaded by the user to form part of a forum post.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Describing data stored in database tables====&lt;br /&gt;
&lt;br /&gt;
Most Moodle plugins will store some form of user data in their own database tables.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each database table, and each field which includes user data.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_database_table(&lt;br /&gt;
        &#039;forum_discussion_subs&#039;,&lt;br /&gt;
         [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:userid&#039;,&lt;br /&gt;
            &#039;discussionid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:discussionid&#039;,&lt;br /&gt;
            &#039;preference&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:preference&#039;,&lt;br /&gt;
&lt;br /&gt;
         ],&lt;br /&gt;
        &#039;privacy:metadata:forum_discussion_subs&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs&#039;] = &#039;Information about the subscriptions to individual forum discussions. This includes when a user has chosen to subscribe to a discussion, or to unsubscribe from one where they would otherwise be subscribed.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:userid&#039;] = &#039;The ID of the user with this subscription preference.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:discussionid&#039;] = &#039;The ID of the discussion that was subscribed to.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:preference&#039;] = &#039;The start time of the subscription.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store site-wide user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins will include one or more user preferences. Unfortunately this is one of Moodle’s older components and many of the values stored are not pure user preferences. Each plugin should be aware of how it handles its own preferences and is best placed to determine whether they are site-wide preferences, or per-instance preferences.&lt;br /&gt;
&lt;br /&gt;
Whilst most of these will have a fixed name (e.g. &#039;&#039;filepicker_recentrepository&#039;&#039;), some will include a variable of some kind (e.g. &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039;). Only the general name needs to be indicated rather than one copy for each preference.&lt;br /&gt;
&lt;br /&gt;
Also, these should only be &#039;&#039;site-wide&#039;&#039; user preferences which do not belong to a specific Moodle context.&lt;br /&gt;
&lt;br /&gt;
In the above examples:&lt;br /&gt;
&lt;br /&gt;
* Preference &#039;&#039;filepicker_recentrepository&#039;&#039; belongs to the file subsystem, and is a site-wide preference affecting the user anywhere that they view the filepicker.&lt;br /&gt;
* Preference &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039; belongs to user tours. User tours are a site-wide feature which can affect many parts of Moodle and cross multiple contexts.&lt;br /&gt;
&lt;br /&gt;
In some cases a value may be stored in the preferences table but is known to belong to a specific context within Moodle. In these cases they should be stored as metadata against that context rather than as a site-wide user preference.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_user_preference()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Any plugin providing user preferences must also implement the &#039;&#039;\core_privacy\local\request\preference_provider&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_user_preference(&#039;tool_usertours_tour_completion_time&#039;,&lt;br /&gt;
        &#039;privacy:metadata:preference:tool_usertours_tour_completion_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/lang/en/tool_usertours.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:tool_usertours_tour_completion_time&#039;] = &#039;The time that a specific user tour was last completed by a user.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you export data to an external location====&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with external systems - for example cloud-based services. Often this external location is configurable within the plugin either at the site or the instance level.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each &#039;&#039;type&#039;&#039; of target destination, alongside a list of each exported field which includes user data.&lt;br /&gt;
The &#039;&#039;actual&#039;&#039; destination does not need to be described as this can change based on configuration.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_external_location_link()&#039;&#039; method on the collection.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_external_location_link(&#039;lti_client&#039;, [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:lti_client:userid&#039;,&lt;br /&gt;
            &#039;fullname&#039; =&amp;gt; &#039;privacy:metadata:lti_client:fullname&#039;,&lt;br /&gt;
        ], &#039;privacy:metadata:lti_client&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/lang/en/lti.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client&#039;] = &#039;In order to integrate with a remote LTI service, user data needs to be exchanged with that service.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:userid&#039;] = &#039;The userid is sent from Moodle to allow you to access your data on the remote system.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:fullname&#039;] = &#039;Your full name is sent to the remote system to allow a better user experience.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to export user data===&lt;br /&gt;
&lt;br /&gt;
In order to export the user data that you store, you must implement the relevant request provider.&lt;br /&gt;
&lt;br /&gt;
We have named these request providers because they are called in response to a specific request from a user to access their information.&lt;br /&gt;
&lt;br /&gt;
There are several different types of request provider, and you may need to implement several of these, depending on the type and nature of your plugin.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking plugins will fit into one of the following categories:&lt;br /&gt;
&lt;br /&gt;
* Plugins which are a subplugin of another plugin. Examples include &#039;&#039;assignsubmission&#039;&#039;, &#039;&#039;atto&#039;&#039;, and &#039;&#039;datafield&#039;&#039;;&lt;br /&gt;
* Plugins which are typically called by a Moodle subsystem. Examples include &#039;&#039;qtype&#039;&#039;, and &#039;&#039;profilefield&#039;&#039;;&lt;br /&gt;
* All other plugins which store data.&lt;br /&gt;
&lt;br /&gt;
Most plugins will fit into this final category, whilst other plugins may fall into several categories.&lt;br /&gt;
Plugins which &#039;&#039;define&#039;&#039; a subplugin will also be responsible for  collecting this data from their subplugins.&lt;br /&gt;
&lt;br /&gt;
A final category exists - plugins which store user preferences. In some cases this may be the &#039;&#039;only&#039;&#039; provider implemented.&lt;br /&gt;
&lt;br /&gt;
====Standard plugins which store data====&lt;br /&gt;
&lt;br /&gt;
A majority of Moodle plugins will fit into this category and will be required to implement the &#039;&#039;\core_privacy\local\request\plugin\provider&#039;&#039; interface. This interface requires that you define four functions (the first two of which are dealt with in this section):&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;get_contexts_for_userid&#039;&#039; - to explain where data is held within Moodle for your plugin; and&lt;br /&gt;
* &#039;&#039;export_user_data&#039;&#039; - to export a user’s personal data from your plugin.&lt;br /&gt;
* &#039;&#039;delete_data_for_all_users_in_context&#039;&#039; - to delete all data for all users in the specified context.&lt;br /&gt;
* &#039;&#039;delete_data_for_user&#039;&#039; - to delete all user data for the specified user, in the specified contexts.&lt;br /&gt;
&lt;br /&gt;
These APIs make use of the Moodle &#039;&#039;context&#039;&#039; system to hierarchically store this data.&lt;br /&gt;
&lt;br /&gt;
=====Retrieving the list of contexts=====&lt;br /&gt;
&lt;br /&gt;
Contexts are retrieved using the &#039;&#039;get_contexts_for_userid&#039;&#039; function which takes the ID of the user being fetched, and returns a list of contexts in which the user has any data.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function returns a &#039;&#039;\core_privacy\local\request\contextlist&#039;&#039; which is used to keep a set of contexts together in a fixed fashion.&lt;br /&gt;
&lt;br /&gt;
Because a Subject Access Request covers &#039;&#039;every&#039;&#039; piece of data that is held for a user within Moodle, efficiency and performance is highly important. As a result, contexts are added to the &#039;&#039;contextlist&#039;&#039; by defining one or more SQL queries which return just the contextid. Multiple SQL queries can be added as required.&lt;br /&gt;
&lt;br /&gt;
To decide which type of context to return (CONTEXT_COURSE, CONTEXT_USER etc.) consider the most appropriate type for your plugin. Typically, this will be the same type of context as you would create to check capabilities. For example, an enrolment plugin will return the course contexts in which it is used. &lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with specific subsystems and store data within them.&lt;br /&gt;
These subsystems will also provide a way in which to link the data that you have stored with your own database tables.&lt;br /&gt;
At present these are still a work in progress and only the &#039;&#039;core_ratings&#039;&#039; subsystem includes this.&lt;br /&gt;
&lt;br /&gt;
======Basic example======&lt;br /&gt;
&lt;br /&gt;
The following example simply fetches the contextid for all forums where a user has a single discussion (note: this is an incomplete example):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
        $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
&lt;br /&gt;
        $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                 FROM {context} c&lt;br /&gt;
           INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
           INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
           INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
                WHERE (&lt;br /&gt;
                d.userid        = :discussionuserid&lt;br /&gt;
                )&lt;br /&gt;
        &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        $params = [&lt;br /&gt;
            &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
            &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
            &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
        return $contextlist;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
======More complete example======&lt;br /&gt;
&lt;br /&gt;
The following example includes a link to core_rating. &lt;br /&gt;
It will find any forum, forum discussion, or forum post where the user has any data, including:&lt;br /&gt;
&lt;br /&gt;
* Per-forum digest preferences;&lt;br /&gt;
* Per-forum subscription preferences;&lt;br /&gt;
* Per-forum read tracking preferences;&lt;br /&gt;
* Per-discussion subscription preferences;&lt;br /&gt;
* Per-post read data (if a user has read a post or not); and&lt;br /&gt;
* Per-post rating data.&lt;br /&gt;
&lt;br /&gt;
In the case of the rating data, this will include any post where the user has rated the post of another user.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int           $userid       The user to search.&lt;br /&gt;
 * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
 */&lt;br /&gt;
public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
    $ratingsql = \core_rating\privacy\provider::get_sql_join(&#039;rat&#039;, &#039;mod_forum&#039;, &#039;post&#039;, &#039;p.id&#039;, $userid);&lt;br /&gt;
    // Fetch all forum discussions, and forum posts.&lt;br /&gt;
    $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                FROM {context} c&lt;br /&gt;
        INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
        INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
        INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_posts} p ON p.discussion = d.id&lt;br /&gt;
            LEFT JOIN {forum_digests} dig ON dig.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_read} hasread ON hasread.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id&lt;br /&gt;
            {$ratingsql-&amp;gt;join}&lt;br /&gt;
                WHERE (&lt;br /&gt;
                p.userid        = :postuserid OR&lt;br /&gt;
                d.userid        = :discussionuserid OR&lt;br /&gt;
                dig.userid      = :digestuserid OR&lt;br /&gt;
                sub.userid      = :subuserid OR&lt;br /&gt;
                pref.userid     = :prefuserid OR&lt;br /&gt;
                hasread.userid  = :hasreaduserid OR&lt;br /&gt;
                dsub.userid     = :dsubuserid OR&lt;br /&gt;
                {$ratingsql-&amp;gt;userwhere}&lt;br /&gt;
            )&lt;br /&gt;
    &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    $params = [&lt;br /&gt;
        &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
        &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
        &#039;postuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        &#039;digestuserid&#039;      =&amp;gt; $userid,&lt;br /&gt;
        &#039;subuserid&#039;         =&amp;gt; $userid,&lt;br /&gt;
        &#039;prefuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;hasreaduserid&#039;     =&amp;gt; $userid,&lt;br /&gt;
        &#039;dsubuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
    ];&lt;br /&gt;
    $params += $ratingsql-&amp;gt;params;&lt;br /&gt;
&lt;br /&gt;
    $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
    $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
    return $contextlist;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Exporting user data=====&lt;br /&gt;
&lt;br /&gt;
After determining where in Moodle your plugin holds data about a user, the &#039;&#039;\core_privacy\manager&#039;&#039; will then ask your plugin to export all user data for a subset of those locations.&lt;br /&gt;
&lt;br /&gt;
This is achieved through use of the &#039;&#039;export_user_data&#039;&#039; function which takes the list of approved contexts in a &#039;&#039;\core_privacy\local\request\approved_contextlist&#039;&#039; object.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   approved_contextlist    $contextlist    The approved contexts to export information for.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_data(approved_contextlist $contextlist) {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;approved_contextlist&#039;&#039; includes both the user record, and a list of contexts, which can be retrieved by either processing it as an Iterator, or by calling &#039;&#039;get_contextids()&#039;&#039; or &#039;&#039;get_contexts()&#039;&#039; as required.&lt;br /&gt;
&lt;br /&gt;
Data is exported using a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which is described in further detail below.&lt;br /&gt;
&lt;br /&gt;
====Plugins which store user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins store a variety of user preferences, and must therefore export them.&lt;br /&gt;
&lt;br /&gt;
Since user preferences are a site-wide preference, these are exported separately to other user data.&lt;br /&gt;
In some cases the only data present is user preference data, whilst in others there is a combination of user-provided data, and user preferences.&lt;br /&gt;
&lt;br /&gt;
Storing of user preferences is achieved through implementation of the &#039;&#039;\core_privacy\local\request\user_preference_provider&#039;&#039; interface which defines one required function -- &#039;&#039;export_user_preferences&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user preferences for the plugin.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int         $userid The userid of the user whose data is to be exported.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_preferences(int $userid) {&lt;br /&gt;
    $markasreadonnotification = get_user_preference(&#039;markasreadonnotification&#039;, null, $userid);&lt;br /&gt;
    if (null !== $markasreadonnotification) {&lt;br /&gt;
        switch ($markasreadonnotification) {&lt;br /&gt;
            case 0:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationno&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
            case 1:&lt;br /&gt;
            default:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationyes&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
        writer::export_user_preference(&#039;mod_forum&#039;, &#039;markasreadonnotification&#039;, $markasreadonnotification, $markasreadonnotificationdescription);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Plugins which can have own subplugins ====&lt;br /&gt;
&lt;br /&gt;
Many plugin types are also able to define their own subplugins and will need to define a contract between themselves and their subplugins in order to fetch their data.&lt;br /&gt;
&lt;br /&gt;
This is required as the parent plugin and the child subplugin should be separate entities and the parent plugin must be able to function if one or more of its subplugins are uninstalled.&lt;br /&gt;
&lt;br /&gt;
The parent plugin is responsible for defining the contract,  and for interacting with its subplugins, though we intend to create helpers to make this easier.&lt;br /&gt;
&lt;br /&gt;
The parent plugin should define a new interface for each type of subplugin that it defines. This interface should extend the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
===== When a parent plugin should and should not provide the interface for its subplugins =====&lt;br /&gt;
&lt;br /&gt;
There can be cases when there is no point for a plugin to provide the &amp;quot;subplugin_provider&amp;quot; based interface, even if it has own subplugins. See the Atto or TinyMCE editors as real examples.&lt;br /&gt;
&lt;br /&gt;
If the parent plugin has no data passed through to the subplugins, there is no benefit in defining a subplugin provider. For example, Atto subplugins are just used to enhance the functionality and they never receive anything like a context. Most of the time we need to define a subplugin provider, but in cases where there is no data passed from the plugin to its subplugins, there is no need to define the subplugin provider. If the subplugins still do store personal data that are not related to the parent plugin in any way, then subplugins should define their own standard provider.&lt;br /&gt;
&lt;br /&gt;
Compare with something like mod_assign where the subplugins store data for the parent and that data is contextually relevant to the parent plugin. In those cases the subplugin stores data for the plugin and it only makes sense to do so in the context of its parent plugin.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
The following example defines the contract that assign submission subplugins may be required to implement.&lt;br /&gt;
&lt;br /&gt;
The assignment module is responsible for returning the contexts of all assignments where a user has data, but in some cases it is unaware of all of those cases - for example if a Teacher comments on a student submission it may not be aware of these as the information about this interaction may not be stored within its own tables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/assign/privacy/assignsubmission_provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_assign\privacy;&lt;br /&gt;
use \core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
interface assignsubmission_provider extends&lt;br /&gt;
    // This Interface defines a subplugin.&lt;br /&gt;
    \core_privacy\local\request\plugin\subplugin_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the SQL required to find all submission items where this user has had any involvements. &lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  \stdClass                   Object containing the join, params, and where used to select a these records from the database.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_items_with_user_interaction(int $userid) : \stdClass ;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Export all relevant user submissions information which match the combination of userid and attemptid.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @param   \context      $context      The context to export this submission against.&lt;br /&gt;
     * @param   array         $subcontext   The subcontext within the context to export this information&lt;br /&gt;
     * @param   int           $attid        The id of the submission to export.&lt;br /&gt;
     */&lt;br /&gt;
    public static function export_user_submissions(int $userid, \context $context, array $subcontext, int $attid) ;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are subplugins to another plugin====&lt;br /&gt;
&lt;br /&gt;
If you are developing a sub-plugin of another plugin, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subplugin type should define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface and it is up to the parent plugin to define how they will interact with their children.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/pluginname/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace assignsubmission\onlinetext;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does store personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is a subplugin of assign and must meet that contract.&lt;br /&gt;
    \mod_assign\privacy\assignsubmission_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are typically called by a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
There are a number of plugintypes in Moodle which are typically called by a specific Moodle subsystem.&lt;br /&gt;
&lt;br /&gt;
Some of these are &#039;&#039;only&#039;&#039; called by that subsystem, for example plugins which are of the &#039;&#039;plagiarism&#039;&#039; plugintype should never be called directly, but are always invoked via the &#039;&#039;core_plagiarism&#039;&#039; subsystem.&lt;br /&gt;
&lt;br /&gt;
Conversely, there maybe other plugintypes which can be called both via a subsystem, and in some other fashion. We are still determining whether any plugintypes currently fit this pattern.&lt;br /&gt;
&lt;br /&gt;
If you are developing a plugin which belongs to a specific subsystem, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subsystem will define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subsystem_provider&#039;&#039; interface and it is up to that subsystem to define how they will interact with those plugins.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;plagiarism/detectorator/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace plagiarism_detectorator\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does export personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is always linked against another activity module via the Plagiarism API.&lt;br /&gt;
    \core_plagiarism\privacy\plugin_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Exporting data====&lt;br /&gt;
&lt;br /&gt;
Any plugin which stores data must also export it.&lt;br /&gt;
&lt;br /&gt;
To cater for this the privacy API includes a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which defines a set of functions to store different types of data.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking data is broken into the following types:&lt;br /&gt;
&lt;br /&gt;
* Data - this is the object being described. For example the post content in a forum post;&lt;br /&gt;
* Related data - this is data related to the object being stored. For example, ratings of a forum post;&lt;br /&gt;
* Metadata - This is metadata about the main object. For example whether you are subscribed to a forum discussion;&lt;br /&gt;
* User preferences - this is data about a site-wide preference;&lt;br /&gt;
* Files - Any files that you are stored within Moodle on behalf of this plugin; and&lt;br /&gt;
* Custom files - For custom file formats - e.g. a calendar feed for calendar data. These should be used sparingly.&lt;br /&gt;
&lt;br /&gt;
Each piece of data is stored against a specific Moodle &#039;&#039;context&#039;&#039;, which will define how the data is structured within the exporter.&lt;br /&gt;
Data, and Related data only accept the &#039;&#039;stdClass&#039;&#039; object, whilst metadata should be stored as a set of key/value pairs which include a description.&lt;br /&gt;
&lt;br /&gt;
In some cases the data being stored belongs within an implicit structure. For example, one forum has many forum discussions, which each have a number of forum posts. This structure is represented by an &#039;&#039;array&#039;&#039; referred to as a &#039;&#039;subcontext&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;content_writer&#039;&#039; must &#039;&#039;always&#039;&#039; be called with a specific context, and can be called as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
writer::with_context($context)&lt;br /&gt;
    -&amp;gt;export_data($subcontext, $post)&lt;br /&gt;
    -&amp;gt;export_area_files($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id)&lt;br /&gt;
    -&amp;gt;export_metadata($subcontext, &#039;postread&#039;, (object) [&#039;firstread&#039; =&amp;gt; $firstread], new \lang_string(&#039;privacy:export:post:postread&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any text field which supports Moodle files must also be rewritten:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
$post-&amp;gt;message = writer::with_context($context)&lt;br /&gt;
    -&amp;gt;rewrite_pluginfile_urls($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id, $post-&amp;gt;message);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to delete user data===&lt;br /&gt;
&lt;br /&gt;
Deleting user data is also implemented in the request interface. There are two methods that need to be created. The first one to remove all user data from a context, the other to remove user data for a specific user in a list of contexts.&lt;br /&gt;
&lt;br /&gt;
====Delete for a context====&lt;br /&gt;
&lt;br /&gt;
A context is given and all user data (for all users) is to be deleted from the plugin. This will be called when the retention period for the context has expired to adhere to the privacy by design requirement. Retention periods are set in the Data registry.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Delete all personal data for all users in the specified context.&lt;br /&gt;
 *&lt;br /&gt;
 * @param context $context Context to delete data from.&lt;br /&gt;
 */&lt;br /&gt;
public static function delete_data_for_all_users_in_context(\context $context) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
     if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
    $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Delete personal information for a specific user and context(s)====&lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;approved_contextlist&#039;&#039; is given and user data related to that user should either be completely deleted, or overwritten if a structure needs to be maintained. This will be called when a user has requested the right to be forgotten. All attempts should be made to delete this data where practical while still allowing the plugin to be used by other users.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/choice/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function delete_data_for_user(approved_contextlist $contextlist) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
    &lt;br /&gt;
    if (empty($contextlist-&amp;gt;count())) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    $userid = $contextlist-&amp;gt;get_user()-&amp;gt;id;&lt;br /&gt;
    foreach ($contextlist-&amp;gt;get_contexts() as $context) {&lt;br /&gt;
        $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid, &#039;userid&#039; =&amp;gt; $userid]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Difference between Moodle 3.3 and more recent versions==&lt;br /&gt;
Moodle 3.3 has a minimum requirement of php 5.6 and so type hinting and return type declarations are not supported in this version. &lt;br /&gt;
Consequently the privacy API for this version does not have these features.&lt;br /&gt;
==Common Questions==&lt;br /&gt;
===What to do if you have one plugin that supports multiple branches===&lt;br /&gt;
This is something that we have considered and we have put in place a polyfill. This gets around the restrictions of one version having type hinting and return type declarations while another does not.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
To use the polyfill include the legacy polyfill trait and create the necessary static methods but with an underscore (shown below).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
    \core_privacy\local\request\plugin\provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===What to do if your must implement a subplugin or subsystem plugin provider===&lt;br /&gt;
For subplugins (e.g. assignsubmission, assignfeedback, quiz report, quiz access rules), or subsystems which have a plugintype relationship (portfolio, plagiarism, and others), they will also define their own legacy polyfill.&lt;br /&gt;
&lt;br /&gt;
In this instance you will need to include the trait for both the core polyfill, and the provider polyfill as appropriate.&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin has data and must therefore define the metadata provider in order to describe it.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This is a plagiarism plugin. It interacts with the plagiarism subsystem rather than with core.&lt;br /&gt;
    \core_plagiarism\privacy\plagiarism_provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the metadata provider.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the plagirism provider.&lt;br /&gt;
    use \core_plagiarism\privacy\plagiarism_provider\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // This is one of the polyfilled methods from the plagiarism provider.&lt;br /&gt;
    public static function _export_plagiarism_user_data($userid, \context $context, array $subcontext, array $linkarray) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips for development ==&lt;br /&gt;
&lt;br /&gt;
* While implementing the privacy API into your plugin, there are CLI scripts that can help you to test things on the fly. Just don&#039;t forget these are not supposed to replace proper unit tests. See [[Privacy API/Utilities]] for details.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Subject Access Request FAQ]]&lt;br /&gt;
* [[:en:GDPR|GDPR]] in the user documentation&lt;br /&gt;
&lt;br /&gt;
[[Category:Privacy]]&lt;br /&gt;
[[Category:GDPR]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54133</id>
		<title>Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54133"/>
		<updated>2018-05-09T12:18:51Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Standard plugins which store data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The [https://en.wikipedia.org/wiki/General_Data_Protection_Regulation General Data Protection Regulation] (GDPR) is an EU directive that looks at providing users with more control over their data and how it is processed. This regulation will come into effect on 25th of May 2018 and covers any citizen or permanent resident of the European Union. The directive will be respected by a number of other countries outside of the European Union.&lt;br /&gt;
&lt;br /&gt;
To help institutions become compliant with this new regulation we are adding functionality to Moodle. This includes a number of components, amongst others these include a user’s right to:&lt;br /&gt;
&lt;br /&gt;
* request information on the types of personal data held, the instances of that data, and the deletion policy for each;&lt;br /&gt;
* access all of their data; and&lt;br /&gt;
* be forgotten.&lt;br /&gt;
&lt;br /&gt;
The compliance requirements also extend to installed plugins (including third party plugins). These need to also be able to report what information they store or process regarding users, and have the ability to provide and delete data for a user request.&lt;br /&gt;
&lt;br /&gt;
This document describes the proposed API changes required for plugins which will allow a Moodle installation to become GDPR compliant.&lt;br /&gt;
&lt;br /&gt;
Target Audience: The intended audience for this document is Moodle plugin developers, who are aiming to ensure their plugins are updated to comply with GDPR requirements coming into effect in the EU in May, 2018.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Personal data in Moodle==&lt;br /&gt;
&lt;br /&gt;
From the GDPR Spec, Article 4:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;‘personal data’ means any information relating to an identified or identifiable natural person (‘data subject’); an identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In Moodle, we need to consider two main types of personal data; information entered by the user and information stored about the user. The key difference being that information stored about the user will have come from a source other than the user themselves. Both types of data can be used to form a profile of the individual.&lt;br /&gt;
&lt;br /&gt;
The most obvious clue to finding personal data entered by the user is the presence of a userid on a database field. Any data on the record (or linked records) pertaining to that user may be deemed personal data for that user, including things like timestamps and record identification numbers. Additionally, any free text field which allows the user to enter information must also be considered to be the personal data of that user.&lt;br /&gt;
&lt;br /&gt;
Data stored about the user includes things like ratings and comments made on a student submission. These may have been made by an assessor or teacher, but are considered the personal data of the student, as they are considered a reflection of the user’s competency in the subject matter and can be used to form a profile of that individual. &lt;br /&gt;
&lt;br /&gt;
The sections that follow outline what you need to do as a plugin developer to ensure any personal data is advertised and can be accessed and deleted according to the GDPR requirements.&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
===Architecture overview===&lt;br /&gt;
&lt;br /&gt;
[[File:MoodlePrivacyMetadataUML.png|thumb|UML diagram of the metadata part of the privacy subsystem]]&lt;br /&gt;
[[File:MoodlePrivacyRequestUML.png|thumb|UML diagram of the request providers part of the privacy subsystem]]&lt;br /&gt;
&lt;br /&gt;
A new system for Privacy has been created within Moodle. This is broken down into several main parts and forms the &#039;&#039;core_privacy&#039;&#039; subsystem:&lt;br /&gt;
&lt;br /&gt;
* Some metadata providers - a set of PHP interfaces to be implemented by components for that component to describe the kind of data that it stores, and the purpose for its storage;&lt;br /&gt;
* Some request providers - a set of PHP interfaces to be implemented by components to allow that component to act upon user requests such as the Right to be Forgotten, and a Subject Access Request; and&lt;br /&gt;
* A manager - a concrete class used to bridge components which implement the providers with tools which request their data.&lt;br /&gt;
&lt;br /&gt;
All plugins will implement one metadata provider, and zero, one or two request providers.&lt;br /&gt;
&lt;br /&gt;
The fetching of data is broken into two separate steps:&lt;br /&gt;
&lt;br /&gt;
# Detecting in which Moodle contexts the user has any data; and&lt;br /&gt;
# Exporting all data from each of those contexts.&lt;br /&gt;
&lt;br /&gt;
This has been broken into two steps to later allow administrators to exclude certain contexts from an export - e.g. for courses currently in progress.&lt;br /&gt;
&lt;br /&gt;
A third component will later be added to facilitate the deletion of data within these contexts which will help to satisfy the Right to be Forgotten. This will also use the first step.&lt;br /&gt;
&lt;br /&gt;
Please refer to the inline phpdocs of the [https://github.com/moodle/moodle/blob/v3.5.0-beta/privacy/classes/manager.php#L31 core_privacy::manager class] for detailed description of the interfaces, their hierarchy and meaning.&lt;br /&gt;
&lt;br /&gt;
===Implementing a provider===&lt;br /&gt;
&lt;br /&gt;
All plugins will need to create a concrete class which implements the relevant metadata and request providers. The exact providers you need to implement will depend on what data you store, and the type of plugin. This is covered in more detail in the following sections of the document.&lt;br /&gt;
&lt;br /&gt;
In order to do so:&lt;br /&gt;
&lt;br /&gt;
# You must create a class called &#039;&#039;provider&#039;&#039; within the namespace &#039;&#039;\your_pluginname\privacy&#039;&#039;.&lt;br /&gt;
# This class must be created at &#039;&#039;path/to/your/plugin/classes/privacy/provider.php&#039;&#039;.&lt;br /&gt;
# You must have your class implement the relevant metadata and request interfaces.&lt;br /&gt;
&lt;br /&gt;
==Plugins which do not store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do not store any personal data. This is usually the case for plugins which just add functionality, or which display the data already stored elsewhere in Moodle.&lt;br /&gt;
&lt;br /&gt;
Some examples of plugin types which might fit this criteria include themes, blocks, filters, editor plugins, etc.&lt;br /&gt;
&lt;br /&gt;
Plugins which cause data to be stored elsewhere in Moodle (e.g. via a subsystem call) are considered to store data.&lt;br /&gt;
&lt;br /&gt;
One examples of a plugin which does not store any data would be the Calendar month block which just displays a view of the user’s calendar. It does not store any data itself.&lt;br /&gt;
&lt;br /&gt;
An example of a plugin which must not use the null provider is the Comments block. The comments block is responsible for data subsequently being stored within Moodle. Although the block doesn’t store anything itself, it interacts with the comments subsystem and is the only component which knows how that data maps to a user.&lt;br /&gt;
&lt;br /&gt;
===Implementation requirements===&lt;br /&gt;
&lt;br /&gt;
In order to let Moodle know that you have audited your plugin, and that you do not store any personal user data, you must implement the &#039;&#039;\core_privacy\local\metadata\null_provider&#039;&#039; interface in your plugin’s provider.&lt;br /&gt;
&lt;br /&gt;
These null providers can only be implemented where a plugin has:&lt;br /&gt;
&lt;br /&gt;
* no external links (e.g. sends data to an external service like an LTI provider, repository plugin which you can search on)&lt;br /&gt;
* no database tables which store user data (including IP addresses)&lt;br /&gt;
* no user preferences&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;null_provider&#039;&#039; requires you to define one function &#039;&#039;get_reason()&#039;&#039; which returns the language string identifier within your component.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 &amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace block_calendar_month\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
    // This plugin does not store any personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\null_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the language string identifier with the component&#039;s language&lt;br /&gt;
     * file to explain why this plugin stores no data.&lt;br /&gt;
     *&lt;br /&gt;
     * @return  string&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_reason() : string {&lt;br /&gt;
        return &#039;privacy:metadata&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/lang/en/block_calendar_month.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
$string[&#039;privacy:metadata&#039;] = &#039;The Calendar block only displays existing calendar data.&#039;;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That’s it. Congratulations, your plugin now implements the Privacy API.&lt;br /&gt;
&lt;br /&gt;
==Plugins which store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do store some form of personal data.&lt;br /&gt;
&lt;br /&gt;
In some cases this will be stored within database tables in your plugin, and in other cases this will be in one of Moodle’s core subsystems - for example your plugin may store files, ratings, comments, or tags.&lt;br /&gt;
&lt;br /&gt;
Plugins which do store data will need to:&lt;br /&gt;
&lt;br /&gt;
* Describe the type of data that they store; &lt;br /&gt;
* Provide a way to export that data; and&lt;br /&gt;
* Provide a way to delete that data.&lt;br /&gt;
&lt;br /&gt;
Data is described via a &#039;&#039;metadata&#039;&#039; provider, and it is both exported and deleted via an implementation of a &#039;&#039;request&#039;&#039; provider.&lt;br /&gt;
&lt;br /&gt;
These are both explained in the sections below.&lt;br /&gt;
&lt;br /&gt;
===Describing the type of data you store===&lt;br /&gt;
&lt;br /&gt;
In order to describe the type of data that you store, you must implement the &#039;&#039;\core_privacy\local\metadata\provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
This interfaces requires that you define one function: &#039;&#039;get_metadata&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There are several types of item to describe the data that you store. These are for:&lt;br /&gt;
&lt;br /&gt;
* Items in the Moodle database;&lt;br /&gt;
* Items stored by you in a Moodle subsystem - for example files, and ratings; and&lt;br /&gt;
* User preferences stored site-wide within Moodle for your plugin&lt;br /&gt;
&lt;br /&gt;
Note: All fields should include a description from a language string within your plugin.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_forum\privacy;&lt;br /&gt;
use core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
        // This plugin does store personal user data.&lt;br /&gt;
        \core_privacy\local\metadata\provider {&lt;br /&gt;
&lt;br /&gt;
    public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
        // Here you will add more items into the collection.&lt;br /&gt;
&lt;br /&gt;
        return $collection;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store content in a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
Many plugins will use one of the core Moodle subsystems to store data.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer we do not expect you to describe those subsystems in detail, but we do need to know that you use them and to know what you use them for.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_subsystem_link()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_subsystem_link(&lt;br /&gt;
        &#039;core_files&#039;,&lt;br /&gt;
        [],&lt;br /&gt;
        &#039;privacy:metadata:core_files&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:core_files&#039;] = &#039;The forum stores files which have been uploaded by the user to form part of a forum post.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Describing data stored in database tables====&lt;br /&gt;
&lt;br /&gt;
Most Moodle plugins will store some form of user data in their own database tables.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each database table, and each field which includes user data.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_database_table(&lt;br /&gt;
        &#039;forum_discussion_subs&#039;,&lt;br /&gt;
         [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:userid&#039;,&lt;br /&gt;
            &#039;discussionid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:discussionid&#039;,&lt;br /&gt;
            &#039;preference&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:preference&#039;,&lt;br /&gt;
&lt;br /&gt;
         ],&lt;br /&gt;
        &#039;privacy:metadata:forum_discussion_subs&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs&#039;] = &#039;Information about the subscriptions to individual forum discussions. This includes when a user has chosen to subscribe to a discussion, or to unsubscribe from one where they would otherwise be subscribed.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:userid&#039;] = &#039;The ID of the user with this subscription preference.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:discussionid&#039;] = &#039;The ID of the discussion that was subscribed to.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:preference&#039;] = &#039;The start time of the subscription.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store site-wide user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins will include one or more user preferences. Unfortunately this is one of Moodle’s older components and many of the values stored are not pure user preferences. Each plugin should be aware of how it handles its own preferences and is best placed to determine whether they are site-wide preferences, or per-instance preferences.&lt;br /&gt;
&lt;br /&gt;
Whilst most of these will have a fixed name (e.g. &#039;&#039;filepicker_recentrepository&#039;&#039;), some will include a variable of some kind (e.g. &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039;). Only the general name needs to be indicated rather than one copy for each preference.&lt;br /&gt;
&lt;br /&gt;
Also, these should only be &#039;&#039;site-wide&#039;&#039; user preferences which do not belong to a specific Moodle context.&lt;br /&gt;
&lt;br /&gt;
In the above examples:&lt;br /&gt;
&lt;br /&gt;
* Preference &#039;&#039;filepicker_recentrepository&#039;&#039; belongs to the file subsystem, and is a site-wide preference affecting the user anywhere that they view the filepicker.&lt;br /&gt;
* Preference &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039; belongs to user tours. User tours are a site-wide feature which can affect many parts of Moodle and cross multiple contexts.&lt;br /&gt;
&lt;br /&gt;
In some cases a value may be stored in the preferences table but is known to belong to a specific context within Moodle. In these cases they should be stored as metadata against that context rather than as a site-wide user preference.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_user_preference()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Any plugin providing user preferences must also implement the &#039;&#039;\core_privacy\local\request\preference_provider&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_user_preference(&#039;tool_usertours_tour_completion_time&#039;,&lt;br /&gt;
        &#039;privacy:metadata:preference:tool_usertours_tour_completion_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/lang/en/tool_usertours.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:tool_usertours_tour_completion_time&#039;] = &#039;The time that a specific user tour was last completed by a user.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you export data to an external location====&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with external systems - for example cloud-based services. Often this external location is configurable within the plugin either at the site or the instance level.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each &#039;&#039;type&#039;&#039; of target destination, alongside a list of each exported field which includes user data.&lt;br /&gt;
The &#039;&#039;actual&#039;&#039; destination does not need to be described as this can change based on configuration.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_external_location_link()&#039;&#039; method on the collection.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_external_location_link(&#039;lti_client&#039;, [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:lti_client:userid&#039;,&lt;br /&gt;
            &#039;fullname&#039; =&amp;gt; &#039;privacy:metadata:lti_client:fullname&#039;,&lt;br /&gt;
        ], &#039;privacy:metadata:lti_client&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/lang/en/lti.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client&#039;] = &#039;In order to integrate with a remote LTI service, user data needs to be exchanged with that service.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:userid&#039;] = &#039;The userid is sent from Moodle to allow you to access your data on the remote system.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:fullname&#039;] = &#039;Your full name is sent to the remote system to allow a better user experience.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to export user data===&lt;br /&gt;
&lt;br /&gt;
In order to export the user data that you store, you must implement the relevant request provider.&lt;br /&gt;
&lt;br /&gt;
We have named these request providers because they are called in response to a specific request from a user to access their information.&lt;br /&gt;
&lt;br /&gt;
There are several different types of request provider, and you may need to implement several of these, depending on the type and nature of your plugin.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking plugins will fit into one of the following categories:&lt;br /&gt;
&lt;br /&gt;
* Plugins which are a subplugin of another plugin. Examples include &#039;&#039;assignsubmission&#039;&#039;, &#039;&#039;atto&#039;&#039;, and &#039;&#039;datafield&#039;&#039;;&lt;br /&gt;
* Plugins which are typically called by a Moodle subsystem. Examples include &#039;&#039;qtype&#039;&#039;, and &#039;&#039;profilefield&#039;&#039;;&lt;br /&gt;
* All other plugins which store data.&lt;br /&gt;
&lt;br /&gt;
Most plugins will fit into this final category, whilst other plugins may fall into several categories.&lt;br /&gt;
Plugins which &#039;&#039;define&#039;&#039; a subplugin will also be responsible for  collecting this data from their subplugins.&lt;br /&gt;
&lt;br /&gt;
A final category exists - plugins which store user preferences. In some cases this may be the &#039;&#039;only&#039;&#039; provider implemented.&lt;br /&gt;
&lt;br /&gt;
====Standard plugins which store data====&lt;br /&gt;
&lt;br /&gt;
A majority of Moodle plugins will fit into this category and will be required to implement the &#039;&#039;\core_privacy\local\request\plugin\provider&#039;&#039; interface. This interface requires that you define four functions (the first two of which are dealt with in this section):&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;get_contexts_for_userid&#039;&#039; - to explain where data is held within Moodle for your plugin; and&lt;br /&gt;
* &#039;&#039;export_user_data&#039;&#039; - to export a user’s personal data from your plugin.&lt;br /&gt;
* &#039;&#039;delete_data_for_all_users_in_context&#039;&#039; - to delete all data for all users in the specified context.&lt;br /&gt;
* &#039;&#039;delete_data_for_user&#039;&#039; - to delete all user data for the specified user, in the specified contexts.&lt;br /&gt;
&lt;br /&gt;
These APIs make use of the Moodle &#039;&#039;context&#039;&#039; system to hierarchically store this data.&lt;br /&gt;
&lt;br /&gt;
=====Retrieving the list of contexts=====&lt;br /&gt;
&lt;br /&gt;
Contexts are retrieved using the &#039;&#039;get_contexts_for_userid&#039;&#039; function which takes the ID of the user being fetched, and returns a list of contexts in which the user has any data.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function returns a &#039;&#039;\core_privacy\local\request\contextlist&#039;&#039; which is used to keep a set of contexts together in a fixed fashion.&lt;br /&gt;
&lt;br /&gt;
Because a Subject Access Request covers &#039;&#039;every&#039;&#039; piece of data that is held for a user within Moodle, efficiency and performance is highly important. As a result, contexts are added to the &#039;&#039;contextlist&#039;&#039; by defining one or more SQL queries which return just the contextid. Multiple SQL queries can be added as required.&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with specific subsystems and store data within them.&lt;br /&gt;
These subsystems will also provide a way in which to link the data that you have stored with your own database tables.&lt;br /&gt;
At present these are still a work in progress and only the &#039;&#039;core_ratings&#039;&#039; subsystem includes this.&lt;br /&gt;
&lt;br /&gt;
======Basic example======&lt;br /&gt;
&lt;br /&gt;
The following example simply fetches the contextid for all forums where a user has a single discussion (note: this is an incomplete example):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
        $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
&lt;br /&gt;
        $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                 FROM {context} c&lt;br /&gt;
           INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
           INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
           INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
                WHERE (&lt;br /&gt;
                d.userid        = :discussionuserid&lt;br /&gt;
                )&lt;br /&gt;
        &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        $params = [&lt;br /&gt;
            &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
            &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
            &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
        return $contextlist;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
======More complete example======&lt;br /&gt;
&lt;br /&gt;
The following example includes a link to core_rating. &lt;br /&gt;
It will find any forum, forum discussion, or forum post where the user has any data, including:&lt;br /&gt;
&lt;br /&gt;
* Per-forum digest preferences;&lt;br /&gt;
* Per-forum subscription preferences;&lt;br /&gt;
* Per-forum read tracking preferences;&lt;br /&gt;
* Per-discussion subscription preferences;&lt;br /&gt;
* Per-post read data (if a user has read a post or not); and&lt;br /&gt;
* Per-post rating data.&lt;br /&gt;
&lt;br /&gt;
In the case of the rating data, this will include any post where the user has rated the post of another user.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int           $userid       The user to search.&lt;br /&gt;
 * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
 */&lt;br /&gt;
public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
    $ratingsql = \core_rating\privacy\provider::get_sql_join(&#039;rat&#039;, &#039;mod_forum&#039;, &#039;post&#039;, &#039;p.id&#039;, $userid);&lt;br /&gt;
    // Fetch all forum discussions, and forum posts.&lt;br /&gt;
    $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                FROM {context} c&lt;br /&gt;
        INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
        INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
        INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_posts} p ON p.discussion = d.id&lt;br /&gt;
            LEFT JOIN {forum_digests} dig ON dig.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_read} hasread ON hasread.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id&lt;br /&gt;
            {$ratingsql-&amp;gt;join}&lt;br /&gt;
                WHERE (&lt;br /&gt;
                p.userid        = :postuserid OR&lt;br /&gt;
                d.userid        = :discussionuserid OR&lt;br /&gt;
                dig.userid      = :digestuserid OR&lt;br /&gt;
                sub.userid      = :subuserid OR&lt;br /&gt;
                pref.userid     = :prefuserid OR&lt;br /&gt;
                hasread.userid  = :hasreaduserid OR&lt;br /&gt;
                dsub.userid     = :dsubuserid OR&lt;br /&gt;
                {$ratingsql-&amp;gt;userwhere}&lt;br /&gt;
            )&lt;br /&gt;
    &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    $params = [&lt;br /&gt;
        &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
        &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
        &#039;postuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        &#039;digestuserid&#039;      =&amp;gt; $userid,&lt;br /&gt;
        &#039;subuserid&#039;         =&amp;gt; $userid,&lt;br /&gt;
        &#039;prefuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;hasreaduserid&#039;     =&amp;gt; $userid,&lt;br /&gt;
        &#039;dsubuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
    ];&lt;br /&gt;
    $params += $ratingsql-&amp;gt;params;&lt;br /&gt;
&lt;br /&gt;
    $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
    $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
    return $contextlist;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Exporting user data=====&lt;br /&gt;
&lt;br /&gt;
After determining where in Moodle your plugin holds data about a user, the &#039;&#039;\core_privacy\manager&#039;&#039; will then ask your plugin to export all user data for a subset of those locations.&lt;br /&gt;
&lt;br /&gt;
This is achieved through use of the &#039;&#039;export_user_data&#039;&#039; function which takes the list of approved contexts in a &#039;&#039;\core_privacy\local\request\approved_contextlist&#039;&#039; object.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   approved_contextlist    $contextlist    The approved contexts to export information for.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_data(approved_contextlist $contextlist) {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;approved_contextlist&#039;&#039; includes both the user record, and a list of contexts, which can be retrieved by either processing it as an Iterator, or by calling &#039;&#039;get_contextids()&#039;&#039; or &#039;&#039;get_contexts()&#039;&#039; as required.&lt;br /&gt;
&lt;br /&gt;
Data is exported using a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which is described in further detail below.&lt;br /&gt;
&lt;br /&gt;
====Plugins which store user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins store a variety of user preferences, and must therefore export them.&lt;br /&gt;
&lt;br /&gt;
Since user preferences are a site-wide preference, these are exported separately to other user data.&lt;br /&gt;
In some cases the only data present is user preference data, whilst in others there is a combination of user-provided data, and user preferences.&lt;br /&gt;
&lt;br /&gt;
Storing of user preferences is achieved through implementation of the &#039;&#039;\core_privacy\local\request\user_preference_provider&#039;&#039; interface which defines one required function -- &#039;&#039;export_user_preferences&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user preferences for the plugin.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int         $userid The userid of the user whose data is to be exported.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_preferences(int $userid) {&lt;br /&gt;
    $markasreadonnotification = get_user_preference(&#039;markasreadonnotification&#039;, null, $userid);&lt;br /&gt;
    if (null !== $markasreadonnotification) {&lt;br /&gt;
        switch ($markasreadonnotification) {&lt;br /&gt;
            case 0:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationno&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
            case 1:&lt;br /&gt;
            default:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationyes&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
        writer::export_user_preference(&#039;mod_forum&#039;, &#039;markasreadonnotification&#039;, $markasreadonnotification, $markasreadonnotificationdescription);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Plugins which can have own subplugins ====&lt;br /&gt;
&lt;br /&gt;
Many plugin types are also able to define their own subplugins and will need to define a contract between themselves and their subplugins in order to fetch their data.&lt;br /&gt;
&lt;br /&gt;
This is required as the parent plugin and the child subplugin should be separate entities and the parent plugin must be able to function if one or more of its subplugins are uninstalled.&lt;br /&gt;
&lt;br /&gt;
The parent plugin is responsible for defining the contract,  and for interacting with its subplugins, though we intend to create helpers to make this easier.&lt;br /&gt;
&lt;br /&gt;
The parent plugin should define a new interface for each type of subplugin that it defines. This interface should extend the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
===== When a parent plugin should and should not provide the interface for its subplugins =====&lt;br /&gt;
&lt;br /&gt;
There can be cases when there is no point for a plugin to provide the &amp;quot;subplugin_provider&amp;quot; based interface, even if it has own subplugins. See the Atto or TinyMCE editors as real examples.&lt;br /&gt;
&lt;br /&gt;
If the parent plugin has no data passed through to the subplugins, there is no benefit in defining a subplugin provider. For example, Atto subplugins are just used to enhance the functionality and they never receive anything like a context. Most of the time we need to define a subplugin provider, but in cases where there is no data passed from the plugin to its subplugins, there is no need to define the subplugin provider. If the subplugins still do store personal data that are not related to the parent plugin in any way, then subplugins should define their own standard provider.&lt;br /&gt;
&lt;br /&gt;
Compare with something like mod_assign where the subplugins store data for the parent and that data is contextually relevant to the parent plugin. In those cases the subplugin stores data for the plugin and it only makes sense to do so in the context of its parent plugin.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
The following example defines the contract that assign submission subplugins may be required to implement.&lt;br /&gt;
&lt;br /&gt;
The assignment module is responsible for returning the contexts of all assignments where a user has data, but in some cases it is unaware of all of those cases - for example if a Teacher comments on a student submission it may not be aware of these as the information about this interaction may not be stored within its own tables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/assign/privacy/assignsubmission_provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_assign\privacy;&lt;br /&gt;
use \core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
interface assignsubmission_provider extends&lt;br /&gt;
    // This Interface defines a subplugin.&lt;br /&gt;
    \core_privacy\local\request\plugin\subplugin_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the SQL required to find all submission items where this user has had any involvements. &lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  \stdClass                   Object containing the join, params, and where used to select a these records from the database.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_items_with_user_interaction(int $userid) : \stdClass ;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Export all relevant user submissions information which match the combination of userid and attemptid.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @param   \context      $context      The context to export this submission against.&lt;br /&gt;
     * @param   array         $subcontext   The subcontext within the context to export this information&lt;br /&gt;
     * @param   int           $attid        The id of the submission to export.&lt;br /&gt;
     */&lt;br /&gt;
    public static function export_user_submissions(int $userid, \context $context, array $subcontext, int $attid) ;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are subplugins to another plugin====&lt;br /&gt;
&lt;br /&gt;
If you are developing a sub-plugin of another plugin, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subplugin type should define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface and it is up to the parent plugin to define how they will interact with their children.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/pluginname/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace assignsubmission\onlinetext;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does store personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is a subplugin of assign and must meet that contract.&lt;br /&gt;
    \mod_assign\privacy\assignsubmission_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Plugins which are typically called by a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
There are a number of plugintypes in Moodle which are typically called by a specific Moodle subsystem.&lt;br /&gt;
&lt;br /&gt;
Some of these are &#039;&#039;only&#039;&#039; called by that subsystem, for example plugins which are of the &#039;&#039;plagiarism&#039;&#039; plugintype should never be called directly, but are always invoked via the &#039;&#039;core_plagiarism&#039;&#039; subsystem.&lt;br /&gt;
&lt;br /&gt;
Conversely, there maybe other plugintypes which can be called both via a subsystem, and in some other fashion. We are still determining whether any plugintypes currently fit this pattern.&lt;br /&gt;
&lt;br /&gt;
If you are developing a plugin which belongs to a specific subsystem, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subsystem will define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subsystem_provider&#039;&#039; interface and it is up to that subsystem to define how they will interact with those plugins.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;plagiarism/detectorator/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace plagiarism_detectorator\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does export personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is always linked against another activity module via the Plagiarism API.&lt;br /&gt;
    \core_plagiarism\privacy\plugin_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Exporting data====&lt;br /&gt;
&lt;br /&gt;
Any plugin which stores data must also export it.&lt;br /&gt;
&lt;br /&gt;
To cater for this the privacy API includes a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which defines a set of functions to store different types of data.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking data is broken into the following types:&lt;br /&gt;
&lt;br /&gt;
* Data - this is the object being described. For example the post content in a forum post;&lt;br /&gt;
* Related data - this is data related to the object being stored. For example, ratings of a forum post;&lt;br /&gt;
* Metadata - This is metadata about the main object. For example whether you are subscribed to a forum discussion;&lt;br /&gt;
* User preferences - this is data about a site-wide preference;&lt;br /&gt;
* Files - Any files that you are stored within Moodle on behalf of this plugin; and&lt;br /&gt;
* Custom files - For custom file formats - e.g. a calendar feed for calendar data. These should be used sparingly.&lt;br /&gt;
&lt;br /&gt;
Each piece of data is stored against a specific Moodle &#039;&#039;context&#039;&#039;, which will define how the data is structured within the exporter.&lt;br /&gt;
Data, and Related data only accept the &#039;&#039;stdClass&#039;&#039; object, whilst metadata should be stored as a set of key/value pairs which include a description.&lt;br /&gt;
&lt;br /&gt;
In some cases the data being stored belongs within an implicit structure. For example, one forum has many forum discussions, which each have a number of forum posts. This structure is represented by an &#039;&#039;array&#039;&#039; referred to as a &#039;&#039;subcontext&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;content_writer&#039;&#039; must &#039;&#039;always&#039;&#039; be called with a specific context, and can be called as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
writer::with_context($context)&lt;br /&gt;
    -&amp;gt;export_data($subcontext, $post)&lt;br /&gt;
    -&amp;gt;export_area_files($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id)&lt;br /&gt;
    -&amp;gt;export_metadata($subcontext, &#039;postread&#039;, (object) [&#039;firstread&#039; =&amp;gt; $firstread], new \lang_string(&#039;privacy:export:post:postread&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any text field which supports Moodle files must also be rewritten:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
$post-&amp;gt;message = writer::with_context($context)&lt;br /&gt;
    -&amp;gt;rewrite_pluginfile_urls($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id, $post-&amp;gt;message);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to delete user data===&lt;br /&gt;
&lt;br /&gt;
Deleting user data is also implemented in the request interface. There are two methods that need to be created. The first one to remove all user data from a context, the other to remove user data for a specific user in a list of contexts.&lt;br /&gt;
&lt;br /&gt;
====Delete for a context====&lt;br /&gt;
&lt;br /&gt;
A context is given and all user data (for all users) is to be deleted from the plugin. This will be called when the retention period for the context has expired to adhere to the privacy by design requirement. Retention periods are set in the Data registry.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Delete all personal data for all users in the specified context.&lt;br /&gt;
 *&lt;br /&gt;
 * @param context $context Context to delete data from.&lt;br /&gt;
 */&lt;br /&gt;
public static function delete_data_for_all_users_in_context(\context $context) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
     if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
    $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Delete personal information for a specific user and context(s)====&lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;approved_contextlist&#039;&#039; is given and user data related to that user should either be completely deleted, or overwritten if a structure needs to be maintained. This will be called when a user has requested the right to be forgotten. All attempts should be made to delete this data where practical while still allowing the plugin to be used by other users.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/choice/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function delete_data_for_user(approved_contextlist $contextlist) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
    &lt;br /&gt;
    if (empty($contextlist-&amp;gt;count())) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    $userid = $contextlist-&amp;gt;get_user()-&amp;gt;id;&lt;br /&gt;
    foreach ($contextlist-&amp;gt;get_contexts() as $context) {&lt;br /&gt;
        $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid, &#039;userid&#039; =&amp;gt; $userid]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Difference between Moodle 3.3 and more recent versions==&lt;br /&gt;
Moodle 3.3 has a minimum requirement of php 5.6 and so type hinting and return type declarations are not supported in this version. &lt;br /&gt;
Consequently the privacy API for this version does not have these features.&lt;br /&gt;
==Common Questions==&lt;br /&gt;
===What to do if you have one plugin that supports multiple branches===&lt;br /&gt;
This is something that we have considered and we have put in place a polyfill. This gets around the restrictions of one version having type hinting and return type declarations while another does not.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
To use the polyfill include the legacy polyfill trait and create the necessary static methods but with an underscore (shown below).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
    \core_privacy\local\request\plugin\provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===What to do if your must implement a subplugin or subsystem plugin provider===&lt;br /&gt;
For subplugins (e.g. assignsubmission, assignfeedback, quiz report, quiz access rules), or subsystems which have a plugintype relationship (portfolio, plagiarism, and others), they will also define their own legacy polyfill.&lt;br /&gt;
&lt;br /&gt;
In this instance you will need to include the trait for both the core polyfill, and the provider polyfill as appropriate.&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin has data and must therefore define the metadata provider in order to describe it.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This is a plagiarism plugin. It interacts with the plagiarism subsystem rather than with core.&lt;br /&gt;
    \core_plagiarism\privacy\plagiarism_provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the metadata provider.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the plagirism provider.&lt;br /&gt;
    use \core_plagiarism\privacy\plagiarism_provider\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // This is one of the polyfilled methods from the plagiarism provider.&lt;br /&gt;
    public static function _export_plagiarism_user_data($userid, \context $context, array $subcontext, array $linkarray) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips for development ==&lt;br /&gt;
&lt;br /&gt;
* While implementing the privacy API into your plugin, there are CLI scripts that can help you to test things on the fly. Just don&#039;t forget these are not supposed to replace proper unit tests. See [[Privacy API/Utilities]] for details.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Subject Access Request FAQ]]&lt;br /&gt;
* [[:en:GDPR|GDPR]] in the user documentation&lt;br /&gt;
&lt;br /&gt;
[[Category:Privacy]]&lt;br /&gt;
[[Category:GDPR]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54127</id>
		<title>Talk:Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54127"/>
		<updated>2018-05-08T14:49:44Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Privacy API author credits==&lt;br /&gt;
&lt;br /&gt;
Thanks to Andrew Nicols with help from Adrian Greeve, Jake Dallimore, Sander Bangma and Jun Pataleta.&lt;br /&gt;
&lt;br /&gt;
This extensive documentation was drafted first in Google Docs ([https://docs.google.com/document/d/1Y7n4Qkez4Tl83rWArOQPQCpE2NeSA2bUa8gOR2r_JFE/edit# GDPR plugin API]), thus the above authors are not credited in the page history.&lt;br /&gt;
&lt;br /&gt;
==Some issues/questions==&lt;br /&gt;
&lt;br /&gt;
Could the get_contexts_for_userid function be explained in a bit more detail. It&#039;s unclear why this data is being collected. My particular case in a custom enrolment plugin that contains user data. Is the context the course context, the user context or something else... and why? There are no actual example to go on. --[[User:Howard Miller|Howard Miller]] ([[User talk:Howard Miller|talk]]) 14:09, 8 May 2018 (UTC)&lt;br /&gt;
&lt;br /&gt;
Pretty sure menu structure is all broken on this page. See menu levels 4.2 and 4.3. Sounds like they are not on the same level at all. It&#039;s currently rather confusing --[[User:Howard Miller|Howard Miller]] ([[User talk:Howard Miller|talk]]) 14:49, 8 May 2018 (UTC)&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54126</id>
		<title>Talk:Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54126"/>
		<updated>2018-05-08T14:49:16Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Privacy API author credits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Privacy API author credits==&lt;br /&gt;
&lt;br /&gt;
Thanks to Andrew Nicols with help from Adrian Greeve, Jake Dallimore, Sander Bangma and Jun Pataleta.&lt;br /&gt;
&lt;br /&gt;
This extensive documentation was drafted first in Google Docs ([https://docs.google.com/document/d/1Y7n4Qkez4Tl83rWArOQPQCpE2NeSA2bUa8gOR2r_JFE/edit# GDPR plugin API]), thus the above authors are not credited in the page history.&lt;br /&gt;
&lt;br /&gt;
Could the get_contexts_for_userid function be explained in a bit more detail. It&#039;s unclear why this data is being collected. My particular case in a custom enrolment plugin that contains user data. Is the context the course context, the user context or something else... and why? There are no actual example to go on. --[[User:Howard Miller|Howard Miller]] ([[User talk:Howard Miller|talk]]) 14:09, 8 May 2018 (UTC)&lt;br /&gt;
&lt;br /&gt;
Pretty sure menu structure is all broken on this page. See menu levels 4.2 and 4.3. Sounds like they are not on the same level at all. It&#039;s currently rather confusing --[[User:Howard Miller|Howard Miller]] ([[User talk:Howard Miller|talk]]) 14:49, 8 May 2018 (UTC)&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54125</id>
		<title>Talk:Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54125"/>
		<updated>2018-05-08T14:48:05Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Privacy API author credits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Privacy API author credits==&lt;br /&gt;
&lt;br /&gt;
Thanks to Andrew Nicols with help from Adrian Greeve, Jake Dallimore, Sander Bangma and Jun Pataleta.&lt;br /&gt;
&lt;br /&gt;
This extensive documentation was drafted first in Google Docs ([https://docs.google.com/document/d/1Y7n4Qkez4Tl83rWArOQPQCpE2NeSA2bUa8gOR2r_JFE/edit# GDPR plugin API]), thus the above authors are not credited in the page history.&lt;br /&gt;
&lt;br /&gt;
Could the get_contexts_for_userid function be explained in a bit more detail. It&#039;s unclear why this data is being collected. My particular case in a custom enrolment plugin that contains user data. Is the context the course context, the user context or something else... and why? There are no actual example to go on. --[[User:Howard Miller|Howard Miller]] ([[User talk:Howard Miller|talk]]) 14:09, 8 May 2018 (UTC)&lt;br /&gt;
&lt;br /&gt;
Pretty sure menu structure is all broken on this page.&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54124</id>
		<title>Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=54124"/>
		<updated>2018-05-08T14:36:26Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Basic example */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The [https://en.wikipedia.org/wiki/General_Data_Protection_Regulation General Data Protection Regulation] (GDPR) is an EU directive that looks at providing users with more control over their data and how it is processed. This regulation will come into effect on 25th of May 2018 and covers any citizen or permanent resident of the European Union. The directive will be respected by a number of other countries outside of the European Union.&lt;br /&gt;
&lt;br /&gt;
To help institutions become compliant with this new regulation we are adding functionality to Moodle. This includes a number of components, amongst others these include a user’s right to:&lt;br /&gt;
&lt;br /&gt;
* request information on the types of personal data held, the instances of that data, and the deletion policy for each;&lt;br /&gt;
* access all of their data; and&lt;br /&gt;
* be forgotten.&lt;br /&gt;
&lt;br /&gt;
The compliance requirements also extend to installed plugins (including third party plugins). These need to also be able to report what information they store or process regarding users, and have the ability to provide and delete data for a user request.&lt;br /&gt;
&lt;br /&gt;
This document describes the proposed API changes required for plugins which will allow a Moodle installation to become GDPR compliant.&lt;br /&gt;
&lt;br /&gt;
Target Audience: The intended audience for this document is Moodle plugin developers, who are aiming to ensure their plugins are updated to comply with GDPR requirements coming into effect in the EU in May, 2018.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Personal data in Moodle==&lt;br /&gt;
&lt;br /&gt;
From the GDPR Spec, Article 4:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;‘personal data’ means any information relating to an identified or identifiable natural person (‘data subject’); an identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In Moodle, we need to consider two main types of personal data; information entered by the user and information stored about the user. The key difference being that information stored about the user will have come from a source other than the user themselves. Both types of data can be used to form a profile of the individual.&lt;br /&gt;
&lt;br /&gt;
The most obvious clue to finding personal data entered by the user is the presence of a userid on a database field. Any data on the record (or linked records) pertaining to that user may be deemed personal data for that user, including things like timestamps and record identification numbers. Additionally, any free text field which allows the user to enter information must also be considered to be the personal data of that user.&lt;br /&gt;
&lt;br /&gt;
Data stored about the user includes things like ratings and comments made on a student submission. These may have been made by an assessor or teacher, but are considered the personal data of the student, as they are considered a reflection of the user’s competency in the subject matter and can be used to form a profile of that individual. &lt;br /&gt;
&lt;br /&gt;
The sections that follow outline what you need to do as a plugin developer to ensure any personal data is advertised and can be accessed and deleted according to the GDPR requirements.&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
===Architecture overview===&lt;br /&gt;
&lt;br /&gt;
[[File:MoodlePrivacyMetadataUML.png|thumb|UML diagram of the metadata part of the privacy subsystem]]&lt;br /&gt;
[[File:MoodlePrivacyRequestUML.png|thumb|UML diagram of the request providers part of the privacy subsystem]]&lt;br /&gt;
&lt;br /&gt;
A new system for Privacy has been created within Moodle. This is broken down into several main parts and forms the &#039;&#039;core_privacy&#039;&#039; subsystem:&lt;br /&gt;
&lt;br /&gt;
* Some metadata providers - a set of PHP interfaces to be implemented by components for that component to describe the kind of data that it stores, and the purpose for its storage;&lt;br /&gt;
* Some request providers - a set of PHP interfaces to be implemented by components to allow that component to act upon user requests such as the Right to be Forgotten, and a Subject Access Request; and&lt;br /&gt;
* A manager - a concrete class used to bridge components which implement the providers with tools which request their data.&lt;br /&gt;
&lt;br /&gt;
All plugins will implement one metadata provider, and zero, one or two request providers.&lt;br /&gt;
&lt;br /&gt;
The fetching of data is broken into two separate steps:&lt;br /&gt;
&lt;br /&gt;
# Detecting in which Moodle contexts the user has any data; and&lt;br /&gt;
# Exporting all data from each of those contexts.&lt;br /&gt;
&lt;br /&gt;
This has been broken into two steps to later allow administrators to exclude certain contexts from an export - e.g. for courses currently in progress.&lt;br /&gt;
&lt;br /&gt;
A third component will later be added to facilitate the deletion of data within these contexts which will help to satisfy the Right to be Forgotten. This will also use the first step.&lt;br /&gt;
&lt;br /&gt;
Please refer to the inline phpdocs of the [https://github.com/moodle/moodle/blob/v3.5.0-beta/privacy/classes/manager.php#L31 core_privacy::manager class] for detailed description of the interfaces, their hierarchy and meaning.&lt;br /&gt;
&lt;br /&gt;
===Implementing a provider===&lt;br /&gt;
&lt;br /&gt;
All plugins will need to create a concrete class which implements the relevant metadata and request providers. The exact providers you need to implement will depend on what data you store, and the type of plugin. This is covered in more detail in the following sections of the document.&lt;br /&gt;
&lt;br /&gt;
In order to do so:&lt;br /&gt;
&lt;br /&gt;
# You must create a class called &#039;&#039;provider&#039;&#039; within the namespace &#039;&#039;\your_pluginname\privacy&#039;&#039;.&lt;br /&gt;
# This class must be created at &#039;&#039;path/to/your/plugin/classes/privacy/provider.php&#039;&#039;.&lt;br /&gt;
# You must have your class implement the relevant metadata and request interfaces.&lt;br /&gt;
&lt;br /&gt;
==Plugins which do not store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do not store any personal data. This is usually the case for plugins which just add functionality, or which display the data already stored elsewhere in Moodle.&lt;br /&gt;
&lt;br /&gt;
Some examples of plugin types which might fit this criteria include themes, blocks, filters, editor plugins, etc.&lt;br /&gt;
&lt;br /&gt;
Plugins which cause data to be stored elsewhere in Moodle (e.g. via a subsystem call) are considered to store data.&lt;br /&gt;
&lt;br /&gt;
One examples of a plugin which does not store any data would be the Calendar month block which just displays a view of the user’s calendar. It does not store any data itself.&lt;br /&gt;
&lt;br /&gt;
An example of a plugin which must not use the null provider is the Comments block. The comments block is responsible for data subsequently being stored within Moodle. Although the block doesn’t store anything itself, it interacts with the comments subsystem and is the only component which knows how that data maps to a user.&lt;br /&gt;
&lt;br /&gt;
===Implementation requirements===&lt;br /&gt;
&lt;br /&gt;
In order to let Moodle know that you have audited your plugin, and that you do not store any personal user data, you must implement the &#039;&#039;\core_privacy\local\metadata\null_provider&#039;&#039; interface in your plugin’s provider.&lt;br /&gt;
&lt;br /&gt;
These null providers can only be implemented where a plugin has:&lt;br /&gt;
&lt;br /&gt;
* no external links (e.g. sends data to an external service like an LTI provider, repository plugin which you can search on)&lt;br /&gt;
* no database tables which store user data (including IP addresses)&lt;br /&gt;
* no user preferences&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;null_provider&#039;&#039; requires you to define one function &#039;&#039;get_reason()&#039;&#039; which returns the language string identifier within your component.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 &amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace block_calendar_month\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
    // This plugin does not store any personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\null_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the language string identifier with the component&#039;s language&lt;br /&gt;
     * file to explain why this plugin stores no data.&lt;br /&gt;
     *&lt;br /&gt;
     * @return  string&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_reason() : string {&lt;br /&gt;
        return &#039;privacy:metadata&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;blocks/calendar_month/lang/en/block_calendar_month.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
$string[&#039;privacy:metadata&#039;] = &#039;The Calendar block only displays existing calendar data.&#039;;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That’s it. Congratulations, your plugin now implements the Privacy API.&lt;br /&gt;
&lt;br /&gt;
==Plugins which store personal data==&lt;br /&gt;
&lt;br /&gt;
Many Moodle plugins do store some form of personal data.&lt;br /&gt;
&lt;br /&gt;
In some cases this will be stored within database tables in your plugin, and in other cases this will be in one of Moodle’s core subsystems - for example your plugin may store files, ratings, comments, or tags.&lt;br /&gt;
&lt;br /&gt;
Plugins which do store data will need to:&lt;br /&gt;
&lt;br /&gt;
* Describe the type of data that they store; &lt;br /&gt;
* Provide a way to export that data; and&lt;br /&gt;
* Provide a way to delete that data.&lt;br /&gt;
&lt;br /&gt;
Data is described via a &#039;&#039;metadata&#039;&#039; provider, and it is both exported and deleted via an implementation of a &#039;&#039;request&#039;&#039; provider.&lt;br /&gt;
&lt;br /&gt;
These are both explained in the sections below.&lt;br /&gt;
&lt;br /&gt;
===Describing the type of data you store===&lt;br /&gt;
&lt;br /&gt;
In order to describe the type of data that you store, you must implement the &#039;&#039;\core_privacy\local\metadata\provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
This interfaces requires that you define one function: &#039;&#039;get_metadata&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There are several types of item to describe the data that you store. These are for:&lt;br /&gt;
&lt;br /&gt;
* Items in the Moodle database;&lt;br /&gt;
* Items stored by you in a Moodle subsystem - for example files, and ratings; and&lt;br /&gt;
* User preferences stored site-wide within Moodle for your plugin&lt;br /&gt;
&lt;br /&gt;
Note: All fields should include a description from a language string within your plugin.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_forum\privacy;&lt;br /&gt;
use core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
class provider implements &lt;br /&gt;
        // This plugin does store personal user data.&lt;br /&gt;
        \core_privacy\local\metadata\provider {&lt;br /&gt;
&lt;br /&gt;
    public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
        // Here you will add more items into the collection.&lt;br /&gt;
&lt;br /&gt;
        return $collection;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store content in a Moodle subsystem====&lt;br /&gt;
&lt;br /&gt;
Many plugins will use one of the core Moodle subsystems to store data.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer we do not expect you to describe those subsystems in detail, but we do need to know that you use them and to know what you use them for.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_subsystem_link()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_subsystem_link(&lt;br /&gt;
        &#039;core_files&#039;,&lt;br /&gt;
        [],&lt;br /&gt;
        &#039;privacy:metadata:core_files&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:core_files&#039;] = &#039;The forum stores files which have been uploaded by the user to form part of a forum post.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Describing data stored in database tables====&lt;br /&gt;
&lt;br /&gt;
Most Moodle plugins will store some form of user data in their own database tables.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each database table, and each field which includes user data.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_database_table(&lt;br /&gt;
        &#039;forum_discussion_subs&#039;,&lt;br /&gt;
         [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:userid&#039;,&lt;br /&gt;
            &#039;discussionid&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:discussionid&#039;,&lt;br /&gt;
            &#039;preference&#039; =&amp;gt; &#039;privacy:metadata:forum_discussion_subs:preference&#039;,&lt;br /&gt;
&lt;br /&gt;
         ],&lt;br /&gt;
        &#039;privacy:metadata:forum_discussion_subs&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs&#039;] = &#039;Information about the subscriptions to individual forum discussions. This includes when a user has chosen to subscribe to a discussion, or to unsubscribe from one where they would otherwise be subscribed.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:userid&#039;] = &#039;The ID of the user with this subscription preference.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:discussionid&#039;] = &#039;The ID of the discussion that was subscribed to.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:forum_discussion_subs:preference&#039;] = &#039;The start time of the subscription.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you store site-wide user preferences====&lt;br /&gt;
&lt;br /&gt;
Many plugins will include one or more user preferences. Unfortunately this is one of Moodle’s older components and many of the values stored are not pure user preferences. Each plugin should be aware of how it handles its own preferences and is best placed to determine whether they are site-wide preferences, or per-instance preferences.&lt;br /&gt;
&lt;br /&gt;
Whilst most of these will have a fixed name (e.g. &#039;&#039;filepicker_recentrepository&#039;&#039;), some will include a variable of some kind (e.g. &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039;). Only the general name needs to be indicated rather than one copy for each preference.&lt;br /&gt;
&lt;br /&gt;
Also, these should only be &#039;&#039;site-wide&#039;&#039; user preferences which do not belong to a specific Moodle context.&lt;br /&gt;
&lt;br /&gt;
In the above examples:&lt;br /&gt;
&lt;br /&gt;
* Preference &#039;&#039;filepicker_recentrepository&#039;&#039; belongs to the file subsystem, and is a site-wide preference affecting the user anywhere that they view the filepicker.&lt;br /&gt;
* Preference &#039;&#039;tool_usertours_tour_completion_time_2&#039;&#039; belongs to user tours. User tours are a site-wide feature which can affect many parts of Moodle and cross multiple contexts.&lt;br /&gt;
&lt;br /&gt;
In some cases a value may be stored in the preferences table but is known to belong to a specific context within Moodle. In these cases they should be stored as metadata against that context rather than as a site-wide user preference.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_user_preference()&#039;&#039; method on the &#039;&#039;collection&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Any plugin providing user preferences must also implement the &#039;&#039;\core_privacy\local\request\preference_provider&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_user_preference(&#039;tool_usertours_tour_completion_time&#039;,&lt;br /&gt;
        &#039;privacy:metadata:preference:tool_usertours_tour_completion_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;admin/tool/usertours/lang/en/tool_usertours.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:tool_usertours_tour_completion_time&#039;] = &#039;The time that a specific user tour was last completed by a user.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Indicating that you export data to an external location====&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with external systems - for example cloud-based services. Often this external location is configurable within the plugin either at the site or the instance level.&lt;br /&gt;
&lt;br /&gt;
As a plugin developer you will need to describe each &#039;&#039;type&#039;&#039; of target destination, alongside a list of each exported field which includes user data.&lt;br /&gt;
The &#039;&#039;actual&#039;&#039; destination does not need to be described as this can change based on configuration.&lt;br /&gt;
&lt;br /&gt;
You can indicate this by calling the &#039;&#039;add_external_location_link()&#039;&#039; method on the collection.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_metadata(collection $collection) : collection {&lt;br /&gt;
&lt;br /&gt;
    $collection-&amp;gt;add_external_location_link(&#039;lti_client&#039;, [&lt;br /&gt;
            &#039;userid&#039; =&amp;gt; &#039;privacy:metadata:lti_client:userid&#039;,&lt;br /&gt;
            &#039;fullname&#039; =&amp;gt; &#039;privacy:metadata:lti_client:fullname&#039;,&lt;br /&gt;
        ], &#039;privacy:metadata:lti_client&#039;);&lt;br /&gt;
&lt;br /&gt;
    return $collection;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/lti/lang/en/lti.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client&#039;] = &#039;In order to integrate with a remote LTI service, user data needs to be exchanged with that service.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:userid&#039;] = &#039;The userid is sent from Moodle to allow you to access your data on the remote system.&#039;;&lt;br /&gt;
$string[&#039;privacy:metadata:lti_client:fullname&#039;] = &#039;Your full name is sent to the remote system to allow a better user experience.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to export user data===&lt;br /&gt;
&lt;br /&gt;
In order to export the user data that you store, you must implement the relevant request provider.&lt;br /&gt;
&lt;br /&gt;
We have named these request providers because they are called in response to a specific request from a user to access their information.&lt;br /&gt;
&lt;br /&gt;
There are several different types of request provider, and you may need to implement several of these, depending on the type and nature of your plugin.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking plugins will fit into one of the following categories:&lt;br /&gt;
&lt;br /&gt;
* Plugins which are a subplugin of another plugin. Examples include &#039;&#039;assignsubmission&#039;&#039;, &#039;&#039;atto&#039;&#039;, and &#039;&#039;datafield&#039;&#039;;&lt;br /&gt;
* Plugins which are typically called by a Moodle subsystem. Examples include &#039;&#039;qtype&#039;&#039;, and &#039;&#039;profilefield&#039;&#039;;&lt;br /&gt;
* All other plugins which store data.&lt;br /&gt;
&lt;br /&gt;
Most plugins will fit into this final category, whilst other plugins may fall into several categories.&lt;br /&gt;
Plugins which &#039;&#039;define&#039;&#039; a subplugin will also be responsible for  collecting this data from their subplugins.&lt;br /&gt;
&lt;br /&gt;
A final category exists - plugins which store user preferences. In some cases this may be the &#039;&#039;only&#039;&#039; provider implemented.&lt;br /&gt;
&lt;br /&gt;
====Standard plugins which store data====&lt;br /&gt;
&lt;br /&gt;
A majority of Moodle plugins will fit into this category and will be required to implement the &#039;&#039;\core_privacy\local\request\plugin\provider&#039;&#039; interface. This interface requires that you define two functions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;get_contexts_for_userid&#039;&#039; - to explain where data is held within Moodle for your plugin; and&lt;br /&gt;
* &#039;&#039;export_user_data&#039;&#039; - to export a user’s personal data from your plugin.&lt;br /&gt;
&lt;br /&gt;
These APIs make use of the Moodle &#039;&#039;context&#039;&#039; system to hierarchically store this data.&lt;br /&gt;
&lt;br /&gt;
====Retrieving the list of contexts====&lt;br /&gt;
&lt;br /&gt;
Contexts are retrieved using the &#039;&#039;get_contexts_for_userid&#039;&#039; function which takes the ID of the user being fetched, and returns a list of contexts in which the user has any data.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function returns a &#039;&#039;\core_privacy\local\request\contextlist&#039;&#039; which is used to keep a set of contexts together in a fixed fashion.&lt;br /&gt;
&lt;br /&gt;
Because a Subject Access Request covers &#039;&#039;every&#039;&#039; piece of data that is held for a user within Moodle, efficiency and performance is highly important. As a result, contexts are added to the &#039;&#039;contextlist&#039;&#039; by defining one or more SQL queries which return just the contextid. Multiple SQL queries can be added as required.&lt;br /&gt;
&lt;br /&gt;
Many plugins will interact with specific subsystems and store data within them.&lt;br /&gt;
These subsystems will also provide a way in which to link the data that you have stored with your own database tables.&lt;br /&gt;
At present these are still a work in progress and only the &#039;&#039;core_ratings&#039;&#039; subsystem includes this.&lt;br /&gt;
&lt;br /&gt;
=====Basic example=====&lt;br /&gt;
&lt;br /&gt;
The following example simply fetches the contextid for all forums where a user has a single discussion (note: this is an incomplete example):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
        $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
&lt;br /&gt;
        $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                 FROM {context} c&lt;br /&gt;
           INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
           INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
           INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
                WHERE (&lt;br /&gt;
                d.userid        = :discussionuserid&lt;br /&gt;
                )&lt;br /&gt;
        &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        $params = [&lt;br /&gt;
            &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
            &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
            &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
        return $contextlist;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====More complete example=====&lt;br /&gt;
&lt;br /&gt;
The following example includes a link to core_rating. &lt;br /&gt;
It will find any forum, forum discussion, or forum post where the user has any data, including:&lt;br /&gt;
&lt;br /&gt;
* Per-forum digest preferences;&lt;br /&gt;
* Per-forum subscription preferences;&lt;br /&gt;
* Per-forum read tracking preferences;&lt;br /&gt;
* Per-discussion subscription preferences;&lt;br /&gt;
* Per-post read data (if a user has read a post or not); and&lt;br /&gt;
* Per-post rating data.&lt;br /&gt;
&lt;br /&gt;
In the case of the rating data, this will include any post where the user has rated the post of another user.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Get the list of contexts that contain user information for the specified user.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int           $userid       The user to search.&lt;br /&gt;
 * @return  contextlist   $contextlist  The list of contexts used in this plugin.&lt;br /&gt;
 */&lt;br /&gt;
public static function get_contexts_for_userid(int $userid) : contextlist {&lt;br /&gt;
    $ratingsql = \core_rating\privacy\provider::get_sql_join(&#039;rat&#039;, &#039;mod_forum&#039;, &#039;post&#039;, &#039;p.id&#039;, $userid);&lt;br /&gt;
    // Fetch all forum discussions, and forum posts.&lt;br /&gt;
    $sql = &amp;quot;SELECT c.id&lt;br /&gt;
                FROM {context} c&lt;br /&gt;
        INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel&lt;br /&gt;
        INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname&lt;br /&gt;
        INNER JOIN {forum} f ON f.id = cm.instance&lt;br /&gt;
            LEFT JOIN {forum_discussions} d ON d.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_posts} p ON p.discussion = d.id&lt;br /&gt;
            LEFT JOIN {forum_digests} dig ON dig.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id&lt;br /&gt;
            LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_read} hasread ON hasread.forumid = f.id&lt;br /&gt;
            LEFT JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id&lt;br /&gt;
            {$ratingsql-&amp;gt;join}&lt;br /&gt;
                WHERE (&lt;br /&gt;
                p.userid        = :postuserid OR&lt;br /&gt;
                d.userid        = :discussionuserid OR&lt;br /&gt;
                dig.userid      = :digestuserid OR&lt;br /&gt;
                sub.userid      = :subuserid OR&lt;br /&gt;
                pref.userid     = :prefuserid OR&lt;br /&gt;
                hasread.userid  = :hasreaduserid OR&lt;br /&gt;
                dsub.userid     = :dsubuserid OR&lt;br /&gt;
                {$ratingsql-&amp;gt;userwhere}&lt;br /&gt;
            )&lt;br /&gt;
    &amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    $params = [&lt;br /&gt;
        &#039;modname&#039;           =&amp;gt; &#039;forum&#039;,&lt;br /&gt;
        &#039;contextlevel&#039;      =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
        &#039;postuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;discussionuserid&#039;  =&amp;gt; $userid,&lt;br /&gt;
        &#039;digestuserid&#039;      =&amp;gt; $userid,&lt;br /&gt;
        &#039;subuserid&#039;         =&amp;gt; $userid,&lt;br /&gt;
        &#039;prefuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
        &#039;hasreaduserid&#039;     =&amp;gt; $userid,&lt;br /&gt;
        &#039;dsubuserid&#039;        =&amp;gt; $userid,&lt;br /&gt;
    ];&lt;br /&gt;
    $params += $ratingsql-&amp;gt;params;&lt;br /&gt;
&lt;br /&gt;
    $contextlist = new \core_privacy\local\request\contextlist();&lt;br /&gt;
    $contextlist-&amp;gt;add_from_sql($sql, $params);&lt;br /&gt;
&lt;br /&gt;
    return $contextlist;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Exporting user data====&lt;br /&gt;
&lt;br /&gt;
After determining where in Moodle your plugin holds data about a user, the &#039;&#039;\core_privacy\manager&#039;&#039; will then ask your plugin to export all user data for a subset of those locations.&lt;br /&gt;
&lt;br /&gt;
This is achieved through use of the &#039;&#039;export_user_data&#039;&#039; function which takes the list of approved contexts in a &#039;&#039;\core_privacy\local\request\approved_contextlist&#039;&#039; object.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   approved_contextlist    $contextlist    The approved contexts to export information for.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_data(approved_contextlist $contextlist) {}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;approved_contextlist&#039;&#039; includes both the user record, and a list of contexts, which can be retrieved by either processing it as an Iterator, or by calling &#039;&#039;get_contextids()&#039;&#039; or &#039;&#039;get_contexts()&#039;&#039; as required.&lt;br /&gt;
&lt;br /&gt;
Data is exported using a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which is described in further detail below.&lt;br /&gt;
&lt;br /&gt;
===Plugins which store user preferences===&lt;br /&gt;
&lt;br /&gt;
Many plugins store a variety of user preferences, and must therefore export them.&lt;br /&gt;
&lt;br /&gt;
Since user preferences are a site-wide preference, these are exported separately to other user data.&lt;br /&gt;
In some cases the only data present is user preference data, whilst in others there is a combination of user-provided data, and user preferences.&lt;br /&gt;
&lt;br /&gt;
Storing of user preferences is achieved through implementation of the &#039;&#039;\core_privacy\local\request\user_preference_provider&#039;&#039; interface which defines one required function -- &#039;&#039;export_user_preferences&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Export all user preferences for the plugin.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   int         $userid The userid of the user whose data is to be exported.&lt;br /&gt;
 */&lt;br /&gt;
public static function export_user_preferences(int $userid) {&lt;br /&gt;
    $markasreadonnotification = get_user_preference(&#039;markasreadonnotification&#039;, null, $userid);&lt;br /&gt;
    if (null !== $markasreadonnotification) {&lt;br /&gt;
        switch ($markasreadonnotification) {&lt;br /&gt;
            case 0:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationno&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
            case 1:&lt;br /&gt;
            default:&lt;br /&gt;
                $markasreadonnotificationdescription = get_string(&#039;markasreadonnotificationyes&#039;, &#039;mod_forum&#039;);&lt;br /&gt;
                break;&lt;br /&gt;
        }&lt;br /&gt;
        writer::export_user_preference(&#039;mod_forum&#039;, &#039;markasreadonnotification&#039;, $markasreadonnotification, $markasreadonnotificationdescription);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Plugins which can have own subplugins ===&lt;br /&gt;
&lt;br /&gt;
Many plugin types are also able to define their own subplugins and will need to define a contract between themselves and their subplugins in order to fetch their data.&lt;br /&gt;
&lt;br /&gt;
This is required as the parent plugin and the child subplugin should be separate entities and the parent plugin must be able to function if one or more of its subplugins are uninstalled.&lt;br /&gt;
&lt;br /&gt;
The parent plugin is responsible for defining the contract,  and for interacting with its subplugins, though we intend to create helpers to make this easier.&lt;br /&gt;
&lt;br /&gt;
The parent plugin should define a new interface for each type of subplugin that it defines. This interface should extend the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface.&lt;br /&gt;
&lt;br /&gt;
==== When a parent plugin should and should not provide the interface for its subplugins ====&lt;br /&gt;
&lt;br /&gt;
There can be cases when there is no point for a plugin to provide the &amp;quot;subplugin_provider&amp;quot; based interface, even if it has own subplugins. See the Atto or TinyMCE editors as real examples.&lt;br /&gt;
&lt;br /&gt;
If the parent plugin has no data passed through to the subplugins, there is no benefit in defining a subplugin provider. For example, Atto subplugins are just used to enhance the functionality and they never receive anything like a context. Most of the time we need to define a subplugin provider, but in cases where there is no data passed from the plugin to its subplugins, there is no need to define the subplugin provider. If the subplugins still do store personal data that are not related to the parent plugin in any way, then subplugins should define their own standard provider.&lt;br /&gt;
&lt;br /&gt;
Compare with something like mod_assign where the subplugins store data for the parent and that data is contextually relevant to the parent plugin. In those cases the subplugin stores data for the plugin and it only makes sense to do so in the context of its parent plugin.&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&lt;br /&gt;
The following example defines the contract that assign submission subplugins may be required to implement.&lt;br /&gt;
&lt;br /&gt;
The assignment module is responsible for returning the contexts of all assignments where a user has data, but in some cases it is unaware of all of those cases - for example if a Teacher comments on a student submission it may not be aware of these as the information about this interaction may not be stored within its own tables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/assign/privacy/assignsubmission_provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
&lt;br /&gt;
namespace mod_assign\privacy;&lt;br /&gt;
use \core_privacy\local\metadata\collection;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
interface assignsubmission_provider extends&lt;br /&gt;
    // This Interface defines a subplugin.&lt;br /&gt;
    \core_privacy\local\request\plugin\subplugin_provider {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Get the SQL required to find all submission items where this user has had any involvements. &lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @return  \stdClass                   Object containing the join, params, and where used to select a these records from the database.&lt;br /&gt;
     */&lt;br /&gt;
    public static function get_items_with_user_interaction(int $userid) : \stdClass ;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Export all relevant user submissions information which match the combination of userid and attemptid.&lt;br /&gt;
     *&lt;br /&gt;
     * @param   int           $userid       The user to search.&lt;br /&gt;
     * @param   \context      $context      The context to export this submission against.&lt;br /&gt;
     * @param   array         $subcontext   The subcontext within the context to export this information&lt;br /&gt;
     * @param   int           $attid        The id of the submission to export.&lt;br /&gt;
     */&lt;br /&gt;
    public static function export_user_submissions(int $userid, \context $context, array $subcontext, int $attid) ;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Plugins which are subplugins to another plugin===&lt;br /&gt;
&lt;br /&gt;
If you are developing a sub-plugin of another plugin, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subplugin type should define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subplugin_provider&#039;&#039; interface and it is up to the parent plugin to define how they will interact with their children.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/pluginname/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace assignsubmission\onlinetext;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does store personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is a subplugin of assign and must meet that contract.&lt;br /&gt;
    \mod_assign\privacy\assignsubmission_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Plugins which are typically called by a Moodle subsystem===&lt;br /&gt;
&lt;br /&gt;
There are a number of plugintypes in Moodle which are typically called by a specific Moodle subsystem.&lt;br /&gt;
&lt;br /&gt;
Some of these are &#039;&#039;only&#039;&#039; called by that subsystem, for example plugins which are of the &#039;&#039;plagiarism&#039;&#039; plugintype should never be called directly, but are always invoked via the &#039;&#039;core_plagiarism&#039;&#039; subsystem.&lt;br /&gt;
&lt;br /&gt;
Conversely, there maybe other plugintypes which can be called both via a subsystem, and in some other fashion. We are still determining whether any plugintypes currently fit this pattern.&lt;br /&gt;
&lt;br /&gt;
If you are developing a plugin which belongs to a specific subsystem, then you will have to look at the relevant plugin in order to determine the exact contract.&lt;br /&gt;
&lt;br /&gt;
Each subsystem will define a new interface which extends the &#039;&#039;\core_privacy\local\request\plugin\subsystem_provider&#039;&#039; interface and it is up to that subsystem to define how they will interact with those plugins.&lt;br /&gt;
&lt;br /&gt;
The principles remain the same, but the exact implementation will differ depending upon requirements.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;plagiarism/detectorator/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
namespace plagiarism_detectorator\privacy;&lt;br /&gt;
&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin does export personal user data.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This plugin is always linked against another activity module via the Plagiarism API.&lt;br /&gt;
    \core_plagiarism\privacy\plugin_provider {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Exporting data===&lt;br /&gt;
&lt;br /&gt;
Any plugin which stores data must also export it.&lt;br /&gt;
&lt;br /&gt;
To cater for this the privacy API includes a &#039;&#039;\core_privacy\local\request\content_writer&#039;&#039;, which defines a set of functions to store different types of data.&lt;br /&gt;
&lt;br /&gt;
Broadly speaking data is broken into the following types:&lt;br /&gt;
&lt;br /&gt;
* Data - this is the object being described. For example the post content in a forum post;&lt;br /&gt;
* Related data - this is data related to the object being stored. For example, ratings of a forum post;&lt;br /&gt;
* Metadata - This is metadata about the main object. For example whether you are subscribed to a forum discussion;&lt;br /&gt;
* User preferences - this is data about a site-wide preference;&lt;br /&gt;
* Files - Any files that you are stored within Moodle on behalf of this plugin; and&lt;br /&gt;
* Custom files - For custom file formats - e.g. a calendar feed for calendar data. These should be used sparingly.&lt;br /&gt;
&lt;br /&gt;
Each piece of data is stored against a specific Moodle &#039;&#039;context&#039;&#039;, which will define how the data is structured within the exporter.&lt;br /&gt;
Data, and Related data only accept the &#039;&#039;stdClass&#039;&#039; object, whilst metadata should be stored as a set of key/value pairs which include a description.&lt;br /&gt;
&lt;br /&gt;
In some cases the data being stored belongs within an implicit structure. For example, one forum has many forum discussions, which each have a number of forum posts. This structure is represented by an &#039;&#039;array&#039;&#039; referred to as a &#039;&#039;subcontext&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;content_writer&#039;&#039; must &#039;&#039;always&#039;&#039; be called with a specific context, and can be called as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
writer::with_context($context)&lt;br /&gt;
    -&amp;gt;export_data($subcontext, $post)&lt;br /&gt;
    -&amp;gt;export_area_files($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id)&lt;br /&gt;
    -&amp;gt;export_metadata($subcontext, &#039;postread&#039;, (object) [&#039;firstread&#039; =&amp;gt; $firstread], new \lang_string(&#039;privacy:export:post:postread&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any text field which supports Moodle files must also be rewritten:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/forum/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// …&lt;br /&gt;
use \core_privacy\local\request\writer;&lt;br /&gt;
&lt;br /&gt;
$post-&amp;gt;message = writer::with_context($context)&lt;br /&gt;
    -&amp;gt;rewrite_pluginfile_urls($subcontext, &#039;mod_forum&#039;, &#039;post&#039;, $post-&amp;gt;id, $post-&amp;gt;message);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Providing a way to delete user data===&lt;br /&gt;
&lt;br /&gt;
Deleting user data is also implemented in the request interface. There are two methods that need to be created. The first one to remove all user data from a context, the other to remove user data for a specific user in a list of contexts.&lt;br /&gt;
&lt;br /&gt;
====Delete for a context====&lt;br /&gt;
&lt;br /&gt;
A context is given and all user data (for all users) is to be deleted from the plugin. This will be called when the retention period for the context has expired to adhere to the privacy by design requirement. Retention periods are set in the Data registry.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Delete all personal data for all users in the specified context.&lt;br /&gt;
 *&lt;br /&gt;
 * @param context $context Context to delete data from.&lt;br /&gt;
 */&lt;br /&gt;
public static function delete_data_for_all_users_in_context(\context $context) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
     if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
    $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Delete personal information for a specific user and context(s)====&lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;approved_contextlist&#039;&#039; is given and user data related to that user should either be completely deleted, or overwritten if a structure needs to be maintained. This will be called when a user has requested the right to be forgotten. All attempts should be made to delete this data where practical while still allowing the plugin to be used by other users.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;mod/choice/classes/privacy/provider.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function delete_data_for_user(approved_contextlist $contextlist) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
    &lt;br /&gt;
    if (empty($contextlist-&amp;gt;count())) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    $userid = $contextlist-&amp;gt;get_user()-&amp;gt;id;&lt;br /&gt;
    foreach ($contextlist-&amp;gt;get_contexts() as $context) {&lt;br /&gt;
        $instanceid = $DB-&amp;gt;get_field(&#039;course_modules&#039;, &#039;instance&#039;, [&#039;id&#039; =&amp;gt; $context-&amp;gt;instanceid], MUST_EXIST);&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;choice_answers&#039;, [&#039;choiceid&#039; =&amp;gt; $instanceid, &#039;userid&#039; =&amp;gt; $userid]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Difference between Moodle 3.3 and more recent versions===&lt;br /&gt;
Moodle 3.3 has a minimum requirement of php 5.6 and so type hinting and return type declarations are not supported in this version. Consequently the privacy API for this version does not have these features.&lt;br /&gt;
====What to do if you have one plugin that supports multiple branches====&lt;br /&gt;
This is something that we have considered and we have put in place a polyfill. This gets around the restrictions of one version having type hinting and return type declarations while another does not.&lt;br /&gt;
&lt;br /&gt;
=====Example=====&lt;br /&gt;
To use the polyfill include the legacy polyfill trait and create the necessary static methods but with an underscore (shown below).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
    \core_privacy\local\request\plugin\provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====What to do if your must implement a subplugin or subsystem plugin provider====&lt;br /&gt;
For subplugins (e.g. assignsubmission, assignfeedback, quiz report, quiz access rules), or subsystems which have a plugintype relationship (portfolio, plagiarism, and others), they will also define their own legacy polyfill.&lt;br /&gt;
&lt;br /&gt;
In this instance you will need to include the trait for both the core polyfill, and the provider polyfill as appropriate.&lt;br /&gt;
===== Example =====&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class provider implements&lt;br /&gt;
    // This plugin has data and must therefore define the metadata provider in order to describe it.&lt;br /&gt;
    \core_privacy\local\metadata\provider,&lt;br /&gt;
&lt;br /&gt;
    // This is a plagiarism plugin. It interacts with the plagiarism subsystem rather than with core.&lt;br /&gt;
    \core_plagiarism\privacy\plagiarism_provider {&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the metadata provider.&lt;br /&gt;
    use \core_privacy\local\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // This trait must be included to provide the relevant polyfill for the plagirism provider.&lt;br /&gt;
    use \core_plagiarism\privacy\plagiarism_provider\legacy_polyfill;&lt;br /&gt;
&lt;br /&gt;
    // The required methods must be in this format starting with an underscore.&lt;br /&gt;
    public static function _get_metadata(collection $collection) {&lt;br /&gt;
        // Code for returning metadata goes here.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // This is one of the polyfilled methods from the plagiarism provider.&lt;br /&gt;
    public static function _export_plagiarism_user_data($userid, \context $context, array $subcontext, array $linkarray) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips for development ==&lt;br /&gt;
&lt;br /&gt;
* While implementing the privacy API into your plugin, there are CLI scripts that can help you to test things on the fly. Just don&#039;t forget these are not supposed to replace proper unit tests. See [[Privacy API/Utilities]] for details.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Subject Access Request FAQ]]&lt;br /&gt;
* [[:en:GDPR|GDPR]] in the user documentation&lt;br /&gt;
&lt;br /&gt;
[[Category:Privacy]]&lt;br /&gt;
[[Category:GDPR]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54123</id>
		<title>Talk:Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Talk:Privacy_API&amp;diff=54123"/>
		<updated>2018-05-08T14:09:34Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Privacy API author credits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Privacy API author credits==&lt;br /&gt;
&lt;br /&gt;
Thanks to Andrew Nicols with help from Adrian Greeve, Jake Dallimore, Sander Bangma and Jun Pataleta.&lt;br /&gt;
&lt;br /&gt;
This extensive documentation was drafted first in Google Docs ([https://docs.google.com/document/d/1Y7n4Qkez4Tl83rWArOQPQCpE2NeSA2bUa8gOR2r_JFE/edit# GDPR plugin API]), thus the above authors are not credited in the page history.&lt;br /&gt;
&lt;br /&gt;
Could the get_contexts_for_userid function be explained in a bit more detail. It&#039;s unclear why this data is being collected. My particular case in a custom enrolment plugin that contains user data. Is the context the course context, the user context or something else... and why? There are no actual example to go on. --[[User:Howard Miller|Howard Miller]] ([[User talk:Howard Miller|talk]]) 14:09, 8 May 2018 (UTC)&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=54007</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=54007"/>
		<updated>2018-04-07T15:01:48Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Minimum (getting started) module for plugins */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[[grunt]]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/. Moodle currently requires node &amp;quot;8.9.x&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Development mode ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing. &lt;br /&gt;
Or, &amp;quot;No define call for theme_XXX/loader&amp;quot; as you are probably missing the &#039;src&#039; folder with relevant JS files. which might happen when you turn debugging ON on a theme that was bought, without &#039;src&#039; folder :-(&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
Note: Once you have run grunt and built your code, you will then need to purge Moodle caches otherwise the changes made to your minified files may not be picked up by Moodle.&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
&lt;br /&gt;
            // Put whatever you like here. $ is available&lt;br /&gt;
            // to you as normal.&lt;br /&gt;
            $(&amp;quot;.someclass&amp;quot;).change(function() {&lt;br /&gt;
                alert(&amp;quot;It changed!!&amp;quot;);&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;
This code passes the jquery module into our function (parameter $). There are a number of other useful modules available in Moodle, some of which you&#039;ll probably need in a practical application. See [[Useful_core_Javascript_modules]]. Simply list them in both the define() first parameter and the function callback. E.g.,&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    define([&#039;jquery&#039;, &#039;core/str&#039;, &#039;core/ajax&#039;], function($, str, ajax) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;[[Frankenstyle]]&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    return {&lt;br /&gt;
        init: function(first, last) {&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&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;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
            return factory(a0);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($, c) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&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;
2. Put the javascript into a mustache template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Call the javascript directly from php (although who would want to put javascript into php? ergh):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* [https://assets.moodlemoot.org/sites/15/20171004085436/JavaScript-AMD-with-RequireJS-presented-by-Daniel-Roperto-Catalyst.pdf JavaScript AMD with RequireJS] presented by Daniel Roperto, Catalyst. (MoodleMOOT AU 2017)&lt;br /&gt;
* [[Useful_core_Javascript_modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=AMD_Modal&amp;diff=53767</id>
		<title>AMD Modal</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=AMD_Modal&amp;diff=53767"/>
		<updated>2018-03-04T21:51:18Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* How do you create a different type of modal? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
This is currently (2017) the recommended way to create various sorts of pop-up dialogue boxes in JavaScript.&lt;br /&gt;
&lt;br /&gt;
= Why should you use it? =&lt;br /&gt;
The AMD modal modules provide a simple interface for creating a modal within Moodle. The module will ensure all accessibility requirements are met including applying the correct aria roles, focus control, aria hiding background elements and locking keyboard navigation.&lt;br /&gt;
&lt;br /&gt;
The modals will fire events for common actions that occur within the modal, e.g. show / hide, for other code to listen to and react accordingly.&lt;br /&gt;
&lt;br /&gt;
Moodle ships with a couple of standard modal types for you to re-use including a simple cancel modal, a confirm (yes/no) modal and a save/cancel modal. Hopefully with more to come!&lt;br /&gt;
&lt;br /&gt;
= How do you create a basic modal? =&lt;br /&gt;
If you&#039;d simply like to display a modal with some simple content then all you&#039;ll need is the modal factory. The factory provides a create function that accepts some configuration for your modal that the factory will use to create the modal and optionally a trigger element (the element that will open the modal when activated). The create function will return a promise that is resolved with the created modal.&lt;br /&gt;
&lt;br /&gt;
The configuration is provided as an object with key/value pairs. The options are:&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;key&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| title&lt;br /&gt;
| the title to display in the modal header - note: this will render HTML&lt;br /&gt;
|-&lt;br /&gt;
| body&lt;br /&gt;
| the main content to be rendered in the modal body&lt;br /&gt;
|-&lt;br /&gt;
| footer&lt;br /&gt;
| the content to be rendered in the modal footer&lt;br /&gt;
|-&lt;br /&gt;
| type&lt;br /&gt;
| one of the modal types registered with the factory&lt;br /&gt;
|-&lt;br /&gt;
| large&lt;br /&gt;
| a boolean to indicate if the modal should be wider than the default size&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example 1&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The modals will also accept a promise for the body and footer content. It is expected that the promise will be resolved with two strings, the html content and any associated javascript. This allows the modal module to work natively with the templates module, such as in the example below.&lt;br /&gt;
&lt;br /&gt;
Example 2&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/templates&#039;], function($, ModalFactory, Templates) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    // Can include JS which is run when modal is attached to DOM.&lt;br /&gt;
    body: Templates.render(&#039;core/modal_test_3&#039;, {}),&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the modals aren&#039;t actually added to the DOM until they are made visible any javascript resolved with the promise is cached and only run after all of the elements are added to the DOM,  so you don&#039;t have to worry about any special handling in the javascript that is being loaded from the template.&lt;br /&gt;
&lt;br /&gt;
= How do you create a different type of modal? =&lt;br /&gt;
Moodle comes with a few specialised types of modals for common use cases. Look at lib/amd/src/modal_factory.js to see what is available. These can be created using the factory, similar to the examples above, by specifying the type of modal in the configuration provided to the create function. Each of the modal types may have different configuration options, for example the save/cancel modal doesn&#039;t allow you to set the footer content.&lt;br /&gt;
&lt;br /&gt;
Example 3&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/templates&#039;], function($, ModalFactory, Templates) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
    title: &#039;Modal save cancel&#039;,&lt;br /&gt;
    body: &#039;This modal is a save/cancel modal&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each type of modal may fire additional events to allow your code to handle the new functionality being offered. See the modal_events.js module for a list of events that can be fired. For example, if you wanted to have a save/cancel modal that you did some form validation on before saving you could do something like the example below.&lt;br /&gt;
&lt;br /&gt;
Example 4&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/modal_factory&#039;, &#039;core/modal_events&#039;, &#039;core/templates&#039;],&lt;br /&gt;
        function($, ModalFactory, ModalEvents, Templates) {&lt;br /&gt;
  &lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    type: ModalFactory.types.SAVE_CANCEL,&lt;br /&gt;
    title: &#039;Modal save cancel&#039;,&lt;br /&gt;
    body: &#039;This modal is a save/cancel modal&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    modal.getRoot().on(ModalEvents.save, function(e) {&lt;br /&gt;
      // Stop the default save button behaviour which is to close the modal.&lt;br /&gt;
      e.preventDefault();&lt;br /&gt;
      // Do your form validation here.&lt;br /&gt;
    });&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= How do you write a new type of modal? =&lt;br /&gt;
If you&#039;d like to write a new type of modal to be re-used throughout your code you can extend the default modal implementation and make any customisations you require. In order to create a new modal type you&#039;ll need to create a new AMD module and import the core/modal module to extend. You can also optionally create a new modal template that builds upon the core/modal template. Finally, you can register your new type with the modal registry to allow you to create your new type using the modal factory.&lt;br /&gt;
&lt;br /&gt;
For example, let&#039;s create a modal that opens a login form:&lt;br /&gt;
First we can create the HTML for out modal by including and extending the core modal template. All we need to do is override the block defined in that template with our new title, body and footer content.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume the file is &amp;lt;your_module&amp;gt;/templates/modal_login.mustache&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{&amp;lt; core/modal }}&lt;br /&gt;
    {{$title}}{{#str}} login {{/str}}{{/title}}&lt;br /&gt;
    {{$body}}&lt;br /&gt;
        &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;form&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;form-group row&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label for=&amp;quot;inputEmail&amp;quot; class=&amp;quot;col-sm-2 col-form-label&amp;quot;&amp;gt;{{#str}} email {{/str}}&amp;lt;/label&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;col-sm-10&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=&amp;quot;email&amp;quot; class=&amp;quot;form-control&amp;quot; id=&amp;quot;inputEmail&amp;quot; placeholder=&amp;quot;{{#str}} email {{/str}}&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;form-group row&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label for=&amp;quot;inputPassword&amp;quot; class=&amp;quot;col-sm-2 col-form-label&amp;quot;&amp;gt;{{#str}} password {{/str}}&amp;lt;/label&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;col-sm-10&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=&amp;quot;password&amp;quot; class=&amp;quot;form-control&amp;quot; id=&amp;quot;inputPassword&amp;quot; placeholder=&amp;quot;{{#str}} password {{/str}}&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;/form&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    {{/body}}&lt;br /&gt;
    {{$footer}}&lt;br /&gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;btn btn-primary&amp;quot; data-action=&amp;quot;login&amp;quot;&amp;gt;{{#str}} login {{/str}}&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;btn btn-secondary&amp;quot; data-action=&amp;quot;cancel&amp;quot;&amp;gt;{{#str}} cancel {{/str}}&amp;lt;/button&amp;gt;&lt;br /&gt;
    {{/footer}}&lt;br /&gt;
{{/ core/modal }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we can create the new AMD module for the login modal. You can extend the existing modal module which will give you all of the core modal functionality for free allowing you to focus on writing only the specific logic you need. In this example we would only need to write the logic for handling the login.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume the file is &amp;lt;your_module&amp;gt;/amd/src/modal_login.js&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/notification&#039;, &#039;core/custom_interaction_events&#039;, &#039;core/modal&#039;, &#039;core/modal_registry&#039;],&lt;br /&gt;
        function($, Notification, CustomEvents, Modal, ModalRegistry) {&lt;br /&gt;
&lt;br /&gt;
    var registered = false;&lt;br /&gt;
    var SELECTORS = {&lt;br /&gt;
        LOGIN_BUTTON: &#039;[data-action=&amp;quot;login&amp;quot;]&#039;,&lt;br /&gt;
        CANCEL_BUTTON: &#039;[data-action=&amp;quot;cancel&amp;quot;]&#039;,&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Constructor for the Modal.&lt;br /&gt;
     *&lt;br /&gt;
     * @param {object} root The root jQuery element for the modal&lt;br /&gt;
     */&lt;br /&gt;
    var ModalLogin = function(root) {&lt;br /&gt;
        Modal.call(this, root);&lt;br /&gt;
&lt;br /&gt;
        if (!this.getFooter().find(SELECTORS.LOGIN_BUTTON).length) {&lt;br /&gt;
            Notification.exception({message: &#039;No login button found&#039;});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!this.getFooter().find(SELECTORS.CANCEL_BUTTON).length) {&lt;br /&gt;
            Notification.exception({message: &#039;No cancel button found&#039;});&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    ModalLogin.TYPE = &#039;your_module-login&#039;;&lt;br /&gt;
    ModalLogin.prototype = Object.create(Modal.prototype);&lt;br /&gt;
    ModalLogin.prototype.constructor = ModalLogin;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Set up all of the event handling for the modal.&lt;br /&gt;
     *&lt;br /&gt;
     * @method registerEventListeners&lt;br /&gt;
     */&lt;br /&gt;
    ModalLogin.prototype.registerEventListeners = function() {&lt;br /&gt;
        // Apply parent event listeners.&lt;br /&gt;
        Modal.prototype.registerEventListeners.call(this);&lt;br /&gt;
&lt;br /&gt;
        this.getModal().on(CustomEvents.events.activate, SELECTORS.LOGIN_BUTTON, function(e, data) {&lt;br /&gt;
            // Add your logic for when the login button is clicked. This could include the form validation,&lt;br /&gt;
            // loading animations, error handling etc.&lt;br /&gt;
        }.bind(this));&lt;br /&gt;
&lt;br /&gt;
        this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, function(e, data) {&lt;br /&gt;
            // Add your logic for when the cancel button is clicked.&lt;br /&gt;
        }.bind(this));&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    // Automatically register with the modal registry the first time this module is imported so that you can create modals&lt;br /&gt;
    // of this type using the modal factory.&lt;br /&gt;
    if (!registered) {&lt;br /&gt;
        ModalRegistry.register(ModalLogin.TYPE, ModalLogin, &#039;&amp;lt;your_module&amp;gt;/modal_login&#039;);&lt;br /&gt;
        registered = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return ModalLogin;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you have both your template and javascript ready to go then all you need to do is tie them into where ever you&#039;d like to launch the modal. For the purpose of this example let&#039;s assume that we&#039;ve got a page with a login button with id=&amp;quot;login&amp;quot; then you might add this javascript to the page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/templates&#039;, &#039;core/modal_factory&#039;, &#039;&amp;lt;your_module&amp;gt;/modal_login&#039;], function($, Templates, ModalFactory, ModalLogin) {&lt;br /&gt;
    var trigger = $(&#039;#login&#039;);&lt;br /&gt;
&lt;br /&gt;
    ModalFactory.create({type: ModalLogin.TYPE}, trigger); &lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Related =&lt;br /&gt;
* AMD and [[Javascript Modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=AJAX&amp;diff=53766</id>
		<title>AJAX</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=AJAX&amp;diff=53766"/>
		<updated>2018-03-03T20:08:38Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Ajax in Moodle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;AJAX (Asynchronous Javascript and XML)&#039;&#039;&#039; is a modern web design technique that allows for more interactivity by making webpages that fetch data in the background and alter themselves without reloading the entire page. This helps to make a page feel much more like an application than a web page. AJAX is a new way of working with existing technologies (including HTML, [[Javascript]], [[CSS]] and the &#039;&#039;XMLHttpRequest object&#039;&#039; amongst others) rather than a new piece of technology in itself.&lt;br /&gt;
&lt;br /&gt;
Although AJAX indicates that XML is used, the term really relates to the group of technologies and in Moodle we tend to favour use of JSON rather than XML as the syntax is lighter and leads to a smaller output. It is also easier to construct from php data structures.&lt;br /&gt;
&lt;br /&gt;
== Ajax in Moodle ==&lt;br /&gt;
{{ Moodle 2.9 }}&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. &lt;br /&gt;
&lt;br /&gt;
Some benefits of this system are: &lt;br /&gt;
# No new ajax scripts need auditing for security vulnerabilities&lt;br /&gt;
# Multiple requests can be chained in a single http request&lt;br /&gt;
# Strict type checking for all parameters and return types&lt;br /&gt;
# New webservice functions benefit Ajax interfaces and web service clients&lt;br /&gt;
&lt;br /&gt;
So the steps required to create an ajax interaction are:&lt;br /&gt;
&lt;br /&gt;
# Write or find an existing web service function to handle the ajax interaction: See [[ Web_services ]]&lt;br /&gt;
# White list the web service for ajax. To do this, you can define &#039;ajax&#039; =&amp;gt; true in your function&#039;s definition, in db/services.php. Only functions that are whitelisted using this mechanism will be available to the ajax script.&lt;br /&gt;
# Call the web service from javascript in response to a user action:&lt;br /&gt;
&lt;br /&gt;
Example calling core_get_string:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/ajax&#039;], function(ajax) {&lt;br /&gt;
    var promises = ajax.call([&lt;br /&gt;
        { methodname: &#039;core_get_string&#039;, args: { component: &#039;mod_wiki&#039;, stringid: &#039;pluginname&#039; } },&lt;br /&gt;
        { methodname: &#039;core_get_string&#039;, args: { component: &#039;mod_wiki&#039;, stringid: &#039;changerate&#039; } }&lt;br /&gt;
    ]);&lt;br /&gt;
&lt;br /&gt;
   promises[0].done(function(response) {&lt;br /&gt;
       console.log(&#039;mod_wiki/pluginname is&#039; + response);&lt;br /&gt;
   }).fail(function(ex) {&lt;br /&gt;
       // do something with the exception&lt;br /&gt;
   });&lt;br /&gt;
&lt;br /&gt;
   promises[1].done(function(response) {&lt;br /&gt;
       console.log(&#039;mod_wiki/changerate is&#039; + response);&lt;br /&gt;
   }).fail(function(ex) {&lt;br /&gt;
       // do something with the exception&lt;br /&gt;
   });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: This example chains to separate calls to the core_get_string webservice in one http request&lt;br /&gt;
&lt;br /&gt;
Note: Don&#039;t actually fetch strings like this, it is just an example, use the &#039;core/str&#039; module instead.&lt;br /&gt;
&lt;br /&gt;
If there is only  a single action, a simpler form is possible (example from Assignment):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
        ajax.call([{&lt;br /&gt;
            methodname: &#039;mod_assign_submit_grading_form&#039;,&lt;br /&gt;
            args: {assignmentid: assignmentid, userid: this._lastUserId, jsonformdata: JSON.stringify(data)},&lt;br /&gt;
            done: this._handleFormSubmissionResponse.bind(this, data, nextUserId),&lt;br /&gt;
            fail: notification.exception&lt;br /&gt;
        }]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(&#039;notifcation&#039; comes from the &#039;core/notification&#039; javascript module and provides a useful popup error message if the call fails)&lt;br /&gt;
&lt;br /&gt;
To update parts of the UI in response to Ajax changes, consider using [[ Templates ]]&lt;br /&gt;
&lt;br /&gt;
For information on writing AJAX scripts for Moodle before Moodle 2.9 see: [[ AJAX pre 2.9 ]]&lt;br /&gt;
&lt;br /&gt;
Watch a video about using templates with webservices and AJAX in Moodle: https://www.youtube.com/watch?v=UTePjRZqAg8&lt;br /&gt;
&lt;br /&gt;
Tricky things to know about using webservices with ajax calls:&lt;br /&gt;
# Any call to $PAGE-&amp;gt;get_renderer() requires the correct theme be set. If this is done in a webservice - it is likely that the theme needs to be a parameter to the webservice.&lt;br /&gt;
# Text returned from a webservice must be properly filtered. This means it must go through external_format_text or external_format_string (since 3.0 - see MDL-51213) with the correct context.&lt;br /&gt;
# The correct context for 2 is the most specific context relating to the thing being output e.g. for a user&#039;s profile desciption the context is the user context.&lt;br /&gt;
# After adding any dynamic content to a page, Moodle&#039;s filters need to be notified via M.core.event.FILTER_CONTENT_UPDATED (MDL-51222 makes this easier)&lt;br /&gt;
# After adding or changing any webservice definition in db/services.php - you must bump the version number for either the plugin or Moodle and run the upgrade. This will install the webservice in the DB tables so it can be found by ajax.&lt;br /&gt;
&lt;br /&gt;
In some very rare cases - you can mark webservices as safe to call without a session. These should only be used for webservices that return 100% public information and do not consume many resources. A current example is core_get_string. To mark a webservice as safe to call without a session you need to do 2 things. &lt;br /&gt;
# Add &#039;loginrequired&#039; =&amp;gt; false to the service definition in db/services.php&lt;br /&gt;
# Pass &amp;quot;false&amp;quot; as the 3rd argument to the ajax &amp;quot;call&amp;quot; method when calling the webservice. &lt;br /&gt;
The benefit to marking these safe webservice is that (a) they can be called from the login page before we have a session and (b) they will perform faster because they will bypass moodles session code when responding to the webservice call.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[ AJAX pre 2.9 ]]&lt;br /&gt;
* [[Templates]]&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Javascript FAQ]]&lt;br /&gt;
* [[Javascript]]&lt;br /&gt;
* [[Firebug#Debugging_AJAX_with_Firebug|Debugging AJAX with Firebug]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* [http://www.adaptivepath.com/publications/essays/archives/000385.php &#039;&#039;Ajax: A New Approach to Web Applications&#039;&#039;, the original Ajax article by Adaptive Path] (This link is now dead, but the article is preserved on the  [https://web.archive.org/web/20070225140912/http://www.adaptivepath.com/publications/essays/archives/000385.php Internet Archive])&lt;br /&gt;
* [http://developer.mozilla.org/en/docs/AJAX:Getting_Started &#039;&#039;AJAX: Getting Started&#039;&#039; article on developer.mozilla.org]&lt;br /&gt;
* [http://www.sourcelabs.com/blogs/ajb/2005/12/10_places_you_must_use_ajax.html &#039;&#039;10 places you must use AJAX&#039;&#039; by Adam Bosworth] (Also a dead link...  [https://web.archive.org/web/20060127015713/http://www.sourcelabs.com/blogs/ajb/2005/12/10_places_you_must_use_ajax.html Internet Archive copy])&lt;br /&gt;
* [http://www-128.ibm.com/developerworks/web/library/wa-ajaxtop1/?ca=dgr-lnxw01AjaxHype &#039;&#039;Considering Ajax, Part 1: Cut through the hype&#039;&#039; from IBM developerworks] (Also a dead link... [https://web.archive.org/web/20080602101238/http://www-128.ibm.com/developerworks/web/library/wa-ajaxtop1/?ca=dgr-lnxw01AjaxHype Internet Archive copy])&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Ajax_%28programming%29 Wikipedia article on &#039;&#039;AJAX&#039;&#039;]&lt;br /&gt;
* [http://www.maxkiesler.com/index.php/weblog/comments/how_to_make_your_ajax_applications_accessible/ How to Make Your AJAX Applications Accessible: 40 Tutorials and Articles] (Also a dead link... [https://web.archive.org/web/20090225094656/http://www.maxkiesler.com/index.php/weblog/comments/how_to_make_your_ajax_applications_accessible/ Internet Archive copy])&lt;br /&gt;
*[http://www.ajaxload.info/ AJAX loading icon generator]&lt;br /&gt;
&lt;br /&gt;
[[Category:Javascript]]&lt;br /&gt;
[[Category:AJAX]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=AJAX&amp;diff=53765</id>
		<title>AJAX</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=AJAX&amp;diff=53765"/>
		<updated>2018-03-03T20:07:52Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Ajax in Moodle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;AJAX (Asynchronous Javascript and XML)&#039;&#039;&#039; is a modern web design technique that allows for more interactivity by making webpages that fetch data in the background and alter themselves without reloading the entire page. This helps to make a page feel much more like an application than a web page. AJAX is a new way of working with existing technologies (including HTML, [[Javascript]], [[CSS]] and the &#039;&#039;XMLHttpRequest object&#039;&#039; amongst others) rather than a new piece of technology in itself.&lt;br /&gt;
&lt;br /&gt;
Although AJAX indicates that XML is used, the term really relates to the group of technologies and in Moodle we tend to favour use of JSON rather than XML as the syntax is lighter and leads to a smaller output. It is also easier to construct from php data structures.&lt;br /&gt;
&lt;br /&gt;
== Ajax in Moodle ==&lt;br /&gt;
{{ Moodle 2.9 }}&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. &lt;br /&gt;
&lt;br /&gt;
Some benefits of this system are: &lt;br /&gt;
# No new ajax scripts need auditing for security vulnerabilities&lt;br /&gt;
# Multiple requests can be chained in a single http request&lt;br /&gt;
# Strict type checking for all parameters and return types&lt;br /&gt;
# New webservice functions benefit Ajax interfaces and web service clients&lt;br /&gt;
&lt;br /&gt;
So the steps required to create an ajax interaction are:&lt;br /&gt;
&lt;br /&gt;
# Write or find an existing web service function to handle the ajax interaction: See [[ Web_services ]]&lt;br /&gt;
# White list the web service for ajax. To do this, you can define &#039;ajax&#039; =&amp;gt; true in your function&#039;s definition, in db/services.php. Only functions that are whitelisted using this mechanism will be available to the ajax script.&lt;br /&gt;
# Call the web service from javascript in response to a user action:&lt;br /&gt;
&lt;br /&gt;
Example calling core_get_string:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/ajax&#039;], function(ajax) {&lt;br /&gt;
    var promises = ajax.call([&lt;br /&gt;
        { methodname: &#039;core_get_string&#039;, args: { component: &#039;mod_wiki&#039;, stringid: &#039;pluginname&#039; } },&lt;br /&gt;
        { methodname: &#039;core_get_string&#039;, args: { component: &#039;mod_wiki&#039;, stringid: &#039;changerate&#039; } }&lt;br /&gt;
    ]);&lt;br /&gt;
&lt;br /&gt;
   promises[0].done(function(response) {&lt;br /&gt;
       console.log(&#039;mod_wiki/pluginname is&#039; + response);&lt;br /&gt;
   }).fail(function(ex) {&lt;br /&gt;
       // do something with the exception&lt;br /&gt;
   });&lt;br /&gt;
&lt;br /&gt;
   promises[1].done(function(response) {&lt;br /&gt;
       console.log(&#039;mod_wiki/changerate is&#039; + response);&lt;br /&gt;
   }).fail(function(ex) {&lt;br /&gt;
       // do something with the exception&lt;br /&gt;
   });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: This example chains to separate calls to the core_get_string webservice in one http request&lt;br /&gt;
&lt;br /&gt;
Note: Don&#039;t actually fetch strings like this, it is just an example, use the &#039;core/str&#039; module instead.&lt;br /&gt;
&lt;br /&gt;
If there is only  a single action, a simpler form is possible:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
        ajax.call([{&lt;br /&gt;
            methodname: &#039;mod_assign_submit_grading_form&#039;,&lt;br /&gt;
            args: {assignmentid: assignmentid, userid: this._lastUserId, jsonformdata: JSON.stringify(data)},&lt;br /&gt;
            done: this._handleFormSubmissionResponse.bind(this, data, nextUserId),&lt;br /&gt;
            fail: notification.exception&lt;br /&gt;
        }]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(&#039;notifcation&#039; comes from the &#039;core/notification&#039; javascript module and provides a useful popup error message if the call fails)&lt;br /&gt;
&lt;br /&gt;
To update parts of the UI in response to Ajax changes, consider using [[ Templates ]]&lt;br /&gt;
&lt;br /&gt;
For information on writing AJAX scripts for Moodle before Moodle 2.9 see: [[ AJAX pre 2.9 ]]&lt;br /&gt;
&lt;br /&gt;
Watch a video about using templates with webservices and AJAX in Moodle: https://www.youtube.com/watch?v=UTePjRZqAg8&lt;br /&gt;
&lt;br /&gt;
Tricky things to know about using webservices with ajax calls:&lt;br /&gt;
# Any call to $PAGE-&amp;gt;get_renderer() requires the correct theme be set. If this is done in a webservice - it is likely that the theme needs to be a parameter to the webservice.&lt;br /&gt;
# Text returned from a webservice must be properly filtered. This means it must go through external_format_text or external_format_string (since 3.0 - see MDL-51213) with the correct context.&lt;br /&gt;
# The correct context for 2 is the most specific context relating to the thing being output e.g. for a user&#039;s profile desciption the context is the user context.&lt;br /&gt;
# After adding any dynamic content to a page, Moodle&#039;s filters need to be notified via M.core.event.FILTER_CONTENT_UPDATED (MDL-51222 makes this easier)&lt;br /&gt;
# After adding or changing any webservice definition in db/services.php - you must bump the version number for either the plugin or Moodle and run the upgrade. This will install the webservice in the DB tables so it can be found by ajax.&lt;br /&gt;
&lt;br /&gt;
In some very rare cases - you can mark webservices as safe to call without a session. These should only be used for webservices that return 100% public information and do not consume many resources. A current example is core_get_string. To mark a webservice as safe to call without a session you need to do 2 things. &lt;br /&gt;
# Add &#039;loginrequired&#039; =&amp;gt; false to the service definition in db/services.php&lt;br /&gt;
# Pass &amp;quot;false&amp;quot; as the 3rd argument to the ajax &amp;quot;call&amp;quot; method when calling the webservice. &lt;br /&gt;
The benefit to marking these safe webservice is that (a) they can be called from the login page before we have a session and (b) they will perform faster because they will bypass moodles session code when responding to the webservice call.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[ AJAX pre 2.9 ]]&lt;br /&gt;
* [[Templates]]&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Javascript FAQ]]&lt;br /&gt;
* [[Javascript]]&lt;br /&gt;
* [[Firebug#Debugging_AJAX_with_Firebug|Debugging AJAX with Firebug]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* [http://www.adaptivepath.com/publications/essays/archives/000385.php &#039;&#039;Ajax: A New Approach to Web Applications&#039;&#039;, the original Ajax article by Adaptive Path] (This link is now dead, but the article is preserved on the  [https://web.archive.org/web/20070225140912/http://www.adaptivepath.com/publications/essays/archives/000385.php Internet Archive])&lt;br /&gt;
* [http://developer.mozilla.org/en/docs/AJAX:Getting_Started &#039;&#039;AJAX: Getting Started&#039;&#039; article on developer.mozilla.org]&lt;br /&gt;
* [http://www.sourcelabs.com/blogs/ajb/2005/12/10_places_you_must_use_ajax.html &#039;&#039;10 places you must use AJAX&#039;&#039; by Adam Bosworth] (Also a dead link...  [https://web.archive.org/web/20060127015713/http://www.sourcelabs.com/blogs/ajb/2005/12/10_places_you_must_use_ajax.html Internet Archive copy])&lt;br /&gt;
* [http://www-128.ibm.com/developerworks/web/library/wa-ajaxtop1/?ca=dgr-lnxw01AjaxHype &#039;&#039;Considering Ajax, Part 1: Cut through the hype&#039;&#039; from IBM developerworks] (Also a dead link... [https://web.archive.org/web/20080602101238/http://www-128.ibm.com/developerworks/web/library/wa-ajaxtop1/?ca=dgr-lnxw01AjaxHype Internet Archive copy])&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Ajax_%28programming%29 Wikipedia article on &#039;&#039;AJAX&#039;&#039;]&lt;br /&gt;
* [http://www.maxkiesler.com/index.php/weblog/comments/how_to_make_your_ajax_applications_accessible/ How to Make Your AJAX Applications Accessible: 40 Tutorials and Articles] (Also a dead link... [https://web.archive.org/web/20090225094656/http://www.maxkiesler.com/index.php/weblog/comments/how_to_make_your_ajax_applications_accessible/ Internet Archive copy])&lt;br /&gt;
*[http://www.ajaxload.info/ AJAX loading icon generator]&lt;br /&gt;
&lt;br /&gt;
[[Category:Javascript]]&lt;br /&gt;
[[Category:AJAX]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=53764</id>
		<title>Useful core Javascript modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=53764"/>
		<updated>2018-03-03T17:26:28Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
The source code for all these modules (and a number of others) can be found in lib/amd/src. &lt;br /&gt;
&lt;br /&gt;
== Configuration settings (core/config) ==&lt;br /&gt;
&lt;br /&gt;
Example of using config module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/config’], function(mdlcfg) {&lt;br /&gt;
    console.log(mdlcfg.wwwroot); // outputs the wwwroot of moodle to console&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Language strings (core/str) ==&lt;br /&gt;
&lt;br /&gt;
Example of using language strings module (retrieved via ajax, if the string is not yet loaded)&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([‘core/str’], function(str) {&lt;br /&gt;
    // start retrieving the localized string; store the promise that some time in the future the string will be there.&lt;br /&gt;
    var editaPresent = str.get_string(&#039;edita&#039;, &#039;core&#039;, stringargument);&lt;br /&gt;
    // as soon as the string is retrieved, i.e. the promise has been fulfilled,&lt;br /&gt;
    // edit the text of a UI element so that it then is the localized string&lt;br /&gt;
    // Note: $.when can be used with an arbitrary number of promised things&lt;br /&gt;
    $.when(editaPresent).done(function(localizedEditString) {&lt;br /&gt;
         $(&amp;quot;someUIElementSelector&amp;quot;).text = localizedEditString;&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string will be retrieved via AJAX request on the first use and cached in browser local storage until purge caches or site upgrade&lt;br /&gt;
&lt;br /&gt;
IT IS NOT RECOMMENDED to call:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;string_for_js(&#039;edita&#039;, &#039;core&#039;); // You do not need this!&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because:&lt;br /&gt;
* The list of strings used now needs to be maintained in 2 places&lt;br /&gt;
* The strings are always sent, and bloat the page size even if they are not used&lt;br /&gt;
* All strings fetched via the AJAX method above are cached in browser local storage anyway, so these strings will never be used 99% of the time&lt;br /&gt;
&lt;br /&gt;
==Notifications (core/notification)==&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/notification’], function(notification) {&lt;br /&gt;
    notification.alert(&#039;Hello&#039;, &#039;Welcome to my site!&#039;, &#039;Continue&#039;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Strings and notifications:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/str&#039;, &#039;core/notification’], function(str, notification) {&lt;br /&gt;
                str.get_strings([&lt;br /&gt;
                        {&#039;key&#039; : &#039;delete&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;confirmdeletetag&#039;, component : &#039;tag&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;yes&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;no&#039;},&lt;br /&gt;
                    ]).done(function(s) {&lt;br /&gt;
                        notification.confirm(s[0], s[1], s[2], s[3], function() {&lt;br /&gt;
                            window.location.href = href;&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                ).fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== URL module (core/url) ==&lt;br /&gt;
&lt;br /&gt;
Useful Functions:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Generate an absolute URL by using the relative path to a file and optionally slash arguments&lt;br /&gt;
url.fileUrl(relativeScript, slashArgs)&lt;br /&gt;
// Generate an absolute URL from the given relative path, include the URL parameters and possibly the current session key&lt;br /&gt;
url.relativeUrl(relativePath, params, includeSessionKey)&lt;br /&gt;
// Generates an image url using the filename of the image and the Moodle component (core, plugin, etc.) where it can be found&lt;br /&gt;
url.imageUrl(imagename, component)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Prerequisites:&lt;br /&gt;
// - A Javascript file is present under $CFG-&amp;gt;wwwroot.&amp;quot;/path/to/file.js&amp;quot;&lt;br /&gt;
require([&#039;core/url&#039;], function(url) {&lt;br /&gt;
    url.fileUrl(&amp;quot;/path/to/file.js&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
    console.log(&amp;quot;Generated URL: &amp;quot; + url);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ajax Module (core/ajax) ==&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. This saves having to worry about security in ajax functionality because it&#039;s all done for you. &lt;br /&gt;
&lt;br /&gt;
For the full story, see [[AJAX]]&lt;br /&gt;
&lt;br /&gt;
== Tree (core/tree) ==&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
If you want to add a hierarchical interface, you should use the tree module. It will handle user interaction and accessibility for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/tree&#039;], function($, Tree) {&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
            new Tree(&amp;quot;css/jquery selector of the tree container&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[Tree]] module&lt;br /&gt;
&lt;br /&gt;
== Modal (core/modal) ==&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
If you&#039;d like to add a modal to your page the modal modules will handle all of the magic for you, all you need to bring is your content!&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_Modal]] module&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=53763</id>
		<title>Useful core Javascript modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=53763"/>
		<updated>2018-03-03T17:05:42Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* URL module (core/url) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
== Configuration settings (core/config) ==&lt;br /&gt;
&lt;br /&gt;
Example of using config module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/config’], function(mdlcfg) {&lt;br /&gt;
    console.log(mdlcfg.wwwroot); // outputs the wwwroot of moodle to console&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Language strings (core/str) ==&lt;br /&gt;
&lt;br /&gt;
Example of using language strings module (retrieved via ajax, if the string is not yet loaded)&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([‘core/str’], function(str) {&lt;br /&gt;
    // start retrieving the localized string; store the promise that some time in the future the string will be there.&lt;br /&gt;
    var editaPresent = str.get_string(&#039;edita&#039;, &#039;core&#039;, stringargument);&lt;br /&gt;
    // as soon as the string is retrieved, i.e. the promise has been fulfilled,&lt;br /&gt;
    // edit the text of a UI element so that it then is the localized string&lt;br /&gt;
    // Note: $.when can be used with an arbitrary number of promised things&lt;br /&gt;
    $.when(editaPresent).done(function(localizedEditString) {&lt;br /&gt;
         $(&amp;quot;someUIElementSelector&amp;quot;).text = localizedEditString;&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string will be retrieved via AJAX request on the first use and cached in browser local storage until purge caches or site upgrade&lt;br /&gt;
&lt;br /&gt;
IT IS NOT RECOMMENDED to call:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;string_for_js(&#039;edita&#039;, &#039;core&#039;); // You do not need this!&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because:&lt;br /&gt;
* The list of strings used now needs to be maintained in 2 places&lt;br /&gt;
* The strings are always sent, and bloat the page size even if they are not used&lt;br /&gt;
* All strings fetched via the AJAX method above are cached in browser local storage anyway, so these strings will never be used 99% of the time&lt;br /&gt;
&lt;br /&gt;
==Notifications (core/notification)==&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/notification’], function(notification) {&lt;br /&gt;
    notification.alert(&#039;Hello&#039;, &#039;Welcome to my site!&#039;, &#039;Continue&#039;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Strings and notifications:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/str&#039;, &#039;core/notification’], function(str, notification) {&lt;br /&gt;
                str.get_strings([&lt;br /&gt;
                        {&#039;key&#039; : &#039;delete&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;confirmdeletetag&#039;, component : &#039;tag&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;yes&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;no&#039;},&lt;br /&gt;
                    ]).done(function(s) {&lt;br /&gt;
                        notification.confirm(s[0], s[1], s[2], s[3], function() {&lt;br /&gt;
                            window.location.href = href;&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                ).fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== URL module (core/url) ==&lt;br /&gt;
&lt;br /&gt;
Useful Functions:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Generate an absolute URL by using the relative path to a file and optionally slash arguments&lt;br /&gt;
url.fileUrl(relativeScript, slashArgs)&lt;br /&gt;
// Generate an absolute URL from the given relative path, include the URL parameters and possibly the current session key&lt;br /&gt;
url.relativeUrl(relativePath, params, includeSessionKey)&lt;br /&gt;
// Generates an image url using the filename of the image and the Moodle component (core, plugin, etc.) where it can be found&lt;br /&gt;
url.imageUrl(imagename, component)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Prerequisites:&lt;br /&gt;
// - A Javascript file is present under $CFG-&amp;gt;wwwroot.&amp;quot;/path/to/file.js&amp;quot;&lt;br /&gt;
require([&#039;core/url&#039;], function(url) {&lt;br /&gt;
    url.fileUrl(&amp;quot;/path/to/file.js&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
    console.log(&amp;quot;Generated URL: &amp;quot; + url);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ajax Module (core/ajax) ==&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. This saves having to worry about security in ajax functionality because it&#039;s all done for you. &lt;br /&gt;
&lt;br /&gt;
For the full story, see [[AJAX]]&lt;br /&gt;
&lt;br /&gt;
== Tree (core/tree) ==&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
If you want to add a hierarchical interface, you should use the tree module. It will handle user interaction and accessibility for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/tree&#039;], function($, Tree) {&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
            new Tree(&amp;quot;css/jquery selector of the tree container&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[Tree]] module&lt;br /&gt;
&lt;br /&gt;
== Modal (core/modal) ==&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
If you&#039;d like to add a modal to your page the modal modules will handle all of the magic for you, all you need to bring is your content!&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_Modal]] module&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=53762</id>
		<title>Useful core Javascript modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Useful_core_Javascript_modules&amp;diff=53762"/>
		<updated>2018-03-03T17:05:24Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* URL module (core/url) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
== Configuration settings (core/config) ==&lt;br /&gt;
&lt;br /&gt;
Example of using config module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/config’], function(mdlcfg) {&lt;br /&gt;
    console.log(mdlcfg.wwwroot); // outputs the wwwroot of moodle to console&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Language strings (core/str) ==&lt;br /&gt;
&lt;br /&gt;
Example of using language strings module (retrieved via ajax, if the string is not yet loaded)&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([‘core/str’], function(str) {&lt;br /&gt;
    // start retrieving the localized string; store the promise that some time in the future the string will be there.&lt;br /&gt;
    var editaPresent = str.get_string(&#039;edita&#039;, &#039;core&#039;, stringargument);&lt;br /&gt;
    // as soon as the string is retrieved, i.e. the promise has been fulfilled,&lt;br /&gt;
    // edit the text of a UI element so that it then is the localized string&lt;br /&gt;
    // Note: $.when can be used with an arbitrary number of promised things&lt;br /&gt;
    $.when(editaPresent).done(function(localizedEditString) {&lt;br /&gt;
         $(&amp;quot;someUIElementSelector&amp;quot;).text = localizedEditString;&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string will be retrieved via AJAX request on the first use and cached in browser local storage until purge caches or site upgrade&lt;br /&gt;
&lt;br /&gt;
IT IS NOT RECOMMENDED to call:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;string_for_js(&#039;edita&#039;, &#039;core&#039;); // You do not need this!&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because:&lt;br /&gt;
* The list of strings used now needs to be maintained in 2 places&lt;br /&gt;
* The strings are always sent, and bloat the page size even if they are not used&lt;br /&gt;
* All strings fetched via the AJAX method above are cached in browser local storage anyway, so these strings will never be used 99% of the time&lt;br /&gt;
&lt;br /&gt;
==Notifications (core/notification)==&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/notification’], function(notification) {&lt;br /&gt;
    notification.alert(&#039;Hello&#039;, &#039;Welcome to my site!&#039;, &#039;Continue&#039;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Strings and notifications:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/str&#039;, &#039;core/notification’], function(str, notification) {&lt;br /&gt;
                str.get_strings([&lt;br /&gt;
                        {&#039;key&#039; : &#039;delete&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;confirmdeletetag&#039;, component : &#039;tag&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;yes&#039;},&lt;br /&gt;
                        {&#039;key&#039; : &#039;no&#039;},&lt;br /&gt;
                    ]).done(function(s) {&lt;br /&gt;
                        notification.confirm(s[0], s[1], s[2], s[3], function() {&lt;br /&gt;
                            window.location.href = href;&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                ).fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== URL module (core/url) ==&lt;br /&gt;
&lt;br /&gt;
Useful Functions:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Generate an absolute URL by using the relative path to a file and optionally slash arguments&lt;br /&gt;
url.fileUrl(relativeScript, slashArgs)&lt;br /&gt;
// Generate an absolute URL from the given relative path, include the URL parameters and possibly the current session key&lt;br /&gt;
url.relativeUrl(relativePath, params, includeSessionKey)&lt;br /&gt;
// Generates an image url using the filename of the image and the Moodle component (core, plugin, etc.) where it can be found&lt;br /&gt;
url.imageUrl(imagename, component)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Prerequisites:&lt;br /&gt;
// - A Javascript file is present under $CFG-&amp;gt;wwwroot.&amp;quot;/path/to/file.js&amp;quot;&lt;br /&gt;
require([&#039;core/url&#039;], function(url) {&lt;br /&gt;
    url.fileUrl(&amp;quot;/path/to/file.js&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
    console.log(&amp;quot;Generated URL: &amp;quot; + url);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Ajax Module (core/ajax) ===&lt;br /&gt;
&lt;br /&gt;
The preferred way to write new ajax interactions in Moodle is to use the javascript module &amp;quot;core/ajax&amp;quot; which can directly call existing web service functions. This saves having to worry about security in ajax functionality because it&#039;s all done for you. &lt;br /&gt;
&lt;br /&gt;
For the full story, see [[AJAX]]&lt;br /&gt;
&lt;br /&gt;
== Tree (core/tree) ==&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
If you want to add a hierarchical interface, you should use the tree module. It will handle user interaction and accessibility for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;, &#039;core/tree&#039;], function($, Tree) {&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
            new Tree(&amp;quot;css/jquery selector of the tree container&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[Tree]] module&lt;br /&gt;
&lt;br /&gt;
== Modal (core/modal) ==&lt;br /&gt;
{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
If you&#039;d like to add a modal to your page the modal modules will handle all of the magic for you, all you need to bring is your content!&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;jquery&#039;, &#039;core/modal_factory&#039;], function($, ModalFactory) {&lt;br /&gt;
  var trigger = $(&#039;#create-modal&#039;);&lt;br /&gt;
  ModalFactory.create({&lt;br /&gt;
    title: &#039;test title&#039;,&lt;br /&gt;
    body: &#039;&amp;lt;p&amp;gt;test body content&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
    footer: &#039;test footer content&#039;,&lt;br /&gt;
  }, trigger)&lt;br /&gt;
  .done(function(modal) {&lt;br /&gt;
    // Do what you want with your new modal.&lt;br /&gt;
  });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read more about the [[AMD_Modal]] module&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Javascript Modules]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53761</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53761"/>
		<updated>2018-03-03T16:42:04Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Minimum (getting started) module for plugins */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/. Moodle currently requires node &amp;quot;8.9.x&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Development mode ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing. &lt;br /&gt;
Or, &amp;quot;No define call for theme_XXX/loader&amp;quot; as you are probably missing the &#039;src&#039; folder with relevant JS files. which might happen when you turn debugging ON on a theme that was bought, without &#039;src&#039; folder :-(&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
Note: Once you have run grunt and built your code, you will then need to purge Moodle caches otherwise the changes made to your minified files may not be picked up by Moodle.&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
&lt;br /&gt;
            // Put whatever you like here. $ is available&lt;br /&gt;
            // to you as normal.&lt;br /&gt;
            $(&amp;quot;.someclass&amp;quot;).change(function() {&lt;br /&gt;
                alert(&amp;quot;It changed!!&amp;quot;);&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;
This code passes the jquery module into our function (parameter $). There are a number of other useful modules available in Moodle, some of which you&#039;ll probably need in a practical application. See [[Useful_core_Javascript_modules]].&lt;br /&gt;
&lt;br /&gt;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;[[Frankenstyle]]&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    return {&lt;br /&gt;
        init: function(first, last) {&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&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;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
            return factory(a0);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($, c) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&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;
2. Put the javascript into a mustache template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Call the javascript directly from php (although who would want to put javascript into php? ergh):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* [https://assets.moodlemoot.org/sites/15/20171004085436/JavaScript-AMD-with-RequireJS-presented-by-Daniel-Roperto-Catalyst.pdf JavaScript AMD with RequireJS] presented by Daniel Roperto, Catalyst. (MoodleMOOT AU 2017)&lt;br /&gt;
* [[Useful_core_Javascript_modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53760</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53760"/>
		<updated>2018-03-03T16:41:42Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Minimum (getting started) module for plugins */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/. Moodle currently requires node &amp;quot;8.9.x&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Development mode ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing. &lt;br /&gt;
Or, &amp;quot;No define call for theme_XXX/loader&amp;quot; as you are probably missing the &#039;src&#039; folder with relevant JS files. which might happen when you turn debugging ON on a theme that was bought, without &#039;src&#039; folder :-(&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
Note: Once you have run grunt and built your code, you will then need to purge Moodle caches otherwise the changes made to your minified files may not be picked up by Moodle.&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
&lt;br /&gt;
            // Put whatever you like here. $ is available&lt;br /&gt;
            // to you as normal.&lt;br /&gt;
            $(&amp;quot;.someclass&amp;quot;).change(function() {&lt;br /&gt;
                alert(&amp;quot;It changed!!&amp;quot;);&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;
This code passes the jquery module into our function (parameter $). There are a number of other useful functions available in Moodle, some of which you&#039;ll probably need in a practical application. See [[Useful_core_Javascript_modules]].&lt;br /&gt;
&lt;br /&gt;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;[[Frankenstyle]]&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    return {&lt;br /&gt;
        init: function(first, last) {&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&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;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
            return factory(a0);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($, c) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&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;
2. Put the javascript into a mustache template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Call the javascript directly from php (although who would want to put javascript into php? ergh):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* [https://assets.moodlemoot.org/sites/15/20171004085436/JavaScript-AMD-with-RequireJS-presented-by-Daniel-Roperto-Catalyst.pdf JavaScript AMD with RequireJS] presented by Daniel Roperto, Catalyst. (MoodleMOOT AU 2017)&lt;br /&gt;
* [[Useful_core_Javascript_modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53759</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53759"/>
		<updated>2018-03-03T16:38:09Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Useful links */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/. Moodle currently requires node &amp;quot;8.9.x&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Development mode ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing. &lt;br /&gt;
Or, &amp;quot;No define call for theme_XXX/loader&amp;quot; as you are probably missing the &#039;src&#039; folder with relevant JS files. which might happen when you turn debugging ON on a theme that was bought, without &#039;src&#039; folder :-(&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
Note: Once you have run grunt and built your code, you will then need to purge Moodle caches otherwise the changes made to your minified files may not be picked up by Moodle.&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
&lt;br /&gt;
            // Put whatever you like here. $ is available&lt;br /&gt;
            // to you as normal.&lt;br /&gt;
            $(&amp;quot;.someclass&amp;quot;).change(function() {&lt;br /&gt;
                alert(&amp;quot;It changed!!&amp;quot;);&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;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;[[Frankenstyle]]&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    return {&lt;br /&gt;
        init: function(first, last) {&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&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;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
            return factory(a0);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($, c) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&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;
2. Put the javascript into a mustache template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Call the javascript directly from php (although who would want to put javascript into php? ergh):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* [https://assets.moodlemoot.org/sites/15/20171004085436/JavaScript-AMD-with-RequireJS-presented-by-Daniel-Roperto-Catalyst.pdf JavaScript AMD with RequireJS] presented by Daniel Roperto, Catalyst. (MoodleMOOT AU 2017)&lt;br /&gt;
* [[Useful_core_Javascript_modules]]&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=53722</id>
		<title>Using the File API in Moodle forms</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=53722"/>
		<updated>2018-02-26T17:33:12Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Add file manager element */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
{{Moodle 3.X}}&lt;br /&gt;
&lt;br /&gt;
See an example of using a filemanager in an activity module in: https://github.com/CARLOSEDUARDOVIEIRA/moodle-uploadfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to get files from users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 all files are stored in a central database accessible via the [[File API|File API]], and every file is associated with a component and a &amp;quot;file area&amp;quot; in Moodle, such as a particular module.&lt;br /&gt;
&lt;br /&gt;
A common use case is to provide a form (using Moodle&#039;s [[lib/formslib.php|Forms API]]) which allows users to upload or import files as attachments or media embedded into HTML.&lt;br /&gt;
&lt;br /&gt;
Normally this works like this:&lt;br /&gt;
# User starts creation or re-edits an existing item in Moodle (eg forum post, resource, glossary entry etc)&lt;br /&gt;
# User presses some sort of button to browse for new files to attach or embed&lt;br /&gt;
# User sees our &amp;quot;Choose file...&amp;quot; dialog, which contains one or more repository instances. &lt;br /&gt;
# User chooses a file, the corresponding [[Repository plugins|Repository plugin]] takes care of copying the file into a &amp;quot;draft file area&amp;quot; within Moodle&lt;br /&gt;
# File appears in the text or as an attachment in the form.&lt;br /&gt;
# When the user hits save, the [[File API|File API]] is invoked to move the file from the draft file area into a permanent file area associated with that data &lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to interact with users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
If you just want to write code to manipulate Moodle files internally (without user input) then see [[File API]].&lt;br /&gt;
&lt;br /&gt;
==Form elements== &lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 there are three file-related form elements for interacting with users:&lt;br /&gt;
&lt;br /&gt;
# filemanager - the way to attach one or more files as a set&lt;br /&gt;
# editor - the way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML&lt;br /&gt;
# filepicker - a way to specify one file for the case when you want to process the file and throw it away &lt;br /&gt;
&lt;br /&gt;
In Moodle 1.9 there were two other types which are now &#039;&#039;&#039;deprecated&#039;&#039;&#039; (they work, but please do not use these anymore)&lt;br /&gt;
# file - used to just allow a normal file upload from the desktop only.&lt;br /&gt;
# htmleditor - this old method of embedding a HTML editor in a textarea is not able to support repositories etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
&lt;br /&gt;
File picker (&#039;&#039;filepicker&#039;&#039;) is a direct replacement of the older &#039;&#039;file&#039;&#039; formslib element. &lt;br /&gt;
&lt;br /&gt;
It is intended for situations when you want the user to upload &#039;&#039;&#039;one&#039;&#039;&#039; file so you can process it and delete it, such as when you are importing data from a CSV file.&lt;br /&gt;
If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).&lt;br /&gt;
&lt;br /&gt;
==== Using the filepicker element ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null,&lt;br /&gt;
                   array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Obtain the chosen file ====&lt;br /&gt;
&lt;br /&gt;
To get the contents of the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$content = $mform-&amp;gt;get_file_content(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the name of the chosen file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$name = $mform-&amp;gt;get_new_filename(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To save the chosen file to the server filesystem (such as to moodledata folder):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$success = $mform-&amp;gt;save_file(&#039;userfile&#039;, $fullpath, $override);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To store the chosen file in the Moodle files pool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$storedfile = $mform-&amp;gt;save_stored_file(&#039;userfile&#039;, ...);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== filemanager ===&lt;br /&gt;
&lt;br /&gt;
The File Manager element improves on file picker by allowing you to manage more than one file.  It is expected that the files will be stored permanently for future use (such as forum and glossary attachments).&lt;br /&gt;
&lt;br /&gt;
==== Add file manager element ====&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachments&#039;, get_string(&#039;attachment&#039;, &#039;moodle&#039;), null,&lt;br /&gt;
                    array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;areamaxbytes&#039; =&amp;gt; 10485760, &#039;maxfiles&#039; =&amp;gt; 50,&lt;br /&gt;
                          &#039;accepted_types&#039; =&amp;gt; array(&#039;document&#039;), &#039;return_types&#039;=&amp;gt; FILE_INTERNAL | FILE_EXTERNAL));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the fields for filemanager:&lt;br /&gt;
&lt;br /&gt;
;&#039;filemanager&#039;:This is a filemanager element :)&lt;br /&gt;
;elementname:The unique name of the element in the form&lt;br /&gt;
;elementlabel:The label string that users see &lt;br /&gt;
;attributes:(leave it as null)&lt;br /&gt;
;options: an array of further options for the filepicker (see below)&lt;br /&gt;
&lt;br /&gt;
The options array can contain:&lt;br /&gt;
&lt;br /&gt;
;subdirs:(Default 1) Are subdirectories allowed?  (true or false)&lt;br /&gt;
;maxbytes:(Default 0) Restricts the size of each individual file.&lt;br /&gt;
;areamaxbytes:(Default 0) Restricts the total size of all the files.&lt;br /&gt;
;maxfiles:(Default -1) Restricts the total number of files.&lt;br /&gt;
;accepted_types:(Default *) You can specify what file types are accepted by filemanager.  All current file types are listed in this file: [http://cvs.moodle.org/moodle/lib/filestorage/file_types.mm moodle/lib/filestorage/file_types.mm].  This is a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file: if it is edited the changes will be immediately reflected in Moodle.&lt;br /&gt;
&lt;br /&gt;
As of 2.3 file_types.mm is no longer used, instead the file types are listed in &amp;lt;code&amp;gt;get_mimetypes_array()&amp;lt;/code&amp;gt; in lib/classes/filelib.php (https://github.com/moodle/moodle/blob/master/lib/classes/filetypes.php).&lt;br /&gt;
&lt;br /&gt;
Example usage:  &#039;&#039;&#039;array(&#039;audio&#039;, &#039;video&#039;, &#039;document&#039;)&#039;&#039;&#039;, you can include file extensions as well, for example: &#039;&#039;&#039;array(&#039;.txt&#039;, &#039;.jpg&#039;, &#039;audio&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Load existing files into draft area ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
    $entry = new stdClass;&lt;br /&gt;
    $entry-&amp;gt;id = null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftitemid = file_get_submitted_draft_itemid(&#039;attachments&#039;);&lt;br /&gt;
&lt;br /&gt;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id,&lt;br /&gt;
                        array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
&lt;br /&gt;
$entry-&amp;gt;attachments = $draftitemid;&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Store updated set of files ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // ... store or update $entry&lt;br /&gt;
    file_save_draft_area_files($data-&amp;gt;attachments, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;,&lt;br /&gt;
                   $entry-&amp;gt;id, array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===editor===&lt;br /&gt;
There are two ways of using the editor element in code, the first one is easier but expects some standardized fields. The second method is more low level.&lt;br /&gt;
&lt;br /&gt;
====Simple use====&lt;br /&gt;
# name database fields: &#039;&#039;textfield&#039;&#039;, &#039;&#039;textfieldformat&#039;&#039; (and &#039;&#039;textfieldtrust&#039;&#039; if required)&lt;br /&gt;
# create options array.  note that context is the best, most local context you have available.&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$textfieldoptions = array(&#039;trusttext&#039;=&amp;gt;true, &#039;subdirs&#039;=&amp;gt;true, &#039;maxfiles&#039;=&amp;gt;$maxfiles, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;context&#039;=&amp;gt;$context);&amp;lt;/code&amp;gt;&lt;br /&gt;
# add editor &#039;&#039;textfield_editor&#039;&#039; to moodle form, pass options through custom data in form constructor, set $data-&amp;gt;id to null if data not exist yet&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;textfield_editor&#039;, get_string(&#039;fieldname&#039;, &#039;somemodule&#039;), null, $textfieldoptions);&amp;lt;/code&amp;gt;&lt;br /&gt;
# prepare data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_prepare_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
# get submitted data and after inserting/updating of data&lt;br /&gt;
#:&amp;lt;code php&amp;gt;$data = file_postupdate_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context, &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Real world examples are in mod/glossary/edit.php and mod/glossary/comment.php&lt;br /&gt;
&lt;br /&gt;
====Low level use====&lt;br /&gt;
&lt;br /&gt;
When using editor element you  need to preprocess and postprocess the data:&lt;br /&gt;
# detect if form was already submitted (usually means draft is area already exists) - &#039;&#039;file_get_submitted_draft_itemid()&#039;&#039;&lt;br /&gt;
# prepare draft file area, temporary storage of all files attached to the text - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# convert encoded relative links to absolute links - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# create form and set current data&lt;br /&gt;
# after submission the changed files must be merged back into original area - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
# absolute links have to be replaced by relative links - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====Replace old htmleditor with editor=====&lt;br /&gt;
&lt;br /&gt;
The file picker has been integrated with with TinyMCE to make the editor element. This new element should support all types on editors and should be able to switch them on-the-fly. Instances of the old htmleditor element in your forms should be replaced by the new editor element, this may need adding of new format and trusttext columns. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;entry&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
        array(&#039;maxfiles&#039; =&amp;gt; EDITOR_UNLIMITED_FILES));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The editor element can take following options: maxfiles, maxbytes, subdirs and changeformat. Please note that the embedded files is optional feature and is not expected be used everywhere.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: the editor element now includes text format option. You should no longer use the separate format element type.&lt;br /&gt;
&lt;br /&gt;
=====Prepare current data - text and files=====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
  $entry = new object();&lt;br /&gt;
  $entry-&amp;gt;id = null;&lt;br /&gt;
  $entry-&amp;gt;definition = &#039;&#039;;&lt;br /&gt;
  $entry-&amp;gt;format = FORMAT_HTML;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftid_editor = file_get_submitted_draft_itemid(&#039;entry&#039;);&lt;br /&gt;
$currenttext = file_prepare_draft_area($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                       $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $entry-&amp;gt;definition);&lt;br /&gt;
$entry-&amp;gt;entry = array(&#039;text&#039;=&amp;gt;$currenttext, &#039;format&#039;=&amp;gt;$entry-&amp;gt;format, &#039;itemid&#039;=&amp;gt;$draftid_editor);&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are multiple files, they will share the same itemid.&lt;br /&gt;
&lt;br /&gt;
=====Obtain text, format and save draft files=====&lt;br /&gt;
&lt;br /&gt;
To retrieve editor content, you need to use following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // content of editor&lt;br /&gt;
    $messagetext = $fromform-&amp;gt;entry[&#039;text&#039;];&lt;br /&gt;
    // format of content&lt;br /&gt;
    $messageformat  = $fromform-&amp;gt;entry[&#039;format&#039;];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.&lt;br /&gt;
&lt;br /&gt;
When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call $DB-&amp;gt;update_record(&#039;tablename&#039;, $data); to have the other parts of the form submission stored correctly.)&lt;br /&gt;
&lt;br /&gt;
The save_files_from_draft_area function and replace absolute links with internal relative links do:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_save_draft_area_files($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                          $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $messagetext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $context-&amp;gt;id, &#039;component&#039;, &#039;proper_file_area&#039; and $entry-&amp;gt;id : correspond to the contextid, filearea and itemid columns in the [[File_API#Table:_files|files table]].&lt;br /&gt;
; $messagetext : this is the message text. As the files are saved to the real file area, the URLs in this content are rewritten.&lt;br /&gt;
&lt;br /&gt;
All URLs in content that point to files managed to the File API are converted to a form that starts &#039;@@PLUGINFILE@@/&#039; before the content is stored in the database. That is what we mean by rewriting.&lt;br /&gt;
&lt;br /&gt;
== File serving==&lt;br /&gt;
&lt;br /&gt;
=== Convert internal relative links to absolute links ===&lt;br /&gt;
&lt;br /&gt;
Before text content is displayed to the user, any URLs in the &#039;@@PLUGINFILE@@/&#039; form in the content need to be rewritten to the real URL where the user can access the files. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_rewrite_pluginfile_urls($messagetext, &#039;pluginfile.php&#039;,&lt;br /&gt;
        $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $messagetext : is the content containing the @@PLUGINFILE@@ URLs from the database.&lt;br /&gt;
; &#039;pluginfile.php&#039; : there are a number of different scripts that can serve files with different permissions checks. You need to specify which one to use.&lt;br /&gt;
; $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid : uniquely identifies the file area, as before.&lt;br /&gt;
&lt;br /&gt;
=== Implement file serving access control ===&lt;br /&gt;
&lt;br /&gt;
Attachments and embedded images should have the same access control like the text itself, in majority of cases these files are served using pluginfile.php. Access control is defined in &#039;&#039;module/lib.php&#039;&#039; file in function &#039;&#039;module_pluginfile()&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== File browsing support ==&lt;br /&gt;
Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.&lt;br /&gt;
&lt;br /&gt;
Activities may specify browsing support in own module/lib.php file by implementing functions module_get_file_areas() and module_get_file_info().&lt;br /&gt;
&lt;br /&gt;
== Upgrading your code ==&lt;br /&gt;
Here I will attempt to describe some simple steps you can take to upgrade your file-handling form elements from pre-2.0 code to 2.0. We will use the example of glossary, since it has been used above.&lt;br /&gt;
&lt;br /&gt;
=== Preparing your options ===&lt;br /&gt;
Unless you are happy with the defaults, you will need to define an array of options for each file-handling form element. You could define it at different places, but it&#039;s best to put it in one place and make the array(s) available to other files if they need it. In the majority of cases, this will be in a file like edit.php&lt;br /&gt;
&lt;br /&gt;
Previous code in mod/glossary/edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform =&amp;amp; new mod_glossary_entry_form(null, compact(&#039;cm&#039;, &#039;glossary&#039;, &#039;hook&#039;, &#039;mode&#039;, &#039;e&#039;, &#039;context&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$maxbytes = $course-&amp;gt;maxbytes;&lt;br /&gt;
// Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&lt;br /&gt;
&lt;br /&gt;
$definitionoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;trusttext&#039;=&amp;gt;true,&lt;br /&gt;
                           &#039;context&#039;=&amp;gt;$context);&lt;br /&gt;
$attachmentoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note that the data being passed to the form constructor have changed also, but this is not part of the file API changes, I just include them to avoid confusion.&lt;br /&gt;
&lt;br /&gt;
These options are for the htmleditor (definition field) and the filemanager (attachment field). They are used by a file called edit_form.php.&lt;br /&gt;
&lt;br /&gt;
=== Element preparation ===&lt;br /&gt;
Before we look at this, however, we need to &amp;quot;prepare&amp;quot; the elements so that they can correctly display existing embedded images and attached files when you are editing a record instead of just creating one. So, let&#039;s take the code we&#039;ve got so far in edit.php and add to it:&lt;br /&gt;
&lt;br /&gt;
Currently upgraded code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&lt;br /&gt;
        &#039;current&#039;=&amp;gt;$entry, &lt;br /&gt;
        &#039;cm&#039;=&amp;gt;$cm, &lt;br /&gt;
        &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
        &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
        &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code with element preparation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$entry = file_prepare_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                      &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_prepare_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                           &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* $entry in this case is simply a stdClass object which may either represent a new glossary entry or an existing one.&lt;br /&gt;
* $entry-&amp;gt;id must be the unique identifier for the current object. If we are creating a new entry, it will be null, but in all cases it must be defined.&lt;br /&gt;
* These two functions (file_prepare_standard_editor and file_prepare_standard_filemanager) are shortcuts functions that take care of some of the tedious setting up for you, but they make a couple of assumptions:&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; name the form element as {element}_editor or {element}_filemanager (see next section)&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; have at least the following fields in the database: {element} and {element}summary, as described earlier in this documentation&lt;br /&gt;
&lt;br /&gt;
We can now look at the upgrades needed in the form definition file.&lt;br /&gt;
&lt;br /&gt;
=== Form definition ===&lt;br /&gt;
Previous code in mod/glossary/edit_form.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;htmleditor&#039;, &#039;definition&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;),&lt;br /&gt;
                    array(&#039;rows&#039;=&amp;gt;20));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;definition&#039;, array(&#039;writing&#039;, &#039;richtext&#039;), false, &#039;editorhelpbutton&#039;);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;format&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0,&lt;br /&gt;
                                             true, true, false));&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment&#039;, array(&#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                      &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitionoptions = $this-&amp;gt;_customdata[&#039;definitionoptions&#039;];&lt;br /&gt;
$attachmentoptions = $this-&amp;gt;_customdata[&#039;attachmentoptions&#039;];&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;definition_editor&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
                   $definitionoptions);&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition_editor&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition_editor&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachment_filemanager&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                   null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;,&lt;br /&gt;
                      get_string(&#039;attachment&#039;, &#039;glossary&#039;), &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the following:&lt;br /&gt;
* The format element and the help button are no longer required for the HTML editor element&lt;br /&gt;
* The name of the form element needs to be changed by adding &#039;_editor&#039; or &#039;_manager&#039; to the original name. This is a naming convention that is used by a couple of functions we will look at shortly&lt;br /&gt;
* Make sure $definitionoptions has context parameter, else system context is used and editor will not respect filter settings.&lt;br /&gt;
&lt;br /&gt;
=== Handling submitted data ===&lt;br /&gt;
The final step is to handle the submitted data properly, i.e. retrieve the files and save them to disk, associating them with the record we have just created (a glossary entry in our example). This happens in edit.php:&lt;br /&gt;
&lt;br /&gt;
Previous code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Section that updates an entry:&lt;br /&gt;
$todb-&amp;gt;id = $e;&lt;br /&gt;
$dir = glossary_file_area_name($todb);&lt;br /&gt;
if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
    $todb-&amp;gt;attachment = $newfilename;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Section that adds an entry:&lt;br /&gt;
if ($todb-&amp;gt;id = insert_record(&amp;quot;glossary_entries&amp;quot;, $todb)) {&lt;br /&gt;
    $e = $todb-&amp;gt;id;&lt;br /&gt;
    $dir = glossary_file_area_name($todb);&lt;br /&gt;
    if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
        set_field(&amp;quot;glossary_entries&amp;quot;, &amp;quot;attachment&amp;quot;, $newfilename, &amp;quot;id&amp;quot;, $todb-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// $todb was renamed to $entry, and the code was refactored &lt;br /&gt;
// so that the file-handling code is only used once for either an add or an update action.&lt;br /&gt;
// If an entry is being added, $DB-&amp;gt;insert() has already been called, so we have a valid $entry-&amp;gt;id&lt;br /&gt;
$entry = file_postupdate_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                         &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_postupdate_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                              &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
// store the updated value values&lt;br /&gt;
$DB-&amp;gt;update_record(&#039;glossary_entries&#039;, $entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* If you are adding a new record, you will still need to call update_record after calling the file_postupdate* functions&lt;br /&gt;
&lt;br /&gt;
=== Gotchas ===&lt;br /&gt;
A few things to keep in mind:&lt;br /&gt;
* Make sure that you instantiate the moodle form before any call to $OUTPUT-&amp;gt;header()&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Using the file API]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=207748 Adding a text editor to a Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=157953#p692822 Button actions in Moodle form]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=351013#p1416554 File Picker]&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Repositories]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53441</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53441"/>
		<updated>2017-11-24T13:32:16Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* 2. Set up Selenium */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Short version ==&lt;br /&gt;
&lt;br /&gt;
...or how I got it to work on Ubuntu and some of the problems encountered. &lt;br /&gt;
&lt;br /&gt;
You need a bunch of browsers and terminal windows open to do this.&lt;br /&gt;
&lt;br /&gt;
==== 1. Background ====  &lt;br /&gt;
&lt;br /&gt;
# I am using the desktop version of Ubuntu 17.04 so there are no issues about running this software in headless mode. Running in headless mode was not tested.&lt;br /&gt;
# Moodle is version 3.3 and is a fully installed and working version using the &#039;standard&#039; Ubuntu LAMP stack.&lt;br /&gt;
&lt;br /&gt;
==== 2. Set up Selenium ====&lt;br /&gt;
&lt;br /&gt;
# Download the Selenium Standalone Server from [http://www.seleniumhq.org/download/ http://www.seleniumhq.org/download/]. It&#039;s a single JAR file, put it anywhere handy.&lt;br /&gt;
# Download the Chrome driver from [https://sites.google.com/a/chromium.org/chromedriver/ https://sites.google.com/a/chromium.org/chromedriver/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running&amp;quot; when running the behat tests). Try the following&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Then immediately, &#039;&amp;lt;tt&amp;gt;export DISPLAY=:99&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;default&#039; =&amp;gt; [&lt;br /&gt;
            &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;selenium2&#039; =&amp;gt; [&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;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/run.php&amp;lt;/tt&amp;gt;&#039;  (if you don&#039;t want to run all the tests add &#039;&amp;lt;tt&amp;gt;--tags=&amp;quot;@something&amp;quot;&amp;lt;/tt&amp;gt;&#039; where the @something refers to the tags at the top of most feature files)&lt;br /&gt;
# After some initial setup dots should start to go by. It&#039;s a while before Selenium is first accessed. On the Linux desktop a new Chrome window appears and the testing process &#039;remote control&#039; starts (hopefully!)&lt;br /&gt;
&lt;br /&gt;
== Prerequisite ==&lt;br /&gt;
Before initializing acceptance test environment for running behat, you should ensure:&lt;br /&gt;
# [[Acceptance_testing#Requirements Meet min. system requirements for running tests]]&lt;br /&gt;
# [[Acceptance_testing#Installation Have set min. config variable in config.php for behat]]&lt;br /&gt;
# [[Acceptance_testing#Installation Downloaded composer dependencies]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Acceptance tests (also known as behat), use [http://www.seleniumhq.org/download/ Selenium server] and can be run as:&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; In single run, only one behat run is executed. So all features are executed in this single run.&lt;br /&gt;
# &#039;&#039;&#039;Parallel runs:&#039;&#039;&#039; (Since Moodle 3.0) Parallel runs allow dev&#039;s to execute multiple behat runs together. This was introduced to get acceptance tests results faster. To achieve this:&lt;br /&gt;
#* Features are divided between multiple behat runs&lt;br /&gt;
#* Symlinks behatrun{x} (x being the run process number), are created pointing to moodle directory, so site for run 1 is accessible via https://localhost/moodle/behatrun1&lt;br /&gt;
#* Process number is included as suffix to $CFG-&amp;gt;behat_prefix.&lt;br /&gt;
#* Process number is suffixed to $CFG-&amp;gt;behat_dataroot.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Initialise acceptance test environment ==&lt;br /&gt;
Before running acceptance tests, environment needs to be initialised for acceptance testing.&lt;br /&gt;
&lt;br /&gt;
=== Single run ===&lt;br /&gt;
For initialising acceptance tests for single run, above command is sufficient.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For initialising acceptance tests for parallel runs, you can use one of the following options&lt;br /&gt;
# &#039;&#039;&#039;-j or --parallel&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m or --maxruns&#039;&#039;&#039;  (optional) Max parallel site which should be initialised at one time. If your system is slow, then you can initialise sites in chucks.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; (optional) Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; (optional) Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;-o or --optimize-runs&#039;&#039;&#039; (optional) This option will split features with specified tags in all parallel runs, so they are executed first when parallel run gets executed.&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified themes (comma separated list of themes)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Below command will initialise moodle to run 2 parallel tests.&lt;br /&gt;
php admin/tool/behat/cli/init.php --parallel=2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 2: Running acceptance test environment ==&lt;br /&gt;
=== Single run ===&lt;br /&gt;
Run either of the following commands. 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;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behatrun/behat/behat.yml&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following optional options are available for custom run:&lt;br /&gt;
# &#039;&#039;&#039;--feature&#039;&#039;&#039; Only execute specified feature file (Absolute path of feature file).&lt;br /&gt;
# &#039;&#039;&#039;--suite&#039;&#039;&#039; Features for specified theme will be executed.&lt;br /&gt;
# &#039;&#039;&#039;--replace&#039;&#039;&#039; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified theme&#039;s (comma separated list)&lt;br /&gt;
# Behat options can be passed for filtering features/scenarios:&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;--tags=&amp;quot;~@javascript&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific scenario, use the Behat name option to run it, &#039;&#039;&#039;--name=&amp;quot;Filter user accounts by role and cohort&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific feature file, use the Behat feature option to run it, &#039;&#039;&#039;--feature=&amp;quot;/PATH/TO/MOODLE/admin/tests/behat/filter_users.feature&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Common options for running tests ===&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 a 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;@_file_upload&#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;@_alert&#039;&#039;&#039;: All the tests that involves Javascript dialogs (alerts, confirms...) are using a feature that is OS-dependant and out of the browser scope, so they should be tag appropriately as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_window&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_iframe&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&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;
==== Output formats ====&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=pretty --out=/path/to/pretty.txt --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Before Moodle 3.1 option for output was:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=&#039;moodle_progress,pretty&#039; --out=&#039;,/path/to/pretty.txt&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following output formats are supported:&lt;br /&gt;
# &#039;&#039;&#039;progress&#039;&#039;&#039;: Prints one character per step.&lt;br /&gt;
# &#039;&#039;&#039;pretty&#039;&#039;&#039;: Prints the feature as is.&lt;br /&gt;
# &#039;&#039;&#039;junit&#039;&#039;&#039;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: (since Moodle 3.1) Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
If you want to see the failures immediately (rather than waiting ~3 hours for all the tests to finish) then either use the -v option to output a bit more information, or change the output format using --format. Format &#039;pretty&#039; (&#039;&#039;&#039;-f pretty&#039;&#039;&#039;) is sufficient for most cases, as it outputs each step outcomes in the command line making easier to see the progress.&lt;br /&gt;
&lt;br /&gt;
== Advance usage ==&lt;br /&gt;
=== Rerun failed scenarios ===&lt;br /&gt;
With slow systems or parallel run you might see some random failures, to rerun only failed scenarios (to eliminate random failures), use --rerun option&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; --run=&amp;quot;absolute_path_to_empty_file&amp;quot; (Behat will record failed scenarios in this file, and when run again only failed scenarios will be run)&lt;br /&gt;
# &#039;&#039;&#039;Parallel run:&#039;&#039;&#039; --rerun=&amp;quot;absolute_path_to_empty_file_{runprocess}.txt --replace=&amp;quot;{runprocess}&amp;quot; ({runprocess} will be replaced with the process number for recording fails in the specific run process).&lt;br /&gt;
&#039;&#039;&#039;Since Moodle 3.1 --rerun option don&#039;t accept any value, as it is handled internally by behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Running behat with specified theme (Since Moodle 3.2) ===&lt;br /&gt;
You can run behat with any theme installed. To execute behat with specified theme use &#039;&#039;&#039;--suite={THEME_NAME}&#039;&#039;&#039; option, while running behat. By default the features in theme behat folder will be executed for the suite. But if you want to run all core features with the specific theme then initialise acceptance test with -a option. For example, &#039;&#039;&#039;-a {THEME_NAME}&#039;&#039;&#039;&lt;br /&gt;
Also if you want a specific theme for your behat tests, use the $CFG-&amp;gt;theme variable to set the theme.&lt;br /&gt;
&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
To override behat step definitions so as to run behat with specified theme, you should create a contexts within &#039;&#039;&#039;/theme/{MYTHEME}/tests/behat/&#039;&#039;&#039; with prefix behat_theme_{MYTHEME}_ and suffixed with the context being overridden. For example, if you want to override behat_mod_forum context, then you should create a class /theme/{MYTHEME}/tests/behat/mod_forum/behat_theme_{MYTHEME}_behat_mod_forum.php&lt;br /&gt;
&lt;br /&gt;
==== Blacklist behat context or features to run in theme suite ====&lt;br /&gt;
To blacklist contexts/ features to be executed by theme suite you should create a /theme/{MYTHEME}/tests/behat/blacklist.json file with following format. Following will not use step_definitions from  behat_grade and behat_navigation while running theme suite. Also, scenarios in auth/tests/behat/login.feature and grade/tests/behat/grade_hidden_items.feature won&#039;t be executed with theme suite.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;contexts&amp;quot;: [&lt;br /&gt;
    &amp;quot;behat_grade&amp;quot;,&lt;br /&gt;
    &amp;quot;behat_navigation&amp;quot;,&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;features&amp;quot;: [&lt;br /&gt;
    &amp;quot;auth/tests/behat/login.feature&amp;quot;,&lt;br /&gt;
    &amp;quot;grade/tests/behat/grade_hidden_items.feature&amp;quot;,&lt;br /&gt;
   ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
To override behat selectors in specific theme, you should create a class behat_theme_{MYTHEME}_behat_selectors in /theme/{MYTHEME}/tests/behat/behat_theme_{MYTHEME}_behat_selectors.php extending behat_selectors.&lt;br /&gt;
&lt;br /&gt;
=== Use php built in web server ===&lt;br /&gt;
You can use php built-in-web server for executing behat runs. To do so:&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 test site URL that moodle uses by default, 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 or config-dist.php)&lt;br /&gt;
# Update $CFG-&amp;gt;behat_wwwroot = localhost:8000; in config.php&lt;br /&gt;
&lt;br /&gt;
=== Define custom options for parallel runs ===&lt;br /&gt;
You can set following custom config options for parallel runs via $CFG-&amp;gt;behat_parallel_run. It&#039;s an array of options where 1st array is for 1st run and so on.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
       array (&lt;br /&gt;
           &#039;dbtype&#039; =&amp;gt; &#039;mysqli&#039;,&lt;br /&gt;
           &#039;dblibrary&#039; =&amp;gt; &#039;native&#039;,&lt;br /&gt;
           &#039;dbhost&#039; =&amp;gt; &#039;localhost&#039;,&lt;br /&gt;
           &#039;dbname&#039; =&amp;gt; &#039;moodletest&#039;,&lt;br /&gt;
           &#039;dbuser&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;dbpass&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;behat_prefix&#039; =&amp;gt; &#039;mdl_&#039;,&lt;br /&gt;
           &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;,&lt;br /&gt;
           &#039;behat_wwwroot&#039; =&amp;gt; &#039;http://127.0.0.1/moodle&#039;,&lt;br /&gt;
           &#039;behat_dataroot&#039; =&amp;gt; &#039;/home/example/bht_moodledata&#039;&lt;br /&gt;
       )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To set different selenium servers for parallel runs, you can use following. NOTE: Running parallel (headless) runs on different selenium servers avoid random focus failures.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    $CFG-&amp;gt;behat_parallel_run = array (&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;),&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
By default behat will run with Firefox browser through Selenium. By adding the following code to your config.php you can change the selected browser that is run when behat is invoked.  You will need to run php admin/tool/behat/cli/init.php for changes to take effect. Then use --profile=&#039;chrome&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;chrome&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;@javascript&#039;,&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
&lt;br /&gt;
=== Start multiple selenium servers ===&lt;br /&gt;
From command line Start selenium servers at different ports (say 4444, 4445, 4446 for 3 parallel runs)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4444 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4445 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4446&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Using Docker to start selenium server ===&lt;br /&gt;
==== What is Docker ====&lt;br /&gt;
Docker is a app container,  it&#039;s a kind of virtual machine, but only for one app, service,  so you can download&lt;br /&gt;
a docker image and run a selenium server without worry in how to configure selenium in your machine, one for chrome, others for firefox, you either don&#039;t need to install the browsers in your machine&lt;br /&gt;
To install docker follow this link; https://docs.docker.com/engine/installation/&lt;br /&gt;
&lt;br /&gt;
==== Selenium docker images ====&lt;br /&gt;
There is many docker images available,  for many browser, the complete list is in https://hub.docker.com/u/selenium/&lt;br /&gt;
for moodle you can use standalone version.&lt;br /&gt;
You can download  specific selenium version too,  for example,  for firefox,  moodle recommend selenium 2.53.1, see: [https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium What version do I need?]&lt;br /&gt;
&lt;br /&gt;
so  the command will be:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
docker run -d -p4444:4444 selenium/standalone-firefox:2.53.1-beryllium&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to see all available version click in tags.   For firefox you can find at: https://hub.docker.com/r/selenium/standalone-firefox/tags/&lt;br /&gt;
&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In config.php file you must change the $CFG-&amp;gt;behat_wwwroot=   to your network card (NIC) ip address,  you can&#039;t use &lt;br /&gt;
localhost , 127.0.0.1, ...  or selenium docker server  will fail&lt;br /&gt;
&lt;br /&gt;
== NOTE ==&lt;br /&gt;
# Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* (See http://www.installationpage.com/selenium/how-to-run-selenium-headless-firefox-in-ubuntu/ for running &#039;headless&#039; Firefox and xvfm in a server environment)&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;
&lt;br /&gt;
=== Trouble shooting ===&lt;br /&gt;
=== New step definitions or features are not executed === &lt;br /&gt;
If you are adding new tests or steps definitions update the tests list&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; For parallel runs, all options for initialising parallel runs are valid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests are failing ===&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your system&#039;s Firefox version is not compatible with the Selenium version you are running.  Please refer Working combinations to ensure you have correct [[Acceptance_testing/Browsers#Working_combinations_of_OS.2BBrowser.2Bselenium]] of them to run acceptance test.&lt;br /&gt;
&lt;br /&gt;
=== Disable acceptance test environment ===&lt;br /&gt;
if you want to prevent access to test environment&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --disable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note that if you have the HTTP_PROXY environment variable set, which you may have had to do to run composer, then you also need to set NO_PROXY=localhost.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
* Vagrant profile with Moodle and Behat preconfigured: https://github.com/mackensen/moodle-hat&lt;br /&gt;
* Docker containers for Moodle Developers and Behat: https://github.com/moodlehq/moodle-docker&lt;br /&gt;
* Docker environment with Behat preconfigured : https://github.com/tobiga/docker_moodle_environment&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53439</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53439"/>
		<updated>2017-11-24T13:31:05Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* 1. Background */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Short version ==&lt;br /&gt;
&lt;br /&gt;
...or how I got it to work on Ubuntu and some of the problems encountered. &lt;br /&gt;
&lt;br /&gt;
You need a bunch of browsers and terminal windows open to do this.&lt;br /&gt;
&lt;br /&gt;
==== 1. Background ====  &lt;br /&gt;
&lt;br /&gt;
# I am using the desktop version of Ubuntu 17.04 so there are no issues about running this software in headless mode. Running in headless mode was not tested.&lt;br /&gt;
# Moodle is version 3.3 and is a fully installed and working version using the &#039;standard&#039; Ubuntu LAMP stack.&lt;br /&gt;
&lt;br /&gt;
==== 2. Set up Selenium ====&lt;br /&gt;
&lt;br /&gt;
# Download the Selenium Standalone Server from [http://www.seleniumhq.org/download/ http://www.seleniumhq.org/download/]. It&#039;s a single JAR file, put it anywhere handy.&lt;br /&gt;
# Download the Chrome driver from [https://sites.google.com/a/chromium.org/chromedriver/ https://sites.google.com/a/chromium.org/chromedriver/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running). Try the following&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Then immediately, &#039;&amp;lt;tt&amp;gt;export DISPLAY=:99&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;default&#039; =&amp;gt; [&lt;br /&gt;
            &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;selenium2&#039; =&amp;gt; [&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;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/run.php&amp;lt;/tt&amp;gt;&#039;  (if you don&#039;t want to run all the tests add &#039;&amp;lt;tt&amp;gt;--tags=&amp;quot;@something&amp;quot;&amp;lt;/tt&amp;gt;&#039; where the @something refers to the tags at the top of most feature files)&lt;br /&gt;
# After some initial setup dots should start to go by. It&#039;s a while before Selenium is first accessed. On the Linux desktop a new Chrome window appears and the testing process &#039;remote control&#039; starts (hopefully!)&lt;br /&gt;
&lt;br /&gt;
== Prerequisite ==&lt;br /&gt;
Before initializing acceptance test environment for running behat, you should ensure:&lt;br /&gt;
# [[Acceptance_testing#Requirements Meet min. system requirements for running tests]]&lt;br /&gt;
# [[Acceptance_testing#Installation Have set min. config variable in config.php for behat]]&lt;br /&gt;
# [[Acceptance_testing#Installation Downloaded composer dependencies]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Acceptance tests (also known as behat), use [http://www.seleniumhq.org/download/ Selenium server] and can be run as:&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; In single run, only one behat run is executed. So all features are executed in this single run.&lt;br /&gt;
# &#039;&#039;&#039;Parallel runs:&#039;&#039;&#039; (Since Moodle 3.0) Parallel runs allow dev&#039;s to execute multiple behat runs together. This was introduced to get acceptance tests results faster. To achieve this:&lt;br /&gt;
#* Features are divided between multiple behat runs&lt;br /&gt;
#* Symlinks behatrun{x} (x being the run process number), are created pointing to moodle directory, so site for run 1 is accessible via https://localhost/moodle/behatrun1&lt;br /&gt;
#* Process number is included as suffix to $CFG-&amp;gt;behat_prefix.&lt;br /&gt;
#* Process number is suffixed to $CFG-&amp;gt;behat_dataroot.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Initialise acceptance test environment ==&lt;br /&gt;
Before running acceptance tests, environment needs to be initialised for acceptance testing.&lt;br /&gt;
&lt;br /&gt;
=== Single run ===&lt;br /&gt;
For initialising acceptance tests for single run, above command is sufficient.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For initialising acceptance tests for parallel runs, you can use one of the following options&lt;br /&gt;
# &#039;&#039;&#039;-j or --parallel&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m or --maxruns&#039;&#039;&#039;  (optional) Max parallel site which should be initialised at one time. If your system is slow, then you can initialise sites in chucks.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; (optional) Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; (optional) Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;-o or --optimize-runs&#039;&#039;&#039; (optional) This option will split features with specified tags in all parallel runs, so they are executed first when parallel run gets executed.&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified themes (comma separated list of themes)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Below command will initialise moodle to run 2 parallel tests.&lt;br /&gt;
php admin/tool/behat/cli/init.php --parallel=2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 2: Running acceptance test environment ==&lt;br /&gt;
=== Single run ===&lt;br /&gt;
Run either of the following commands. 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;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behatrun/behat/behat.yml&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following optional options are available for custom run:&lt;br /&gt;
# &#039;&#039;&#039;--feature&#039;&#039;&#039; Only execute specified feature file (Absolute path of feature file).&lt;br /&gt;
# &#039;&#039;&#039;--suite&#039;&#039;&#039; Features for specified theme will be executed.&lt;br /&gt;
# &#039;&#039;&#039;--replace&#039;&#039;&#039; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified theme&#039;s (comma separated list)&lt;br /&gt;
# Behat options can be passed for filtering features/scenarios:&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;--tags=&amp;quot;~@javascript&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific scenario, use the Behat name option to run it, &#039;&#039;&#039;--name=&amp;quot;Filter user accounts by role and cohort&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific feature file, use the Behat feature option to run it, &#039;&#039;&#039;--feature=&amp;quot;/PATH/TO/MOODLE/admin/tests/behat/filter_users.feature&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Common options for running tests ===&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 a 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;@_file_upload&#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;@_alert&#039;&#039;&#039;: All the tests that involves Javascript dialogs (alerts, confirms...) are using a feature that is OS-dependant and out of the browser scope, so they should be tag appropriately as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_window&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_iframe&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&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;
==== Output formats ====&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=pretty --out=/path/to/pretty.txt --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Before Moodle 3.1 option for output was:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=&#039;moodle_progress,pretty&#039; --out=&#039;,/path/to/pretty.txt&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following output formats are supported:&lt;br /&gt;
# &#039;&#039;&#039;progress&#039;&#039;&#039;: Prints one character per step.&lt;br /&gt;
# &#039;&#039;&#039;pretty&#039;&#039;&#039;: Prints the feature as is.&lt;br /&gt;
# &#039;&#039;&#039;junit&#039;&#039;&#039;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: (since Moodle 3.1) Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
If you want to see the failures immediately (rather than waiting ~3 hours for all the tests to finish) then either use the -v option to output a bit more information, or change the output format using --format. Format &#039;pretty&#039; (&#039;&#039;&#039;-f pretty&#039;&#039;&#039;) is sufficient for most cases, as it outputs each step outcomes in the command line making easier to see the progress.&lt;br /&gt;
&lt;br /&gt;
== Advance usage ==&lt;br /&gt;
=== Rerun failed scenarios ===&lt;br /&gt;
With slow systems or parallel run you might see some random failures, to rerun only failed scenarios (to eliminate random failures), use --rerun option&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; --run=&amp;quot;absolute_path_to_empty_file&amp;quot; (Behat will record failed scenarios in this file, and when run again only failed scenarios will be run)&lt;br /&gt;
# &#039;&#039;&#039;Parallel run:&#039;&#039;&#039; --rerun=&amp;quot;absolute_path_to_empty_file_{runprocess}.txt --replace=&amp;quot;{runprocess}&amp;quot; ({runprocess} will be replaced with the process number for recording fails in the specific run process).&lt;br /&gt;
&#039;&#039;&#039;Since Moodle 3.1 --rerun option don&#039;t accept any value, as it is handled internally by behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Running behat with specified theme (Since Moodle 3.2) ===&lt;br /&gt;
You can run behat with any theme installed. To execute behat with specified theme use &#039;&#039;&#039;--suite={THEME_NAME}&#039;&#039;&#039; option, while running behat. By default the features in theme behat folder will be executed for the suite. But if you want to run all core features with the specific theme then initialise acceptance test with -a option. For example, &#039;&#039;&#039;-a {THEME_NAME}&#039;&#039;&#039;&lt;br /&gt;
Also if you want a specific theme for your behat tests, use the $CFG-&amp;gt;theme variable to set the theme.&lt;br /&gt;
&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
To override behat step definitions so as to run behat with specified theme, you should create a contexts within &#039;&#039;&#039;/theme/{MYTHEME}/tests/behat/&#039;&#039;&#039; with prefix behat_theme_{MYTHEME}_ and suffixed with the context being overridden. For example, if you want to override behat_mod_forum context, then you should create a class /theme/{MYTHEME}/tests/behat/mod_forum/behat_theme_{MYTHEME}_behat_mod_forum.php&lt;br /&gt;
&lt;br /&gt;
==== Blacklist behat context or features to run in theme suite ====&lt;br /&gt;
To blacklist contexts/ features to be executed by theme suite you should create a /theme/{MYTHEME}/tests/behat/blacklist.json file with following format. Following will not use step_definitions from  behat_grade and behat_navigation while running theme suite. Also, scenarios in auth/tests/behat/login.feature and grade/tests/behat/grade_hidden_items.feature won&#039;t be executed with theme suite.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;contexts&amp;quot;: [&lt;br /&gt;
    &amp;quot;behat_grade&amp;quot;,&lt;br /&gt;
    &amp;quot;behat_navigation&amp;quot;,&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;features&amp;quot;: [&lt;br /&gt;
    &amp;quot;auth/tests/behat/login.feature&amp;quot;,&lt;br /&gt;
    &amp;quot;grade/tests/behat/grade_hidden_items.feature&amp;quot;,&lt;br /&gt;
   ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
To override behat selectors in specific theme, you should create a class behat_theme_{MYTHEME}_behat_selectors in /theme/{MYTHEME}/tests/behat/behat_theme_{MYTHEME}_behat_selectors.php extending behat_selectors.&lt;br /&gt;
&lt;br /&gt;
=== Use php built in web server ===&lt;br /&gt;
You can use php built-in-web server for executing behat runs. To do so:&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 test site URL that moodle uses by default, 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 or config-dist.php)&lt;br /&gt;
# Update $CFG-&amp;gt;behat_wwwroot = localhost:8000; in config.php&lt;br /&gt;
&lt;br /&gt;
=== Define custom options for parallel runs ===&lt;br /&gt;
You can set following custom config options for parallel runs via $CFG-&amp;gt;behat_parallel_run. It&#039;s an array of options where 1st array is for 1st run and so on.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
       array (&lt;br /&gt;
           &#039;dbtype&#039; =&amp;gt; &#039;mysqli&#039;,&lt;br /&gt;
           &#039;dblibrary&#039; =&amp;gt; &#039;native&#039;,&lt;br /&gt;
           &#039;dbhost&#039; =&amp;gt; &#039;localhost&#039;,&lt;br /&gt;
           &#039;dbname&#039; =&amp;gt; &#039;moodletest&#039;,&lt;br /&gt;
           &#039;dbuser&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;dbpass&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;behat_prefix&#039; =&amp;gt; &#039;mdl_&#039;,&lt;br /&gt;
           &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;,&lt;br /&gt;
           &#039;behat_wwwroot&#039; =&amp;gt; &#039;http://127.0.0.1/moodle&#039;,&lt;br /&gt;
           &#039;behat_dataroot&#039; =&amp;gt; &#039;/home/example/bht_moodledata&#039;&lt;br /&gt;
       )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To set different selenium servers for parallel runs, you can use following. NOTE: Running parallel (headless) runs on different selenium servers avoid random focus failures.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    $CFG-&amp;gt;behat_parallel_run = array (&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;),&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
By default behat will run with Firefox browser through Selenium. By adding the following code to your config.php you can change the selected browser that is run when behat is invoked.  You will need to run php admin/tool/behat/cli/init.php for changes to take effect. Then use --profile=&#039;chrome&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;chrome&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;@javascript&#039;,&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
&lt;br /&gt;
=== Start multiple selenium servers ===&lt;br /&gt;
From command line Start selenium servers at different ports (say 4444, 4445, 4446 for 3 parallel runs)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4444 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4445 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4446&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Using Docker to start selenium server ===&lt;br /&gt;
==== What is Docker ====&lt;br /&gt;
Docker is a app container,  it&#039;s a kind of virtual machine, but only for one app, service,  so you can download&lt;br /&gt;
a docker image and run a selenium server without worry in how to configure selenium in your machine, one for chrome, others for firefox, you either don&#039;t need to install the browsers in your machine&lt;br /&gt;
To install docker follow this link; https://docs.docker.com/engine/installation/&lt;br /&gt;
&lt;br /&gt;
==== Selenium docker images ====&lt;br /&gt;
There is many docker images available,  for many browser, the complete list is in https://hub.docker.com/u/selenium/&lt;br /&gt;
for moodle you can use standalone version.&lt;br /&gt;
You can download  specific selenium version too,  for example,  for firefox,  moodle recommend selenium 2.53.1, see: [https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium What version do I need?]&lt;br /&gt;
&lt;br /&gt;
so  the command will be:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
docker run -d -p4444:4444 selenium/standalone-firefox:2.53.1-beryllium&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to see all available version click in tags.   For firefox you can find at: https://hub.docker.com/r/selenium/standalone-firefox/tags/&lt;br /&gt;
&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In config.php file you must change the $CFG-&amp;gt;behat_wwwroot=   to your network card (NIC) ip address,  you can&#039;t use &lt;br /&gt;
localhost , 127.0.0.1, ...  or selenium docker server  will fail&lt;br /&gt;
&lt;br /&gt;
== NOTE ==&lt;br /&gt;
# Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* (See http://www.installationpage.com/selenium/how-to-run-selenium-headless-firefox-in-ubuntu/ for running &#039;headless&#039; Firefox and xvfm in a server environment)&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;
&lt;br /&gt;
=== Trouble shooting ===&lt;br /&gt;
=== New step definitions or features are not executed === &lt;br /&gt;
If you are adding new tests or steps definitions update the tests list&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; For parallel runs, all options for initialising parallel runs are valid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests are failing ===&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your system&#039;s Firefox version is not compatible with the Selenium version you are running.  Please refer Working combinations to ensure you have correct [[Acceptance_testing/Browsers#Working_combinations_of_OS.2BBrowser.2Bselenium]] of them to run acceptance test.&lt;br /&gt;
&lt;br /&gt;
=== Disable acceptance test environment ===&lt;br /&gt;
if you want to prevent access to test environment&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --disable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note that if you have the HTTP_PROXY environment variable set, which you may have had to do to run composer, then you also need to set NO_PROXY=localhost.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
* Vagrant profile with Moodle and Behat preconfigured: https://github.com/mackensen/moodle-hat&lt;br /&gt;
* Docker containers for Moodle Developers and Behat: https://github.com/moodlehq/moodle-docker&lt;br /&gt;
* Docker environment with Behat preconfigured : https://github.com/tobiga/docker_moodle_environment&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53438</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53438"/>
		<updated>2017-11-24T13:30:15Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* 5. Run Behat tests */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Short version ==&lt;br /&gt;
&lt;br /&gt;
...or how I got it to work on Ubuntu and some of the problems encountered. &lt;br /&gt;
&lt;br /&gt;
You need a bunch of browsers and terminal windows open to do this.&lt;br /&gt;
&lt;br /&gt;
==== 1. Background ====  &lt;br /&gt;
&lt;br /&gt;
# I am using the desktop version of Ubuntu 17.04 so there are no issues about running this software in headless mode. Running in headless mode is not considered in this section.&lt;br /&gt;
# Moodle is version 3.3 and is a fully installed and working version using the &#039;standard&#039; Ubuntu LAMP stack.&lt;br /&gt;
&lt;br /&gt;
==== 2. Set up Selenium ====&lt;br /&gt;
&lt;br /&gt;
# Download the Selenium Standalone Server from [http://www.seleniumhq.org/download/ http://www.seleniumhq.org/download/]. It&#039;s a single JAR file, put it anywhere handy.&lt;br /&gt;
# Download the Chrome driver from [https://sites.google.com/a/chromium.org/chromedriver/ https://sites.google.com/a/chromium.org/chromedriver/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running). Try the following&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Then immediately, &#039;&amp;lt;tt&amp;gt;export DISPLAY=:99&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;default&#039; =&amp;gt; [&lt;br /&gt;
            &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;selenium2&#039; =&amp;gt; [&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;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/run.php&amp;lt;/tt&amp;gt;&#039;  (if you don&#039;t want to run all the tests add &#039;&amp;lt;tt&amp;gt;--tags=&amp;quot;@something&amp;quot;&amp;lt;/tt&amp;gt;&#039; where the @something refers to the tags at the top of most feature files)&lt;br /&gt;
# After some initial setup dots should start to go by. It&#039;s a while before Selenium is first accessed. On the Linux desktop a new Chrome window appears and the testing process &#039;remote control&#039; starts (hopefully!)&lt;br /&gt;
&lt;br /&gt;
== Prerequisite ==&lt;br /&gt;
Before initializing acceptance test environment for running behat, you should ensure:&lt;br /&gt;
# [[Acceptance_testing#Requirements Meet min. system requirements for running tests]]&lt;br /&gt;
# [[Acceptance_testing#Installation Have set min. config variable in config.php for behat]]&lt;br /&gt;
# [[Acceptance_testing#Installation Downloaded composer dependencies]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Acceptance tests (also known as behat), use [http://www.seleniumhq.org/download/ Selenium server] and can be run as:&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; In single run, only one behat run is executed. So all features are executed in this single run.&lt;br /&gt;
# &#039;&#039;&#039;Parallel runs:&#039;&#039;&#039; (Since Moodle 3.0) Parallel runs allow dev&#039;s to execute multiple behat runs together. This was introduced to get acceptance tests results faster. To achieve this:&lt;br /&gt;
#* Features are divided between multiple behat runs&lt;br /&gt;
#* Symlinks behatrun{x} (x being the run process number), are created pointing to moodle directory, so site for run 1 is accessible via https://localhost/moodle/behatrun1&lt;br /&gt;
#* Process number is included as suffix to $CFG-&amp;gt;behat_prefix.&lt;br /&gt;
#* Process number is suffixed to $CFG-&amp;gt;behat_dataroot.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Initialise acceptance test environment ==&lt;br /&gt;
Before running acceptance tests, environment needs to be initialised for acceptance testing.&lt;br /&gt;
&lt;br /&gt;
=== Single run ===&lt;br /&gt;
For initialising acceptance tests for single run, above command is sufficient.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For initialising acceptance tests for parallel runs, you can use one of the following options&lt;br /&gt;
# &#039;&#039;&#039;-j or --parallel&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m or --maxruns&#039;&#039;&#039;  (optional) Max parallel site which should be initialised at one time. If your system is slow, then you can initialise sites in chucks.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; (optional) Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; (optional) Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;-o or --optimize-runs&#039;&#039;&#039; (optional) This option will split features with specified tags in all parallel runs, so they are executed first when parallel run gets executed.&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified themes (comma separated list of themes)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Below command will initialise moodle to run 2 parallel tests.&lt;br /&gt;
php admin/tool/behat/cli/init.php --parallel=2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 2: Running acceptance test environment ==&lt;br /&gt;
=== Single run ===&lt;br /&gt;
Run either of the following commands. 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;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behatrun/behat/behat.yml&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following optional options are available for custom run:&lt;br /&gt;
# &#039;&#039;&#039;--feature&#039;&#039;&#039; Only execute specified feature file (Absolute path of feature file).&lt;br /&gt;
# &#039;&#039;&#039;--suite&#039;&#039;&#039; Features for specified theme will be executed.&lt;br /&gt;
# &#039;&#039;&#039;--replace&#039;&#039;&#039; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified theme&#039;s (comma separated list)&lt;br /&gt;
# Behat options can be passed for filtering features/scenarios:&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;--tags=&amp;quot;~@javascript&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific scenario, use the Behat name option to run it, &#039;&#039;&#039;--name=&amp;quot;Filter user accounts by role and cohort&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific feature file, use the Behat feature option to run it, &#039;&#039;&#039;--feature=&amp;quot;/PATH/TO/MOODLE/admin/tests/behat/filter_users.feature&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Common options for running tests ===&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 a 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;@_file_upload&#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;@_alert&#039;&#039;&#039;: All the tests that involves Javascript dialogs (alerts, confirms...) are using a feature that is OS-dependant and out of the browser scope, so they should be tag appropriately as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_window&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_iframe&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&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;
==== Output formats ====&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=pretty --out=/path/to/pretty.txt --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Before Moodle 3.1 option for output was:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=&#039;moodle_progress,pretty&#039; --out=&#039;,/path/to/pretty.txt&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following output formats are supported:&lt;br /&gt;
# &#039;&#039;&#039;progress&#039;&#039;&#039;: Prints one character per step.&lt;br /&gt;
# &#039;&#039;&#039;pretty&#039;&#039;&#039;: Prints the feature as is.&lt;br /&gt;
# &#039;&#039;&#039;junit&#039;&#039;&#039;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: (since Moodle 3.1) Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
If you want to see the failures immediately (rather than waiting ~3 hours for all the tests to finish) then either use the -v option to output a bit more information, or change the output format using --format. Format &#039;pretty&#039; (&#039;&#039;&#039;-f pretty&#039;&#039;&#039;) is sufficient for most cases, as it outputs each step outcomes in the command line making easier to see the progress.&lt;br /&gt;
&lt;br /&gt;
== Advance usage ==&lt;br /&gt;
=== Rerun failed scenarios ===&lt;br /&gt;
With slow systems or parallel run you might see some random failures, to rerun only failed scenarios (to eliminate random failures), use --rerun option&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; --run=&amp;quot;absolute_path_to_empty_file&amp;quot; (Behat will record failed scenarios in this file, and when run again only failed scenarios will be run)&lt;br /&gt;
# &#039;&#039;&#039;Parallel run:&#039;&#039;&#039; --rerun=&amp;quot;absolute_path_to_empty_file_{runprocess}.txt --replace=&amp;quot;{runprocess}&amp;quot; ({runprocess} will be replaced with the process number for recording fails in the specific run process).&lt;br /&gt;
&#039;&#039;&#039;Since Moodle 3.1 --rerun option don&#039;t accept any value, as it is handled internally by behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Running behat with specified theme (Since Moodle 3.2) ===&lt;br /&gt;
You can run behat with any theme installed. To execute behat with specified theme use &#039;&#039;&#039;--suite={THEME_NAME}&#039;&#039;&#039; option, while running behat. By default the features in theme behat folder will be executed for the suite. But if you want to run all core features with the specific theme then initialise acceptance test with -a option. For example, &#039;&#039;&#039;-a {THEME_NAME}&#039;&#039;&#039;&lt;br /&gt;
Also if you want a specific theme for your behat tests, use the $CFG-&amp;gt;theme variable to set the theme.&lt;br /&gt;
&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
To override behat step definitions so as to run behat with specified theme, you should create a contexts within &#039;&#039;&#039;/theme/{MYTHEME}/tests/behat/&#039;&#039;&#039; with prefix behat_theme_{MYTHEME}_ and suffixed with the context being overridden. For example, if you want to override behat_mod_forum context, then you should create a class /theme/{MYTHEME}/tests/behat/mod_forum/behat_theme_{MYTHEME}_behat_mod_forum.php&lt;br /&gt;
&lt;br /&gt;
==== Blacklist behat context or features to run in theme suite ====&lt;br /&gt;
To blacklist contexts/ features to be executed by theme suite you should create a /theme/{MYTHEME}/tests/behat/blacklist.json file with following format. Following will not use step_definitions from  behat_grade and behat_navigation while running theme suite. Also, scenarios in auth/tests/behat/login.feature and grade/tests/behat/grade_hidden_items.feature won&#039;t be executed with theme suite.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;contexts&amp;quot;: [&lt;br /&gt;
    &amp;quot;behat_grade&amp;quot;,&lt;br /&gt;
    &amp;quot;behat_navigation&amp;quot;,&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;features&amp;quot;: [&lt;br /&gt;
    &amp;quot;auth/tests/behat/login.feature&amp;quot;,&lt;br /&gt;
    &amp;quot;grade/tests/behat/grade_hidden_items.feature&amp;quot;,&lt;br /&gt;
   ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
To override behat selectors in specific theme, you should create a class behat_theme_{MYTHEME}_behat_selectors in /theme/{MYTHEME}/tests/behat/behat_theme_{MYTHEME}_behat_selectors.php extending behat_selectors.&lt;br /&gt;
&lt;br /&gt;
=== Use php built in web server ===&lt;br /&gt;
You can use php built-in-web server for executing behat runs. To do so:&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 test site URL that moodle uses by default, 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 or config-dist.php)&lt;br /&gt;
# Update $CFG-&amp;gt;behat_wwwroot = localhost:8000; in config.php&lt;br /&gt;
&lt;br /&gt;
=== Define custom options for parallel runs ===&lt;br /&gt;
You can set following custom config options for parallel runs via $CFG-&amp;gt;behat_parallel_run. It&#039;s an array of options where 1st array is for 1st run and so on.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
       array (&lt;br /&gt;
           &#039;dbtype&#039; =&amp;gt; &#039;mysqli&#039;,&lt;br /&gt;
           &#039;dblibrary&#039; =&amp;gt; &#039;native&#039;,&lt;br /&gt;
           &#039;dbhost&#039; =&amp;gt; &#039;localhost&#039;,&lt;br /&gt;
           &#039;dbname&#039; =&amp;gt; &#039;moodletest&#039;,&lt;br /&gt;
           &#039;dbuser&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;dbpass&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;behat_prefix&#039; =&amp;gt; &#039;mdl_&#039;,&lt;br /&gt;
           &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;,&lt;br /&gt;
           &#039;behat_wwwroot&#039; =&amp;gt; &#039;http://127.0.0.1/moodle&#039;,&lt;br /&gt;
           &#039;behat_dataroot&#039; =&amp;gt; &#039;/home/example/bht_moodledata&#039;&lt;br /&gt;
       )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To set different selenium servers for parallel runs, you can use following. NOTE: Running parallel (headless) runs on different selenium servers avoid random focus failures.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    $CFG-&amp;gt;behat_parallel_run = array (&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;),&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
By default behat will run with Firefox browser through Selenium. By adding the following code to your config.php you can change the selected browser that is run when behat is invoked.  You will need to run php admin/tool/behat/cli/init.php for changes to take effect. Then use --profile=&#039;chrome&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;chrome&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;@javascript&#039;,&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
&lt;br /&gt;
=== Start multiple selenium servers ===&lt;br /&gt;
From command line Start selenium servers at different ports (say 4444, 4445, 4446 for 3 parallel runs)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4444 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4445 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4446&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Using Docker to start selenium server ===&lt;br /&gt;
==== What is Docker ====&lt;br /&gt;
Docker is a app container,  it&#039;s a kind of virtual machine, but only for one app, service,  so you can download&lt;br /&gt;
a docker image and run a selenium server without worry in how to configure selenium in your machine, one for chrome, others for firefox, you either don&#039;t need to install the browsers in your machine&lt;br /&gt;
To install docker follow this link; https://docs.docker.com/engine/installation/&lt;br /&gt;
&lt;br /&gt;
==== Selenium docker images ====&lt;br /&gt;
There is many docker images available,  for many browser, the complete list is in https://hub.docker.com/u/selenium/&lt;br /&gt;
for moodle you can use standalone version.&lt;br /&gt;
You can download  specific selenium version too,  for example,  for firefox,  moodle recommend selenium 2.53.1, see: [https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium What version do I need?]&lt;br /&gt;
&lt;br /&gt;
so  the command will be:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
docker run -d -p4444:4444 selenium/standalone-firefox:2.53.1-beryllium&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to see all available version click in tags.   For firefox you can find at: https://hub.docker.com/r/selenium/standalone-firefox/tags/&lt;br /&gt;
&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In config.php file you must change the $CFG-&amp;gt;behat_wwwroot=   to your network card (NIC) ip address,  you can&#039;t use &lt;br /&gt;
localhost , 127.0.0.1, ...  or selenium docker server  will fail&lt;br /&gt;
&lt;br /&gt;
== NOTE ==&lt;br /&gt;
# Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* (See http://www.installationpage.com/selenium/how-to-run-selenium-headless-firefox-in-ubuntu/ for running &#039;headless&#039; Firefox and xvfm in a server environment)&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;
&lt;br /&gt;
=== Trouble shooting ===&lt;br /&gt;
=== New step definitions or features are not executed === &lt;br /&gt;
If you are adding new tests or steps definitions update the tests list&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; For parallel runs, all options for initialising parallel runs are valid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests are failing ===&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your system&#039;s Firefox version is not compatible with the Selenium version you are running.  Please refer Working combinations to ensure you have correct [[Acceptance_testing/Browsers#Working_combinations_of_OS.2BBrowser.2Bselenium]] of them to run acceptance test.&lt;br /&gt;
&lt;br /&gt;
=== Disable acceptance test environment ===&lt;br /&gt;
if you want to prevent access to test environment&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --disable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note that if you have the HTTP_PROXY environment variable set, which you may have had to do to run composer, then you also need to set NO_PROXY=localhost.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
* Vagrant profile with Moodle and Behat preconfigured: https://github.com/mackensen/moodle-hat&lt;br /&gt;
* Docker containers for Moodle Developers and Behat: https://github.com/moodlehq/moodle-docker&lt;br /&gt;
* Docker environment with Behat preconfigured : https://github.com/tobiga/docker_moodle_environment&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53437</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53437"/>
		<updated>2017-11-24T13:29:31Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* 5. Run Behat tests */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Short version ==&lt;br /&gt;
&lt;br /&gt;
...or how I got it to work on Ubuntu and some of the problems encountered. &lt;br /&gt;
&lt;br /&gt;
You need a bunch of browsers and terminal windows open to do this.&lt;br /&gt;
&lt;br /&gt;
==== 1. Background ====  &lt;br /&gt;
&lt;br /&gt;
# I am using the desktop version of Ubuntu 17.04 so there are no issues about running this software in headless mode. Running in headless mode is not considered in this section.&lt;br /&gt;
# Moodle is version 3.3 and is a fully installed and working version using the &#039;standard&#039; Ubuntu LAMP stack.&lt;br /&gt;
&lt;br /&gt;
==== 2. Set up Selenium ====&lt;br /&gt;
&lt;br /&gt;
# Download the Selenium Standalone Server from [http://www.seleniumhq.org/download/ http://www.seleniumhq.org/download/]. It&#039;s a single JAR file, put it anywhere handy.&lt;br /&gt;
# Download the Chrome driver from [https://sites.google.com/a/chromium.org/chromedriver/ https://sites.google.com/a/chromium.org/chromedriver/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running). Try the following&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Then immediately, &#039;&amp;lt;tt&amp;gt;export DISPLAY=:99&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;default&#039; =&amp;gt; [&lt;br /&gt;
            &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;selenium2&#039; =&amp;gt; [&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;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/run.php&amp;lt;/tt&amp;gt;&#039;  (if you don&#039;t want to run all the tests add &#039;&amp;lt;tt&amp;gt;--tag=&amp;quot;@something&amp;quot;&amp;lt;/tt&amp;gt;&#039; where the @something refers to the tags at the top of most feature files)&lt;br /&gt;
# After some initial setup dots should start to go by. It&#039;s a while before Selenium is first accessed. On the Linux desktop a new Chrome window appears and the testing process &#039;remote control&#039; starts (hopefully!)&lt;br /&gt;
&lt;br /&gt;
== Prerequisite ==&lt;br /&gt;
Before initializing acceptance test environment for running behat, you should ensure:&lt;br /&gt;
# [[Acceptance_testing#Requirements Meet min. system requirements for running tests]]&lt;br /&gt;
# [[Acceptance_testing#Installation Have set min. config variable in config.php for behat]]&lt;br /&gt;
# [[Acceptance_testing#Installation Downloaded composer dependencies]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Acceptance tests (also known as behat), use [http://www.seleniumhq.org/download/ Selenium server] and can be run as:&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; In single run, only one behat run is executed. So all features are executed in this single run.&lt;br /&gt;
# &#039;&#039;&#039;Parallel runs:&#039;&#039;&#039; (Since Moodle 3.0) Parallel runs allow dev&#039;s to execute multiple behat runs together. This was introduced to get acceptance tests results faster. To achieve this:&lt;br /&gt;
#* Features are divided between multiple behat runs&lt;br /&gt;
#* Symlinks behatrun{x} (x being the run process number), are created pointing to moodle directory, so site for run 1 is accessible via https://localhost/moodle/behatrun1&lt;br /&gt;
#* Process number is included as suffix to $CFG-&amp;gt;behat_prefix.&lt;br /&gt;
#* Process number is suffixed to $CFG-&amp;gt;behat_dataroot.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Initialise acceptance test environment ==&lt;br /&gt;
Before running acceptance tests, environment needs to be initialised for acceptance testing.&lt;br /&gt;
&lt;br /&gt;
=== Single run ===&lt;br /&gt;
For initialising acceptance tests for single run, above command is sufficient.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For initialising acceptance tests for parallel runs, you can use one of the following options&lt;br /&gt;
# &#039;&#039;&#039;-j or --parallel&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m or --maxruns&#039;&#039;&#039;  (optional) Max parallel site which should be initialised at one time. If your system is slow, then you can initialise sites in chucks.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; (optional) Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; (optional) Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;-o or --optimize-runs&#039;&#039;&#039; (optional) This option will split features with specified tags in all parallel runs, so they are executed first when parallel run gets executed.&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified themes (comma separated list of themes)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Below command will initialise moodle to run 2 parallel tests.&lt;br /&gt;
php admin/tool/behat/cli/init.php --parallel=2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 2: Running acceptance test environment ==&lt;br /&gt;
=== Single run ===&lt;br /&gt;
Run either of the following commands. 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;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behatrun/behat/behat.yml&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following optional options are available for custom run:&lt;br /&gt;
# &#039;&#039;&#039;--feature&#039;&#039;&#039; Only execute specified feature file (Absolute path of feature file).&lt;br /&gt;
# &#039;&#039;&#039;--suite&#039;&#039;&#039; Features for specified theme will be executed.&lt;br /&gt;
# &#039;&#039;&#039;--replace&#039;&#039;&#039; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified theme&#039;s (comma separated list)&lt;br /&gt;
# Behat options can be passed for filtering features/scenarios:&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;--tags=&amp;quot;~@javascript&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific scenario, use the Behat name option to run it, &#039;&#039;&#039;--name=&amp;quot;Filter user accounts by role and cohort&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific feature file, use the Behat feature option to run it, &#039;&#039;&#039;--feature=&amp;quot;/PATH/TO/MOODLE/admin/tests/behat/filter_users.feature&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Common options for running tests ===&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 a 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;@_file_upload&#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;@_alert&#039;&#039;&#039;: All the tests that involves Javascript dialogs (alerts, confirms...) are using a feature that is OS-dependant and out of the browser scope, so they should be tag appropriately as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_window&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_iframe&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&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;
==== Output formats ====&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=pretty --out=/path/to/pretty.txt --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Before Moodle 3.1 option for output was:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=&#039;moodle_progress,pretty&#039; --out=&#039;,/path/to/pretty.txt&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following output formats are supported:&lt;br /&gt;
# &#039;&#039;&#039;progress&#039;&#039;&#039;: Prints one character per step.&lt;br /&gt;
# &#039;&#039;&#039;pretty&#039;&#039;&#039;: Prints the feature as is.&lt;br /&gt;
# &#039;&#039;&#039;junit&#039;&#039;&#039;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: (since Moodle 3.1) Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
If you want to see the failures immediately (rather than waiting ~3 hours for all the tests to finish) then either use the -v option to output a bit more information, or change the output format using --format. Format &#039;pretty&#039; (&#039;&#039;&#039;-f pretty&#039;&#039;&#039;) is sufficient for most cases, as it outputs each step outcomes in the command line making easier to see the progress.&lt;br /&gt;
&lt;br /&gt;
== Advance usage ==&lt;br /&gt;
=== Rerun failed scenarios ===&lt;br /&gt;
With slow systems or parallel run you might see some random failures, to rerun only failed scenarios (to eliminate random failures), use --rerun option&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; --run=&amp;quot;absolute_path_to_empty_file&amp;quot; (Behat will record failed scenarios in this file, and when run again only failed scenarios will be run)&lt;br /&gt;
# &#039;&#039;&#039;Parallel run:&#039;&#039;&#039; --rerun=&amp;quot;absolute_path_to_empty_file_{runprocess}.txt --replace=&amp;quot;{runprocess}&amp;quot; ({runprocess} will be replaced with the process number for recording fails in the specific run process).&lt;br /&gt;
&#039;&#039;&#039;Since Moodle 3.1 --rerun option don&#039;t accept any value, as it is handled internally by behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Running behat with specified theme (Since Moodle 3.2) ===&lt;br /&gt;
You can run behat with any theme installed. To execute behat with specified theme use &#039;&#039;&#039;--suite={THEME_NAME}&#039;&#039;&#039; option, while running behat. By default the features in theme behat folder will be executed for the suite. But if you want to run all core features with the specific theme then initialise acceptance test with -a option. For example, &#039;&#039;&#039;-a {THEME_NAME}&#039;&#039;&#039;&lt;br /&gt;
Also if you want a specific theme for your behat tests, use the $CFG-&amp;gt;theme variable to set the theme.&lt;br /&gt;
&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
To override behat step definitions so as to run behat with specified theme, you should create a contexts within &#039;&#039;&#039;/theme/{MYTHEME}/tests/behat/&#039;&#039;&#039; with prefix behat_theme_{MYTHEME}_ and suffixed with the context being overridden. For example, if you want to override behat_mod_forum context, then you should create a class /theme/{MYTHEME}/tests/behat/mod_forum/behat_theme_{MYTHEME}_behat_mod_forum.php&lt;br /&gt;
&lt;br /&gt;
==== Blacklist behat context or features to run in theme suite ====&lt;br /&gt;
To blacklist contexts/ features to be executed by theme suite you should create a /theme/{MYTHEME}/tests/behat/blacklist.json file with following format. Following will not use step_definitions from  behat_grade and behat_navigation while running theme suite. Also, scenarios in auth/tests/behat/login.feature and grade/tests/behat/grade_hidden_items.feature won&#039;t be executed with theme suite.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;contexts&amp;quot;: [&lt;br /&gt;
    &amp;quot;behat_grade&amp;quot;,&lt;br /&gt;
    &amp;quot;behat_navigation&amp;quot;,&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;features&amp;quot;: [&lt;br /&gt;
    &amp;quot;auth/tests/behat/login.feature&amp;quot;,&lt;br /&gt;
    &amp;quot;grade/tests/behat/grade_hidden_items.feature&amp;quot;,&lt;br /&gt;
   ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
To override behat selectors in specific theme, you should create a class behat_theme_{MYTHEME}_behat_selectors in /theme/{MYTHEME}/tests/behat/behat_theme_{MYTHEME}_behat_selectors.php extending behat_selectors.&lt;br /&gt;
&lt;br /&gt;
=== Use php built in web server ===&lt;br /&gt;
You can use php built-in-web server for executing behat runs. To do so:&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 test site URL that moodle uses by default, 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 or config-dist.php)&lt;br /&gt;
# Update $CFG-&amp;gt;behat_wwwroot = localhost:8000; in config.php&lt;br /&gt;
&lt;br /&gt;
=== Define custom options for parallel runs ===&lt;br /&gt;
You can set following custom config options for parallel runs via $CFG-&amp;gt;behat_parallel_run. It&#039;s an array of options where 1st array is for 1st run and so on.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
       array (&lt;br /&gt;
           &#039;dbtype&#039; =&amp;gt; &#039;mysqli&#039;,&lt;br /&gt;
           &#039;dblibrary&#039; =&amp;gt; &#039;native&#039;,&lt;br /&gt;
           &#039;dbhost&#039; =&amp;gt; &#039;localhost&#039;,&lt;br /&gt;
           &#039;dbname&#039; =&amp;gt; &#039;moodletest&#039;,&lt;br /&gt;
           &#039;dbuser&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;dbpass&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;behat_prefix&#039; =&amp;gt; &#039;mdl_&#039;,&lt;br /&gt;
           &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;,&lt;br /&gt;
           &#039;behat_wwwroot&#039; =&amp;gt; &#039;http://127.0.0.1/moodle&#039;,&lt;br /&gt;
           &#039;behat_dataroot&#039; =&amp;gt; &#039;/home/example/bht_moodledata&#039;&lt;br /&gt;
       )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To set different selenium servers for parallel runs, you can use following. NOTE: Running parallel (headless) runs on different selenium servers avoid random focus failures.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    $CFG-&amp;gt;behat_parallel_run = array (&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;),&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
By default behat will run with Firefox browser through Selenium. By adding the following code to your config.php you can change the selected browser that is run when behat is invoked.  You will need to run php admin/tool/behat/cli/init.php for changes to take effect. Then use --profile=&#039;chrome&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;chrome&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;@javascript&#039;,&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
&lt;br /&gt;
=== Start multiple selenium servers ===&lt;br /&gt;
From command line Start selenium servers at different ports (say 4444, 4445, 4446 for 3 parallel runs)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4444 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4445 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4446&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Using Docker to start selenium server ===&lt;br /&gt;
==== What is Docker ====&lt;br /&gt;
Docker is a app container,  it&#039;s a kind of virtual machine, but only for one app, service,  so you can download&lt;br /&gt;
a docker image and run a selenium server without worry in how to configure selenium in your machine, one for chrome, others for firefox, you either don&#039;t need to install the browsers in your machine&lt;br /&gt;
To install docker follow this link; https://docs.docker.com/engine/installation/&lt;br /&gt;
&lt;br /&gt;
==== Selenium docker images ====&lt;br /&gt;
There is many docker images available,  for many browser, the complete list is in https://hub.docker.com/u/selenium/&lt;br /&gt;
for moodle you can use standalone version.&lt;br /&gt;
You can download  specific selenium version too,  for example,  for firefox,  moodle recommend selenium 2.53.1, see: [https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium What version do I need?]&lt;br /&gt;
&lt;br /&gt;
so  the command will be:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
docker run -d -p4444:4444 selenium/standalone-firefox:2.53.1-beryllium&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to see all available version click in tags.   For firefox you can find at: https://hub.docker.com/r/selenium/standalone-firefox/tags/&lt;br /&gt;
&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In config.php file you must change the $CFG-&amp;gt;behat_wwwroot=   to your network card (NIC) ip address,  you can&#039;t use &lt;br /&gt;
localhost , 127.0.0.1, ...  or selenium docker server  will fail&lt;br /&gt;
&lt;br /&gt;
== NOTE ==&lt;br /&gt;
# Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* (See http://www.installationpage.com/selenium/how-to-run-selenium-headless-firefox-in-ubuntu/ for running &#039;headless&#039; Firefox and xvfm in a server environment)&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;
&lt;br /&gt;
=== Trouble shooting ===&lt;br /&gt;
=== New step definitions or features are not executed === &lt;br /&gt;
If you are adding new tests or steps definitions update the tests list&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; For parallel runs, all options for initialising parallel runs are valid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests are failing ===&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your system&#039;s Firefox version is not compatible with the Selenium version you are running.  Please refer Working combinations to ensure you have correct [[Acceptance_testing/Browsers#Working_combinations_of_OS.2BBrowser.2Bselenium]] of them to run acceptance test.&lt;br /&gt;
&lt;br /&gt;
=== Disable acceptance test environment ===&lt;br /&gt;
if you want to prevent access to test environment&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --disable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note that if you have the HTTP_PROXY environment variable set, which you may have had to do to run composer, then you also need to set NO_PROXY=localhost.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
* Vagrant profile with Moodle and Behat preconfigured: https://github.com/mackensen/moodle-hat&lt;br /&gt;
* Docker containers for Moodle Developers and Behat: https://github.com/moodlehq/moodle-docker&lt;br /&gt;
* Docker environment with Behat preconfigured : https://github.com/tobiga/docker_moodle_environment&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53436</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53436"/>
		<updated>2017-11-24T12:53:52Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* 2. Set up Selenium */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Short version ==&lt;br /&gt;
&lt;br /&gt;
...or how I got it to work on Ubuntu and some of the problems encountered. &lt;br /&gt;
&lt;br /&gt;
You need a bunch of browsers and terminal windows open to do this.&lt;br /&gt;
&lt;br /&gt;
==== 1. Background ====  &lt;br /&gt;
&lt;br /&gt;
# I am using the desktop version of Ubuntu 17.04 so there are no issues about running this software in headless mode. Running in headless mode is not considered in this section.&lt;br /&gt;
# Moodle is version 3.3 and is a fully installed and working version using the &#039;standard&#039; Ubuntu LAMP stack.&lt;br /&gt;
&lt;br /&gt;
==== 2. Set up Selenium ====&lt;br /&gt;
&lt;br /&gt;
# Download the Selenium Standalone Server from [http://www.seleniumhq.org/download/ http://www.seleniumhq.org/download/]. It&#039;s a single JAR file, put it anywhere handy.&lt;br /&gt;
# Download the Chrome driver from [https://sites.google.com/a/chromium.org/chromedriver/ https://sites.google.com/a/chromium.org/chromedriver/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running). Try the following&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Then immediately, &#039;&amp;lt;tt&amp;gt;export DISPLAY=:99&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;default&#039; =&amp;gt; [&lt;br /&gt;
            &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;selenium2&#039; =&amp;gt; [&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;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/run.php&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# After some initial setup dots should start to go by. It&#039;s a while before Selenium is first accessed. On the Linux desktop a new Chrome window appears and the testing process &#039;remote control&#039; starts (hopefully!)&lt;br /&gt;
&lt;br /&gt;
== Prerequisite ==&lt;br /&gt;
Before initializing acceptance test environment for running behat, you should ensure:&lt;br /&gt;
# [[Acceptance_testing#Requirements Meet min. system requirements for running tests]]&lt;br /&gt;
# [[Acceptance_testing#Installation Have set min. config variable in config.php for behat]]&lt;br /&gt;
# [[Acceptance_testing#Installation Downloaded composer dependencies]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Acceptance tests (also known as behat), use [http://www.seleniumhq.org/download/ Selenium server] and can be run as:&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; In single run, only one behat run is executed. So all features are executed in this single run.&lt;br /&gt;
# &#039;&#039;&#039;Parallel runs:&#039;&#039;&#039; (Since Moodle 3.0) Parallel runs allow dev&#039;s to execute multiple behat runs together. This was introduced to get acceptance tests results faster. To achieve this:&lt;br /&gt;
#* Features are divided between multiple behat runs&lt;br /&gt;
#* Symlinks behatrun{x} (x being the run process number), are created pointing to moodle directory, so site for run 1 is accessible via https://localhost/moodle/behatrun1&lt;br /&gt;
#* Process number is included as suffix to $CFG-&amp;gt;behat_prefix.&lt;br /&gt;
#* Process number is suffixed to $CFG-&amp;gt;behat_dataroot.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Initialise acceptance test environment ==&lt;br /&gt;
Before running acceptance tests, environment needs to be initialised for acceptance testing.&lt;br /&gt;
&lt;br /&gt;
=== Single run ===&lt;br /&gt;
For initialising acceptance tests for single run, above command is sufficient.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For initialising acceptance tests for parallel runs, you can use one of the following options&lt;br /&gt;
# &#039;&#039;&#039;-j or --parallel&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m or --maxruns&#039;&#039;&#039;  (optional) Max parallel site which should be initialised at one time. If your system is slow, then you can initialise sites in chucks.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; (optional) Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; (optional) Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;-o or --optimize-runs&#039;&#039;&#039; (optional) This option will split features with specified tags in all parallel runs, so they are executed first when parallel run gets executed.&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified themes (comma separated list of themes)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Below command will initialise moodle to run 2 parallel tests.&lt;br /&gt;
php admin/tool/behat/cli/init.php --parallel=2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 2: Running acceptance test environment ==&lt;br /&gt;
=== Single run ===&lt;br /&gt;
Run either of the following commands. 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;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behatrun/behat/behat.yml&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following optional options are available for custom run:&lt;br /&gt;
# &#039;&#039;&#039;--feature&#039;&#039;&#039; Only execute specified feature file (Absolute path of feature file).&lt;br /&gt;
# &#039;&#039;&#039;--suite&#039;&#039;&#039; Features for specified theme will be executed.&lt;br /&gt;
# &#039;&#039;&#039;--replace&#039;&#039;&#039; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified theme&#039;s (comma separated list)&lt;br /&gt;
# Behat options can be passed for filtering features/scenarios:&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;--tags=&amp;quot;~@javascript&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific scenario, use the Behat name option to run it, &#039;&#039;&#039;--name=&amp;quot;Filter user accounts by role and cohort&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific feature file, use the Behat feature option to run it, &#039;&#039;&#039;--feature=&amp;quot;/PATH/TO/MOODLE/admin/tests/behat/filter_users.feature&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Common options for running tests ===&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 a 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;@_file_upload&#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;@_alert&#039;&#039;&#039;: All the tests that involves Javascript dialogs (alerts, confirms...) are using a feature that is OS-dependant and out of the browser scope, so they should be tag appropriately as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_window&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_iframe&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&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;
==== Output formats ====&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=pretty --out=/path/to/pretty.txt --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Before Moodle 3.1 option for output was:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=&#039;moodle_progress,pretty&#039; --out=&#039;,/path/to/pretty.txt&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following output formats are supported:&lt;br /&gt;
# &#039;&#039;&#039;progress&#039;&#039;&#039;: Prints one character per step.&lt;br /&gt;
# &#039;&#039;&#039;pretty&#039;&#039;&#039;: Prints the feature as is.&lt;br /&gt;
# &#039;&#039;&#039;junit&#039;&#039;&#039;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: (since Moodle 3.1) Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
If you want to see the failures immediately (rather than waiting ~3 hours for all the tests to finish) then either use the -v option to output a bit more information, or change the output format using --format. Format &#039;pretty&#039; (&#039;&#039;&#039;-f pretty&#039;&#039;&#039;) is sufficient for most cases, as it outputs each step outcomes in the command line making easier to see the progress.&lt;br /&gt;
&lt;br /&gt;
== Advance usage ==&lt;br /&gt;
=== Rerun failed scenarios ===&lt;br /&gt;
With slow systems or parallel run you might see some random failures, to rerun only failed scenarios (to eliminate random failures), use --rerun option&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; --run=&amp;quot;absolute_path_to_empty_file&amp;quot; (Behat will record failed scenarios in this file, and when run again only failed scenarios will be run)&lt;br /&gt;
# &#039;&#039;&#039;Parallel run:&#039;&#039;&#039; --rerun=&amp;quot;absolute_path_to_empty_file_{runprocess}.txt --replace=&amp;quot;{runprocess}&amp;quot; ({runprocess} will be replaced with the process number for recording fails in the specific run process).&lt;br /&gt;
&#039;&#039;&#039;Since Moodle 3.1 --rerun option don&#039;t accept any value, as it is handled internally by behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Running behat with specified theme (Since Moodle 3.2) ===&lt;br /&gt;
You can run behat with any theme installed. To execute behat with specified theme use &#039;&#039;&#039;--suite={THEME_NAME}&#039;&#039;&#039; option, while running behat. By default the features in theme behat folder will be executed for the suite. But if you want to run all core features with the specific theme then initialise acceptance test with -a option. For example, &#039;&#039;&#039;-a {THEME_NAME}&#039;&#039;&#039;&lt;br /&gt;
Also if you want a specific theme for your behat tests, use the $CFG-&amp;gt;theme variable to set the theme.&lt;br /&gt;
&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
To override behat step definitions so as to run behat with specified theme, you should create a contexts within &#039;&#039;&#039;/theme/{MYTHEME}/tests/behat/&#039;&#039;&#039; with prefix behat_theme_{MYTHEME}_ and suffixed with the context being overridden. For example, if you want to override behat_mod_forum context, then you should create a class /theme/{MYTHEME}/tests/behat/mod_forum/behat_theme_{MYTHEME}_behat_mod_forum.php&lt;br /&gt;
&lt;br /&gt;
==== Blacklist behat context or features to run in theme suite ====&lt;br /&gt;
To blacklist contexts/ features to be executed by theme suite you should create a /theme/{MYTHEME}/tests/behat/blacklist.json file with following format. Following will not use step_definitions from  behat_grade and behat_navigation while running theme suite. Also, scenarios in auth/tests/behat/login.feature and grade/tests/behat/grade_hidden_items.feature won&#039;t be executed with theme suite.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;contexts&amp;quot;: [&lt;br /&gt;
    &amp;quot;behat_grade&amp;quot;,&lt;br /&gt;
    &amp;quot;behat_navigation&amp;quot;,&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;features&amp;quot;: [&lt;br /&gt;
    &amp;quot;auth/tests/behat/login.feature&amp;quot;,&lt;br /&gt;
    &amp;quot;grade/tests/behat/grade_hidden_items.feature&amp;quot;,&lt;br /&gt;
   ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
To override behat selectors in specific theme, you should create a class behat_theme_{MYTHEME}_behat_selectors in /theme/{MYTHEME}/tests/behat/behat_theme_{MYTHEME}_behat_selectors.php extending behat_selectors.&lt;br /&gt;
&lt;br /&gt;
=== Use php built in web server ===&lt;br /&gt;
You can use php built-in-web server for executing behat runs. To do so:&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 test site URL that moodle uses by default, 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 or config-dist.php)&lt;br /&gt;
# Update $CFG-&amp;gt;behat_wwwroot = localhost:8000; in config.php&lt;br /&gt;
&lt;br /&gt;
=== Define custom options for parallel runs ===&lt;br /&gt;
You can set following custom config options for parallel runs via $CFG-&amp;gt;behat_parallel_run. It&#039;s an array of options where 1st array is for 1st run and so on.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
       array (&lt;br /&gt;
           &#039;dbtype&#039; =&amp;gt; &#039;mysqli&#039;,&lt;br /&gt;
           &#039;dblibrary&#039; =&amp;gt; &#039;native&#039;,&lt;br /&gt;
           &#039;dbhost&#039; =&amp;gt; &#039;localhost&#039;,&lt;br /&gt;
           &#039;dbname&#039; =&amp;gt; &#039;moodletest&#039;,&lt;br /&gt;
           &#039;dbuser&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;dbpass&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;behat_prefix&#039; =&amp;gt; &#039;mdl_&#039;,&lt;br /&gt;
           &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;,&lt;br /&gt;
           &#039;behat_wwwroot&#039; =&amp;gt; &#039;http://127.0.0.1/moodle&#039;,&lt;br /&gt;
           &#039;behat_dataroot&#039; =&amp;gt; &#039;/home/example/bht_moodledata&#039;&lt;br /&gt;
       )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To set different selenium servers for parallel runs, you can use following. NOTE: Running parallel (headless) runs on different selenium servers avoid random focus failures.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    $CFG-&amp;gt;behat_parallel_run = array (&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;),&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
By default behat will run with Firefox browser through Selenium. By adding the following code to your config.php you can change the selected browser that is run when behat is invoked.  You will need to run php admin/tool/behat/cli/init.php for changes to take effect. Then use --profile=&#039;chrome&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;chrome&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;@javascript&#039;,&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
&lt;br /&gt;
=== Start multiple selenium servers ===&lt;br /&gt;
From command line Start selenium servers at different ports (say 4444, 4445, 4446 for 3 parallel runs)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4444 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4445 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4446&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Using Docker to start selenium server ===&lt;br /&gt;
==== What is Docker ====&lt;br /&gt;
Docker is a app container,  it&#039;s a kind of virtual machine, but only for one app, service,  so you can download&lt;br /&gt;
a docker image and run a selenium server without worry in how to configure selenium in your machine, one for chrome, others for firefox, you either don&#039;t need to install the browsers in your machine&lt;br /&gt;
To install docker follow this link; https://docs.docker.com/engine/installation/&lt;br /&gt;
&lt;br /&gt;
==== Selenium docker images ====&lt;br /&gt;
There is many docker images available,  for many browser, the complete list is in https://hub.docker.com/u/selenium/&lt;br /&gt;
for moodle you can use standalone version.&lt;br /&gt;
You can download  specific selenium version too,  for example,  for firefox,  moodle recommend selenium 2.53.1, see: [https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium What version do I need?]&lt;br /&gt;
&lt;br /&gt;
so  the command will be:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
docker run -d -p4444:4444 selenium/standalone-firefox:2.53.1-beryllium&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to see all available version click in tags.   For firefox you can find at: https://hub.docker.com/r/selenium/standalone-firefox/tags/&lt;br /&gt;
&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In config.php file you must change the $CFG-&amp;gt;behat_wwwroot=   to your network card (NIC) ip address,  you can&#039;t use &lt;br /&gt;
localhost , 127.0.0.1, ...  or selenium docker server  will fail&lt;br /&gt;
&lt;br /&gt;
== NOTE ==&lt;br /&gt;
# Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* (See http://www.installationpage.com/selenium/how-to-run-selenium-headless-firefox-in-ubuntu/ for running &#039;headless&#039; Firefox and xvfm in a server environment)&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;
&lt;br /&gt;
=== Trouble shooting ===&lt;br /&gt;
=== New step definitions or features are not executed === &lt;br /&gt;
If you are adding new tests or steps definitions update the tests list&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; For parallel runs, all options for initialising parallel runs are valid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests are failing ===&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your system&#039;s Firefox version is not compatible with the Selenium version you are running.  Please refer Working combinations to ensure you have correct [[Acceptance_testing/Browsers#Working_combinations_of_OS.2BBrowser.2Bselenium]] of them to run acceptance test.&lt;br /&gt;
&lt;br /&gt;
=== Disable acceptance test environment ===&lt;br /&gt;
if you want to prevent access to test environment&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --disable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note that if you have the HTTP_PROXY environment variable set, which you may have had to do to run composer, then you also need to set NO_PROXY=localhost.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
* Vagrant profile with Moodle and Behat preconfigured: https://github.com/mackensen/moodle-hat&lt;br /&gt;
* Docker containers for Moodle Developers and Behat: https://github.com/moodlehq/moodle-docker&lt;br /&gt;
* Docker environment with Behat preconfigured : https://github.com/tobiga/docker_moodle_environment&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53435</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53435"/>
		<updated>2017-11-24T12:53:23Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* 2. Set up Selenium */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Short version ==&lt;br /&gt;
&lt;br /&gt;
...or how I got it to work on Ubuntu and some of the problems encountered. &lt;br /&gt;
&lt;br /&gt;
You need a bunch of browsers and terminal windows open to do this.&lt;br /&gt;
&lt;br /&gt;
==== 1. Background ====  &lt;br /&gt;
&lt;br /&gt;
# I am using the desktop version of Ubuntu 17.04 so there are no issues about running this software in headless mode. Running in headless mode is not considered in this section.&lt;br /&gt;
# Moodle is version 3.3 and is a fully installed and working version using the &#039;standard&#039; Ubuntu LAMP stack.&lt;br /&gt;
&lt;br /&gt;
==== 2. Set up Selenium ====&lt;br /&gt;
&lt;br /&gt;
# Download the Selenium Standalone Server from [http://www.seleniumhq.org/download/ http://www.seleniumhq.org/download/]. It&#039;s a single JAR file, put it anywhere handy.&lt;br /&gt;
# Download the Chrome driver from [https://sites.google.com/a/chromium.org/chromedriver/ https://sites.google.com/a/chromium.org/chromedriver/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running). Try the following&lt;br /&gt;
# Run &#039;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&#039;&lt;br /&gt;
# Then immediately, &#039;export DISPLAY=:99&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;default&#039; =&amp;gt; [&lt;br /&gt;
            &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;selenium2&#039; =&amp;gt; [&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;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/run.php&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# After some initial setup dots should start to go by. It&#039;s a while before Selenium is first accessed. On the Linux desktop a new Chrome window appears and the testing process &#039;remote control&#039; starts (hopefully!)&lt;br /&gt;
&lt;br /&gt;
== Prerequisite ==&lt;br /&gt;
Before initializing acceptance test environment for running behat, you should ensure:&lt;br /&gt;
# [[Acceptance_testing#Requirements Meet min. system requirements for running tests]]&lt;br /&gt;
# [[Acceptance_testing#Installation Have set min. config variable in config.php for behat]]&lt;br /&gt;
# [[Acceptance_testing#Installation Downloaded composer dependencies]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Acceptance tests (also known as behat), use [http://www.seleniumhq.org/download/ Selenium server] and can be run as:&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; In single run, only one behat run is executed. So all features are executed in this single run.&lt;br /&gt;
# &#039;&#039;&#039;Parallel runs:&#039;&#039;&#039; (Since Moodle 3.0) Parallel runs allow dev&#039;s to execute multiple behat runs together. This was introduced to get acceptance tests results faster. To achieve this:&lt;br /&gt;
#* Features are divided between multiple behat runs&lt;br /&gt;
#* Symlinks behatrun{x} (x being the run process number), are created pointing to moodle directory, so site for run 1 is accessible via https://localhost/moodle/behatrun1&lt;br /&gt;
#* Process number is included as suffix to $CFG-&amp;gt;behat_prefix.&lt;br /&gt;
#* Process number is suffixed to $CFG-&amp;gt;behat_dataroot.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Initialise acceptance test environment ==&lt;br /&gt;
Before running acceptance tests, environment needs to be initialised for acceptance testing.&lt;br /&gt;
&lt;br /&gt;
=== Single run ===&lt;br /&gt;
For initialising acceptance tests for single run, above command is sufficient.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For initialising acceptance tests for parallel runs, you can use one of the following options&lt;br /&gt;
# &#039;&#039;&#039;-j or --parallel&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m or --maxruns&#039;&#039;&#039;  (optional) Max parallel site which should be initialised at one time. If your system is slow, then you can initialise sites in chucks.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; (optional) Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; (optional) Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &#039;&#039;&#039;-o or --optimize-runs&#039;&#039;&#039; (optional) This option will split features with specified tags in all parallel runs, so they are executed first when parallel run gets executed.&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified themes (comma separated list of themes)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Below command will initialise moodle to run 2 parallel tests.&lt;br /&gt;
php admin/tool/behat/cli/init.php --parallel=2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 2: Running acceptance test environment ==&lt;br /&gt;
=== Single run ===&lt;br /&gt;
Run either of the following commands. 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;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /path/to/your/CFG_behat_dataroot/behatrun/behat/behat.yml&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/run.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following optional options are available for custom run:&lt;br /&gt;
# &#039;&#039;&#039;--feature&#039;&#039;&#039; Only execute specified feature file (Absolute path of feature file).&lt;br /&gt;
# &#039;&#039;&#039;--suite&#039;&#039;&#039; Features for specified theme will be executed.&lt;br /&gt;
# &#039;&#039;&#039;--replace&#039;&#039;&#039; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &#039;&#039;&#039;--fromrun&#039;&#039;&#039; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;--torun&#039;&#039;&#039; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
# &#039;&#039;&#039;-a or --add-core-features-to-theme&#039;&#039;&#039; (optional) Since Moodle 3.2. Use this option to add all core features to specified theme&#039;s (comma separated list)&lt;br /&gt;
# Behat options can be passed for filtering features/scenarios:&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;--tags=&amp;quot;~@javascript&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific scenario, use the Behat name option to run it, &#039;&#039;&#039;--name=&amp;quot;Filter user accounts by role and cohort&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
#* In case you want to run specific feature file, use the Behat feature option to run it, &#039;&#039;&#039;--feature=&amp;quot;/PATH/TO/MOODLE/admin/tests/behat/filter_users.feature&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Common options for running tests ===&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 a 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;@_file_upload&#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;@_alert&#039;&#039;&#039;: All the tests that involves Javascript dialogs (alerts, confirms...) are using a feature that is OS-dependant and out of the browser scope, so they should be tag appropriately as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_window&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &#039;&#039;&#039;@_switch_iframe&#039;&#039;&#039;: All the tests that are using the &#039;&#039;&#039;I switch to &amp;quot;NAME&amp;quot; window&#039;&#039;&#039; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&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;
==== Output formats ====&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=pretty --out=/path/to/pretty.txt --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Before Moodle 3.1 option for output was:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--format=&#039;moodle_progress,pretty&#039; --out=&#039;,/path/to/pretty.txt&#039;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Following output formats are supported:&lt;br /&gt;
# &#039;&#039;&#039;progress&#039;&#039;&#039;: Prints one character per step.&lt;br /&gt;
# &#039;&#039;&#039;pretty&#039;&#039;&#039;: Prints the feature as is.&lt;br /&gt;
# &#039;&#039;&#039;junit&#039;&#039;&#039;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: (since Moodle 3.1) Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
If you want to see the failures immediately (rather than waiting ~3 hours for all the tests to finish) then either use the -v option to output a bit more information, or change the output format using --format. Format &#039;pretty&#039; (&#039;&#039;&#039;-f pretty&#039;&#039;&#039;) is sufficient for most cases, as it outputs each step outcomes in the command line making easier to see the progress.&lt;br /&gt;
&lt;br /&gt;
== Advance usage ==&lt;br /&gt;
=== Rerun failed scenarios ===&lt;br /&gt;
With slow systems or parallel run you might see some random failures, to rerun only failed scenarios (to eliminate random failures), use --rerun option&lt;br /&gt;
# &#039;&#039;&#039;Single run:&#039;&#039;&#039; --run=&amp;quot;absolute_path_to_empty_file&amp;quot; (Behat will record failed scenarios in this file, and when run again only failed scenarios will be run)&lt;br /&gt;
# &#039;&#039;&#039;Parallel run:&#039;&#039;&#039; --rerun=&amp;quot;absolute_path_to_empty_file_{runprocess}.txt --replace=&amp;quot;{runprocess}&amp;quot; ({runprocess} will be replaced with the process number for recording fails in the specific run process).&lt;br /&gt;
&#039;&#039;&#039;Since Moodle 3.1 --rerun option don&#039;t accept any value, as it is handled internally by behat&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Running behat with specified theme (Since Moodle 3.2) ===&lt;br /&gt;
You can run behat with any theme installed. To execute behat with specified theme use &#039;&#039;&#039;--suite={THEME_NAME}&#039;&#039;&#039; option, while running behat. By default the features in theme behat folder will be executed for the suite. But if you want to run all core features with the specific theme then initialise acceptance test with -a option. For example, &#039;&#039;&#039;-a {THEME_NAME}&#039;&#039;&#039;&lt;br /&gt;
Also if you want a specific theme for your behat tests, use the $CFG-&amp;gt;theme variable to set the theme.&lt;br /&gt;
&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
To override behat step definitions so as to run behat with specified theme, you should create a contexts within &#039;&#039;&#039;/theme/{MYTHEME}/tests/behat/&#039;&#039;&#039; with prefix behat_theme_{MYTHEME}_ and suffixed with the context being overridden. For example, if you want to override behat_mod_forum context, then you should create a class /theme/{MYTHEME}/tests/behat/mod_forum/behat_theme_{MYTHEME}_behat_mod_forum.php&lt;br /&gt;
&lt;br /&gt;
==== Blacklist behat context or features to run in theme suite ====&lt;br /&gt;
To blacklist contexts/ features to be executed by theme suite you should create a /theme/{MYTHEME}/tests/behat/blacklist.json file with following format. Following will not use step_definitions from  behat_grade and behat_navigation while running theme suite. Also, scenarios in auth/tests/behat/login.feature and grade/tests/behat/grade_hidden_items.feature won&#039;t be executed with theme suite.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;contexts&amp;quot;: [&lt;br /&gt;
    &amp;quot;behat_grade&amp;quot;,&lt;br /&gt;
    &amp;quot;behat_navigation&amp;quot;,&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;features&amp;quot;: [&lt;br /&gt;
    &amp;quot;auth/tests/behat/login.feature&amp;quot;,&lt;br /&gt;
    &amp;quot;grade/tests/behat/grade_hidden_items.feature&amp;quot;,&lt;br /&gt;
   ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
To override behat selectors in specific theme, you should create a class behat_theme_{MYTHEME}_behat_selectors in /theme/{MYTHEME}/tests/behat/behat_theme_{MYTHEME}_behat_selectors.php extending behat_selectors.&lt;br /&gt;
&lt;br /&gt;
=== Use php built in web server ===&lt;br /&gt;
You can use php built-in-web server for executing behat runs. To do so:&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 test site URL that moodle uses by default, 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 or config-dist.php)&lt;br /&gt;
# Update $CFG-&amp;gt;behat_wwwroot = localhost:8000; in config.php&lt;br /&gt;
&lt;br /&gt;
=== Define custom options for parallel runs ===&lt;br /&gt;
You can set following custom config options for parallel runs via $CFG-&amp;gt;behat_parallel_run. It&#039;s an array of options where 1st array is for 1st run and so on.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
       array (&lt;br /&gt;
           &#039;dbtype&#039; =&amp;gt; &#039;mysqli&#039;,&lt;br /&gt;
           &#039;dblibrary&#039; =&amp;gt; &#039;native&#039;,&lt;br /&gt;
           &#039;dbhost&#039; =&amp;gt; &#039;localhost&#039;,&lt;br /&gt;
           &#039;dbname&#039; =&amp;gt; &#039;moodletest&#039;,&lt;br /&gt;
           &#039;dbuser&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;dbpass&#039; =&amp;gt; &#039;moodle&#039;,&lt;br /&gt;
           &#039;behat_prefix&#039; =&amp;gt; &#039;mdl_&#039;,&lt;br /&gt;
           &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;,&lt;br /&gt;
           &#039;behat_wwwroot&#039; =&amp;gt; &#039;http://127.0.0.1/moodle&#039;,&lt;br /&gt;
           &#039;behat_dataroot&#039; =&amp;gt; &#039;/home/example/bht_moodledata&#039;&lt;br /&gt;
       )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To set different selenium servers for parallel runs, you can use following. NOTE: Running parallel (headless) runs on different selenium servers avoid random focus failures.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    $CFG-&amp;gt;behat_parallel_run = array (&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;),&lt;br /&gt;
        array (&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;),&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
By default behat will run with Firefox browser through Selenium. By adding the following code to your config.php you can change the selected browser that is run when behat is invoked.  You will need to run php admin/tool/behat/cli/init.php for changes to take effect. Then use --profile=&#039;chrome&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;chrome&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;@javascript&#039;,&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
&lt;br /&gt;
=== Start multiple selenium servers ===&lt;br /&gt;
From command line Start selenium servers at different ports (say 4444, 4445, 4446 for 3 parallel runs)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4444 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4445 &amp;amp;&lt;br /&gt;
java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port 4446&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Using Docker to start selenium server ===&lt;br /&gt;
==== What is Docker ====&lt;br /&gt;
Docker is a app container,  it&#039;s a kind of virtual machine, but only for one app, service,  so you can download&lt;br /&gt;
a docker image and run a selenium server without worry in how to configure selenium in your machine, one for chrome, others for firefox, you either don&#039;t need to install the browsers in your machine&lt;br /&gt;
To install docker follow this link; https://docs.docker.com/engine/installation/&lt;br /&gt;
&lt;br /&gt;
==== Selenium docker images ====&lt;br /&gt;
There is many docker images available,  for many browser, the complete list is in https://hub.docker.com/u/selenium/&lt;br /&gt;
for moodle you can use standalone version.&lt;br /&gt;
You can download  specific selenium version too,  for example,  for firefox,  moodle recommend selenium 2.53.1, see: [https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium What version do I need?]&lt;br /&gt;
&lt;br /&gt;
so  the command will be:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
docker run -d -p4444:4444 selenium/standalone-firefox:2.53.1-beryllium&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
to see all available version click in tags.   For firefox you can find at: https://hub.docker.com/r/selenium/standalone-firefox/tags/&lt;br /&gt;
&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In config.php file you must change the $CFG-&amp;gt;behat_wwwroot=   to your network card (NIC) ip address,  you can&#039;t use &lt;br /&gt;
localhost , 127.0.0.1, ...  or selenium docker server  will fail&lt;br /&gt;
&lt;br /&gt;
== NOTE ==&lt;br /&gt;
# Start the Selenium server (in case you want to run tests that involves Javascript)&lt;br /&gt;
#* (See http://www.installationpage.com/selenium/how-to-run-selenium-headless-firefox-in-ubuntu/ for running &#039;headless&#039; Firefox and xvfm in a server environment)&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;
&lt;br /&gt;
=== Trouble shooting ===&lt;br /&gt;
=== New step definitions or features are not executed === &lt;br /&gt;
If you are adding new tests or steps definitions update the tests list&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; For parallel runs, all options for initialising parallel runs are valid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tests are failing ===&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your system&#039;s Firefox version is not compatible with the Selenium version you are running.  Please refer Working combinations to ensure you have correct [[Acceptance_testing/Browsers#Working_combinations_of_OS.2BBrowser.2Bselenium]] of them to run acceptance test.&lt;br /&gt;
&lt;br /&gt;
=== Disable acceptance test environment ===&lt;br /&gt;
if you want to prevent access to test environment&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --disable&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note that if you have the HTTP_PROXY environment variable set, which you may have had to do to run composer, then you also need to set NO_PROXY=localhost.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
* Vagrant profile with Moodle and Behat preconfigured: https://github.com/mackensen/moodle-hat&lt;br /&gt;
* Docker containers for Moodle Developers and Behat: https://github.com/moodlehq/moodle-docker&lt;br /&gt;
* Docker environment with Behat preconfigured : https://github.com/tobiga/docker_moodle_environment&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=53232</id>
		<title>Templates</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=53232"/>
		<updated>2017-11-13T13:48:56Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* How do I call a template from php? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Templates =&lt;br /&gt;
&lt;br /&gt;
== What is a template? ==&lt;br /&gt;
A template is an alternative to writing blocks of HTML directly in javascript / php by concatenating strings. The end result is the same, but templates have a number of advantages:&lt;br /&gt;
* It is easier to see the final result of the template because the code for a template is very close to what the final HTML will look like&lt;br /&gt;
* Because the templating language is intentionally limited, it is hard to introduce complex logic into a template. This makes it far easier for a theme designer to override a template, without breaking the logic&lt;br /&gt;
* Templates can be rendered from javascript. This allows ajax operations to re-render a portion of the page.&lt;br /&gt;
&lt;br /&gt;
== How do I write a template? ==&lt;br /&gt;
Templates are written in a language called &amp;quot;[http://mustache.github.io/mustache.5.html Mustache]&amp;quot;. Mustache is written as HTML with additional tags used to format the display of the data. Mustache tags are made of 2 opening and closing curly braces &amp;lt;code xml&amp;gt;{{tag}}&amp;lt;/code&amp;gt;. There are a few variations of these tags that behave differently.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{raiden}}&amp;lt;/code&amp;gt; This is a simple variable substitution. The variable named &amp;quot;raiden&amp;quot; will be searched for in the current context (and any parent contexts) and when a value is found, the entire tag will be replaced by the variable (HTML escaped).&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{{galaga}}}&amp;lt;/code&amp;gt; This is an unescaped variable substitution. Instead of escaping the variable before replacing it in the template, the variable is included raw. This is useful when the variable contains a block of HTML (for example).&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{#lemmings}} jump off cliff {{/lemmings}}&amp;lt;/code&amp;gt; These are opening and closing section tags. If the lemmings variable exists and evaluates to &amp;quot;not false&amp;quot; value, the variable is pushed on the stack, the contents of the section are parsed and included in the result. If the variable does not exist, or evaluates to false - the section will be skipped. If the variable lemmings evaluates to an array, the section will be repeated for each item in the array with the items of the array on the context. This is how to output a list.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{^lemmings}} enjoy view {{/lemmings}}&amp;lt;/code&amp;gt; Equivalent of &amp;quot;if-not&amp;quot; block, there is no &amp;quot;else&amp;quot; in mustache.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{&amp;gt; franken_style/name_of_template }}&amp;lt;/code&amp;gt; This is a partial. Think of it like an include. Templates can include other templates using this tag. In the called template, the data it sees (for including values is the same as the data available where the partial is included.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{$blockvar}} ... {{/blockvar}}&amp;lt;/code&amp;gt; This is a block variable. It defines a section of the template that can be overridden when it&#039;s included in another template.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{&amp;lt; template_name}} ... {{/template_name}}&amp;lt;/code&amp;gt; This is similar to including a partial but specifically indicates that you&#039;d like to override one or more block variables defined within the template you&#039;re including. You can override the block variables by defining a block variable within these tags that matches the name of the block variable you&#039;d like to override in the included template.&lt;br /&gt;
&lt;br /&gt;
So - putting this all together:&lt;br /&gt;
&lt;br /&gt;
recipe.mustache&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;h3&amp;gt;{{recipename}}&amp;lt;/h3&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;{{description}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;h4&amp;gt;Ingredients&amp;lt;/h4&amp;gt;&lt;br /&gt;
&amp;lt;ol&amp;gt;&lt;br /&gt;
{{#ingredients}}&lt;br /&gt;
&amp;lt;li&amp;gt;{{.}}&amp;lt;/li&amp;gt;&lt;br /&gt;
{{/ingredients}}&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
&amp;lt;h4&amp;gt;Steps&amp;lt;/h4&amp;gt;&lt;br /&gt;
&amp;lt;ol&amp;gt;&lt;br /&gt;
{{#steps}}&lt;br /&gt;
&amp;lt;li&amp;gt;{{{.}}}&amp;lt;/li&amp;gt;&lt;br /&gt;
{{/steps}}&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
{{ &amp;gt; ratethisrecipe }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When given this data:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  recipename: &amp;quot;Cheese sandwich&amp;quot;,&lt;br /&gt;
  description: &amp;quot;Who doesn&#039;t like a good cheese sandwich?&amp;quot;,&lt;br /&gt;
  ingredients: [&amp;quot;bread&amp;quot;, &amp;quot;cheese&amp;quot;, &amp;quot;butter&amp;quot;],&lt;br /&gt;
  steps: [&amp;quot;&amp;lt;p&amp;gt;Step 1 is to spread the butter on the bread&amp;lt;/p&amp;gt;&amp;quot;, &amp;quot;&amp;lt;p&amp;gt;Step 2 is to put the cheese &amp;amp;quot;in&amp;amp;quot; the bread (not on top, or underneath)&amp;lt;/p&amp;gt;&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Gives this: &amp;lt;span style=&amp;quot;font-size:4em&amp;quot;&amp;gt;😋&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
More info - there are much clearer explanations of templates on the [http://mustache.github.io/mustache.5.html Mustache] website. Try reading those pages &amp;quot;before&amp;quot; posting on stack overflow :) .&lt;br /&gt;
&lt;br /&gt;
=== Blocks (Moodle 3.0 onwards) ===&lt;br /&gt;
Blocks are a feature of Mustache that deserves a special mention. The are used as a form of inheritance - and are crucial to building a library of re-usable templates. To make use of &amp;quot;blocks&amp;quot; you define a parent template with replaceable sections. Each of those sections is marked with a &amp;quot;blocks&amp;quot; tag like this (A blocks tag looks like a regular tag, but the variable name is preceded with $):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;section&amp;gt;&lt;br /&gt;
&amp;lt;h1&amp;gt;{{$sectionheading}}Default heading{{/sectionheading}}&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
{{$content}}Content for section{{/content}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/section&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now - wherever I need to re-use this template I can include it and replace the content of those sections at the same time. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{&amp;lt; section}}&lt;br /&gt;
{{$sectionheading}}Latest News{{/sectionheading}}&lt;br /&gt;
{{$content}}Nothing happened today - sorry!{{/content}}&lt;br /&gt;
{{/section}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Notice that when I include a template and I want to make use of blocks - the include tag points the other way:&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{&amp;lt; section}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Not&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{&amp;gt; section}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Blocks support looping and many other cool things - for more info see https://github.com/bobthecow/mustache.php/wiki/BLOCKS-pragma&lt;br /&gt;
&lt;br /&gt;
== Where do I put my templates? ==&lt;br /&gt;
&lt;br /&gt;
Templates go in the &amp;lt;componentdir&amp;gt;/templates folder and must have a .mustache file extension. When loading templates the template name is &amp;lt;componentname&amp;gt;/&amp;lt;filename&amp;gt; (no file extension). &lt;br /&gt;
&lt;br /&gt;
So &amp;quot;mod_lesson/timer&amp;quot; would load the template at mod/lesson/templates/timer.mustache.&lt;br /&gt;
&lt;br /&gt;
Note: Do not try and put your templates in sub folders under the &amp;quot;/templates&amp;quot; directory. This is not supported and will not work.&lt;br /&gt;
&lt;br /&gt;
== How do I call a template from javascript? ==&lt;br /&gt;
&lt;br /&gt;
Rendering a template from javascript is fairly easy. There is a new AMD module that can load/cache and render a template for you. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// This is AMD code for loading the &amp;quot;core/templates&amp;quot; module. see [Javascript Modules].&lt;br /&gt;
require([&#039;core/templates&#039;], function(templates) {&lt;br /&gt;
&lt;br /&gt;
    // This will be the context for our template. So {{name}} in the template will resolve to &amp;quot;Tweety bird&amp;quot;.&lt;br /&gt;
    var context = { name: &#039;Tweety bird&#039;, intelligence: 2 };&lt;br /&gt;
&lt;br /&gt;
    // This will call the function to load and render our template. &lt;br /&gt;
    templates.render(&#039;block_looneytunes/profile&#039;, context);&lt;br /&gt;
&lt;br /&gt;
    // It returns a promise that needs to be resoved.&lt;br /&gt;
            .then(function(html, js) {&lt;br /&gt;
                // Here eventually I have my compiled template, and any javascript that it generated.&lt;br /&gt;
                // The templates object has append, prepend and replace functions.&lt;br /&gt;
                templates.appendNodeContents(&#039;.block_looneytunes .content&#039;, source, javascript);&lt;br /&gt;
            }).fail(function(ex) {&lt;br /&gt;
                // Deal with this exception (I recommend core/notify exception function for this).&lt;br /&gt;
            });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Under the hood, this did many clever things for us. It loaded the template via an ajax call if it was not cached. It found any missing lang strings in the template and loaded them in a single ajax request, it split the JS from the HTML and returned us both in easy to use way. Read on for how to nicely deal with the javascript parameter.&lt;br /&gt;
&lt;br /&gt;
Note: with some nice chaining and sugar, we can shorten the above example quite a bit:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/templates&#039;, &#039;core/notification&#039;], function(templates, notification) {&lt;br /&gt;
    var context = { name: &#039;Tweety bird&#039;, intelligence: 2 };&lt;br /&gt;
    templates.render(&#039;block_looneytunes/profile&#039;, context)&lt;br /&gt;
        .then(doneCallback)&lt;br /&gt;
        .fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== What if a template contains javascript? ==&lt;br /&gt;
&lt;br /&gt;
Sometimes a template requires that some JS be run when it is added to the page in order to give it more features. In the template we can include blocks of javascript, but we should use a special section tag that has a &amp;quot;helper&amp;quot; method registered to handle javascript carefully. &lt;br /&gt;
&lt;br /&gt;
Example&lt;br /&gt;
profile.mustache&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;profile&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Name: {{name}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Intelligence: {{intelligence}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;], function($) {&lt;br /&gt;
    // Effects! Can we have &amp;quot;blink&amp;quot;?&lt;br /&gt;
    $(&#039;#profile&#039;).slideDown();&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If this template is rendered by PHP, the javascript is separated from the HTML, and is appended to a special section in the footer of the page &amp;quot;after&amp;quot; requirejs has loaded. This provides the optimal page loading speed. If the template is rendered by javascript, the javascript source will be passed to the &amp;quot;done&amp;quot; handler from the promise. Then, when the &amp;quot;done&amp;quot; handler has added the template to the DOM, it can call &lt;br /&gt;
&amp;lt;code javascript&amp;gt;templates.runTemplateJS(javascript);&amp;lt;/code&amp;gt; &lt;br /&gt;
which will run the javascript (by creating a new script tag and appending it to the page head).&lt;br /&gt;
&lt;br /&gt;
== What other helpers can I use? ==&lt;br /&gt;
&lt;br /&gt;
The implementation of these helpers is in classes like [https://github.com/moodle/moodle/blob/master/lib/classes/output/mustache_string_helper.php#L31 \core\output\mustache_string_helper]. This is set up in the [https://github.com/moodle/moodle/blob/master/lib/outputrenderers.php#L100 get_mustache method in renderer_base] (There should be no need to look at the implementation, unless you are interested.) If you are considering adding a new helper, it should be limited to display logic - and you need to be able to create identical helpers for the javascript and php implementations (javascript ones go in lib/amd/src/templates.js). &lt;br /&gt;
&lt;br /&gt;
=== {{# str }} ===&lt;br /&gt;
&lt;br /&gt;
There is a string helper for loading language strings.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} helloworld, mod_greeting {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first 2 parameters are the string id and the component name. So this is effectively Mustache variant of &amp;lt;code&amp;gt;get_string(&#039;helloworld&#039;, &#039;mod_greeting&#039;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The optional third parameter defines the value for the string&#039;s &amp;lt;code&amp;gt;$a&amp;lt;/code&amp;gt; placeholder:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} iscool, mod_cool, David Beckham {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example would effectively do what &amp;lt;code&amp;gt;get_string(&#039;iscool&#039;, &#039;mod_cool&#039;, &#039;David Beckham&#039;)&amp;lt;/code&amp;gt; does in Moodle PHP code.&lt;br /&gt;
&lt;br /&gt;
Variable tags are allowed to define the value of the &amp;lt;code&amp;gt;$a&amp;lt;/code&amp;gt; placeholder:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} iscool, mod_cool, {{ name }} {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For strings that accept complex placeholder, see the following section.&lt;br /&gt;
&lt;br /&gt;
=== {{# quote }} ===&lt;br /&gt;
&lt;br /&gt;
As shown in the previous section, the &amp;lt;code&amp;gt;{{# str }}&amp;lt;/code&amp;gt; helper may need complex data structures passed as the value of the &amp;lt;code&amp;gt;$a&amp;lt;/code&amp;gt; placeholder. You can use a JSON object syntax in that case:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} iscool, mod_cool, { &amp;quot;firstname&amp;quot;: &amp;quot;David&amp;quot;, &amp;quot;lastname&amp;quot;: &amp;quot;Beckham&amp;quot; } {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you wanted to use the context values instead of literal strings, you might intuitively use something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{! DO NOT DO THIS !}}&lt;br /&gt;
{{# str }} iscool, mod_cool, { &amp;quot;firstname&amp;quot;: &amp;quot;{{ firstname }}&amp;quot;, &amp;quot;lastname&amp;quot;: &amp;quot;{{ lastname }}&amp;quot; } {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There is a potential problem though. If the variable tag &amp;lt;code&amp;gt;{{ firstname }}&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;{{ lastname }}&amp;lt;/code&amp;gt; evaluates to a string containing the double quote character, that will break the JSON syntax. We need to escape the double quotes potentially appearing in the variable tags. For this, use the &amp;lt;code&amp;gt;{{# quote }}&amp;lt;/code&amp;gt; helper:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{! This is OK !}}&lt;br /&gt;
{{# str }} iscool, mod_cool, { &amp;quot;firstname&amp;quot;:  {{# quote }}{{ firstname }}{{/ quote }}, &amp;quot;lastname&amp;quot;: {{# quote }}{{ lastname }}{{/ quote }} } {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See MDL-52136 for details.&lt;br /&gt;
=== {{# pix }} ===&lt;br /&gt;
&lt;br /&gt;
There is a pix icon helper for generating pix icon tags.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# pix }} t/edit, core, Edit David Beckham {{/ pix }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first 2 parameters are the string id and the component name, the rest is the alt text for the image.&lt;br /&gt;
&lt;br /&gt;
=== {{# userdate }} (Moodle 3.3 onwards) ===&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
This mustache template helper will format unix timestamps into a given human readable date format while using the user&#039;s timezone settings configured (if any) in Moodle. The helper will accept hardcoded values, context variables, or other helpers. &lt;br /&gt;
&lt;br /&gt;
The recommended way to use this helper is to use the string helper to get one of the core Moodle formats because they have been translated into other languages so you&#039;ll get multi-lang support for free (that&#039;s a pretty good deal!).&lt;br /&gt;
&lt;br /&gt;
==== Using the string helper (recommended) ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Assuming we had a context variable named &amp;quot;time&amp;quot; set to 1293876000.&lt;br /&gt;
{{#userdate}} {{time}}, {{#str}} strftimedate {{/str}} {{/userdate}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will ask the Moodle server for the string &amp;quot;strftimedate&amp;quot; and will use the value (which in this case is &amp;quot;%d %B %Y&amp;quot;)  to format the user date. So the resulting formatted timestamp from the userdate helper will be &amp;quot;01 January 2011&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==== Using context variables ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Assuming we had a context variable named &amp;quot;time&amp;quot; set to 1293876000.&lt;br /&gt;
{{#userdate}} {{time}}, %A, %d %B %Y, %I:%M %p {{/userdate}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce &amp;quot;Saturday, 01 January 2011, 10:00 AM&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==== Using hardcoded values ====&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{#userdate}} 1293876000, %A, %d %B %Y, %I:%M %p {{/userdate}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce &amp;quot;Saturday, 01 January 2011, 10:00 AM&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== {{# shortentext }} (Moodle 3.3 onwards) ===&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
This helper can be used to shorten a large amount of text to a specified length and will append a trailing ellipsis to signify that the text has been shortened. &lt;br /&gt;
&lt;br /&gt;
The algorithm will attempt to preserve words while shortening to text. Words, for the purposes of the helper, are considered to be groups of consecutive characters broken by a space or, in the case of a multi-byte character, after the completion of the multi-byte character (rather than in the middle of the character).&lt;br /&gt;
&lt;br /&gt;
It will also attempt to preserve HTML in the text by keeping the opening and closing tags. Only text within the tags will be considered when calculating how much should be truncated to reach the desired length.&lt;br /&gt;
&lt;br /&gt;
The helper takes two comma separated arguments. The first is the desired length and the second is the text to be shortened. Both can be provided as context variables.&lt;br /&gt;
&lt;br /&gt;
==== Plain text ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{#shortentext}} 30, long text without any tags blah de blah blah blah what {{/shortentext}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
long text without any tags ...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== HTML text ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{#shortentext}} 30, &amp;lt;div class=&#039;frog&#039;&amp;gt;&amp;lt;p&amp;gt;&amp;lt;blockquote&amp;gt;Long text with tags that will be chopped off but &amp;lt;b&amp;gt;should be added back again&amp;lt;/b&amp;gt;&amp;lt;/blockquote&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/p&amp;gt; {{/shortentext}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&amp;lt;div class=&#039;frog&#039;&amp;gt;&amp;lt;p&amp;gt;&amp;lt;blockquote&amp;gt;Long text with tags that ...&amp;lt;/blockquote&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Multi-byte text ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{#shortentext}} 6, 𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟 {{/shortentext}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
𠮟𠮟𠮟...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How do I call a template from php? ==&lt;br /&gt;
&lt;br /&gt;
The templates in php are attached to the renderers. There is a renderer method &amp;quot;render_from_template($templatename, $context)&amp;quot; that does the trick.&lt;br /&gt;
&lt;br /&gt;
Note $context is nothing to do with normal Moodle &#039;contexts&#039;. It is the data passed to the template (the &#039;context&#039; mentioned in mustache documentation).&lt;br /&gt;
&lt;br /&gt;
== How do templates work with renderers? ==&lt;br /&gt;
&lt;br /&gt;
Extra care must be taken to ensure that the data passed to the context parameter is useful to the templating language. The template language cannot:&lt;br /&gt;
* Call functions&lt;br /&gt;
* Perform any boolean logic&lt;br /&gt;
* Render renderables&lt;br /&gt;
* Do capability checks&lt;br /&gt;
* Make DB queries&lt;br /&gt;
&lt;br /&gt;
So - I have &amp;quot;some&amp;quot; data in my renderable and some logic and HTML generation in my render method for that renderable - how do I refactor this to use a template?&lt;br /&gt;
&lt;br /&gt;
The first thing to note, is that you don&#039;t have to use a template if you don&#039;t want to. It just means that themers will still have to override your render method, instead of just overriding the template. But if you DO want to use a template, you will earn &amp;quot;cred&amp;quot; with themers, and you will be able to re-render parts of your interface from javascript in response to ajax requests without reloading the whole page (that&#039;s cool).&lt;br /&gt;
&lt;br /&gt;
There is a simple pattern to use to hook a template into a render method. If you make your renderable implement templatable as well as renderable - it will have to implement a new method &amp;quot;export_for_template(renderer_base $output)&amp;quot;. This method takes the data stored in the renderable and &amp;quot;flattens it&amp;quot; so it can be used in a template. If there is some nested data in the renderable (like other renderables) and they do not support templates, they can be &amp;quot;rendered&amp;quot; into the flat data structure using the renderer parameter. It should return an stdClass with properties that are only made of simple types: int, string, bool, float, stdClass or arrays of these types. Then the render method can updated to export the data and render it with the template.&lt;br /&gt;
&lt;br /&gt;
In the renderable:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 /**&lt;br /&gt;
     * Export this data so it can be used as the context for a mustache template.&lt;br /&gt;
     *&lt;br /&gt;
     * @return stdClass&lt;br /&gt;
     */&lt;br /&gt;
    public function export_for_template(renderer_base $output) {&lt;br /&gt;
        $data = new stdClass();&lt;br /&gt;
        $data-&amp;gt;canmanage = $this-&amp;gt;canmanage;&lt;br /&gt;
        $data-&amp;gt;things = array();&lt;br /&gt;
        foreach ($this-&amp;gt;things as $thing) {&lt;br /&gt;
            $data-&amp;gt;things[] = $thing-&amp;gt;to_record();&lt;br /&gt;
        }&lt;br /&gt;
        $data-&amp;gt;navigation = array();&lt;br /&gt;
        foreach ($this-&amp;gt;navigation as $button) {&lt;br /&gt;
            $data-&amp;gt;navigation[] = $output-&amp;gt;render($button);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $data;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the renderer class:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Defer to template.&lt;br /&gt;
     *&lt;br /&gt;
     * @param mywidget $widget&lt;br /&gt;
     *&lt;br /&gt;
     * @return string HTML for the page&lt;br /&gt;
     */&lt;br /&gt;
    render(mywidget $widget) {&lt;br /&gt;
        $data = $widget-&amp;gt;export_for_template($this);&lt;br /&gt;
        return $this-&amp;gt;render_from_template(&#039;mywidget&#039;, $data);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How to I override a template in my theme? ==&lt;br /&gt;
&lt;br /&gt;
Templates can be overridden a bit easier than overriding a renderer. First - find the template that you want to change. E.g. &amp;quot;mod/wiki/templates/ratingui.mustache&amp;quot;. Now, create a sub-folder under your themes &amp;quot;templates&amp;quot; directory with the component name of the plugin you are overriding. E.g &amp;quot;theme/timtam/templates/mod_wiki&amp;quot;. Finally, copy the ratingui.mustache file into the newly created &amp;quot;theme/timtam/templates/mod_wiki&amp;quot; and edit it. You should see your changes immediately if theme designer mode is on. Note: templates are cached just like CSS, so if you are not using theme designer mode you will need to purge all caches to see the latest version of an edited template. If the template you are overriding contains a documentation comment (see next section) it is recommended to remove it, it will still show the documentation in the template library.&lt;br /&gt;
&lt;br /&gt;
== Should I document my templates? ==&lt;br /&gt;
&lt;br /&gt;
Yes!!!! Theme designers need to know the limits of what they can expect to change without breaking anything. As a further benefit - your beautiful new template can be displayed in the &amp;quot;Template Library&amp;quot; tool shipped with Moodle. In order to provide nice documentation and examples for the Template Library, you should follow these conventions when documenting your template.&lt;br /&gt;
&lt;br /&gt;
=== Add a documentation comment to your template ===&lt;br /&gt;
Mustache comments look like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  {{! &lt;br /&gt;
   I am a comment.&lt;br /&gt;
   I can span multiple lines.&lt;br /&gt;
  }}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The template library will look for a mustache comment that contains this special marker as the documentation to display, and the source of an example context.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
@template component/templatename&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Useful things to include in the documentation for a template ====&lt;br /&gt;
===== Classes required for JS =====&lt;br /&gt;
This is a list of classes that are used by the javascript for this template. If removing a class from an element in the template will break the javascript, list it here.&lt;br /&gt;
&lt;br /&gt;
===== Data attributes required for JS =====&lt;br /&gt;
This is a list of data attributes (e.g. data-enhance=&amp;quot;true&amp;quot;) that are used by the javascript for this template. If removing a data attribute from an element in the template will break the javascript, list it here.&lt;br /&gt;
&lt;br /&gt;
===== Context variables required for this template =====&lt;br /&gt;
This is a description of the data that may be contained in the context that is passed to the template. Be explicit and document every attribute.&lt;br /&gt;
&lt;br /&gt;
===== Example context (JSON) =====&lt;br /&gt;
The Template Library will look for this data in your documentation comment as it allows it to render a &amp;quot;preview&amp;quot; of the template right in the Template Library. This is useful for theme designers to test all the available templates in their new theme to make sure they look nice in a new theme. It is also useful to make sure the template responds to different screen sizes, languages and devices. The format is a JSON-encoded object that is passed directly into the render method for this template. &lt;br /&gt;
&lt;br /&gt;
==== A full example ====&lt;br /&gt;
lib/templates/pix_icon.mustache&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  {{!                                                                                                                                 &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;
    @template core/pix_icon                                                                                                         &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Moodle pix_icon template.                                                                                                       &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    The purpose of this template is to render a pix_icon.                                                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Classes required for JS:                                                                                                        &lt;br /&gt;
    * none                                                                                                                          &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Data attributes required for JS:                                                                                                &lt;br /&gt;
    * none                                                                                                                          &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Context variables required for this template:                                                                                   &lt;br /&gt;
    * attributes Array of name / value pairs.                                                                                       &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Example context (json):                                                                                                         &lt;br /&gt;
    {                                                                                                                               &lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [                                                                                                             &lt;br /&gt;
            { &amp;quot;name&amp;quot;: &amp;quot;src&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;http://moodle.com/wp-content/themes/moodle/images/logo-hat2.png&amp;quot; },                          &lt;br /&gt;
            { &amp;quot;name&amp;quot;: &amp;quot;class&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;iconsmall&amp;quot; }                                                                               &lt;br /&gt;
        ]                                                                                                                           &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
  }}                                                                                                                                  &lt;br /&gt;
  &amp;lt;img {{#attributes}}{{name}}=&amp;quot;{{value}}&amp;quot; {{/attributes}}/&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Coding style for templates ==&lt;br /&gt;
This section documents some coding style guidelines to follow when writing templates. The reason for these guidelines is to promote consistency, and interoperability of the templates.&lt;br /&gt;
&lt;br /&gt;
=== Include GPL at the top of each template ===&lt;br /&gt;
&lt;br /&gt;
Templates are a form of code and it is appropriate to license them like any other code.&lt;br /&gt;
&lt;br /&gt;
===Include a documentation comment for each template===&lt;br /&gt;
&lt;br /&gt;
The exception is when you are overriding a template, if the documentation from the parent still applies, you do not need to copy it to the overridden template.&lt;br /&gt;
&lt;br /&gt;
===Use data-attributes for JS hooks===&lt;br /&gt;
&lt;br /&gt;
Data attributes are ideal for adding javascript hooks to templates because:&lt;br /&gt;
* Classes are meant for styling - theme designers should be able to change the classes at will without breaking any functionality.&lt;br /&gt;
* IDs must be unique in the page, but it is not possible to control how many times the same template might be included in the page.&lt;br /&gt;
* Data attributes can have meaningful names and can be efficiently queried with a selector&lt;br /&gt;
&lt;br /&gt;
===Avoid custom CSS for templates===&lt;br /&gt;
&lt;br /&gt;
This is not a hard rule, but a preference. We already have too much CSS in Moodle - where ever possible we should try and re-use the existing CSS instead of adding new CSS to support every new template.&lt;br /&gt;
&lt;br /&gt;
===Re-use core templates as much as possible===&lt;br /&gt;
&lt;br /&gt;
First we need to build the core set of reusable templates - but once that is in place we should always try to re-use those core templates to build interfaces. This will make Moodle more consistent, attractive and customisable.&lt;br /&gt;
&lt;br /&gt;
===Do use the CSS framework classes directly in the templates===&lt;br /&gt;
&lt;br /&gt;
We have bootstrap in core - so lets make the most of it. There is no problem using bootstrap classes in core templates, as long as the &amp;quot;base&amp;quot; theme is also tested, and an overridden template is added there if required.&lt;br /&gt;
&lt;br /&gt;
===Avoid IDs for styling or javascript===&lt;br /&gt;
&lt;br /&gt;
IDs should never evet be used for styling as they have a high CSS specificity, and so are hard to override. In addition, IDs should be unique in the page, which implies that a template could only be used once in a page. IDs are also not ideal for javascript, for the same reason (must be unique in a page).&lt;br /&gt;
&lt;br /&gt;
The only acceptable case to use an ID is you need to create a one to one connection between the JS and template. In this case use the uniqid helper to generate an ID that will not conflict with any other template on the page, and use it as part of the ID.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{uniqid}}-somethingspecific&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    callFunction(&#039;{{uniqid}}-somethingspecific&#039;);&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Follow CSS coding style===&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/CSS_coding_style&lt;br /&gt;
&lt;br /&gt;
Use hyphens as word-separators for class names. &lt;br /&gt;
Use lower case class names.&lt;br /&gt;
&lt;br /&gt;
===Wrap each template in one node with a classname that matches the template name===&lt;br /&gt;
&lt;br /&gt;
Generate a class name by combining the component and template names and separating words with underscore.&lt;br /&gt;
&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;core_user_header&amp;quot;&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;br /&gt;
[[Category:Output]]&lt;br /&gt;
&lt;br /&gt;
===Iterating over php arrays in a mustache template===&lt;br /&gt;
&lt;br /&gt;
Mustache treats hashes and arrays differently because of cross language compatibility&lt;br /&gt;
In php arrays and hashes are the same, but mustache treats them differently&lt;br /&gt;
It decides a php array is a hash and will not iterate over it if it is non 0 indexed and/or has a gap in the key numbers&lt;br /&gt;
so in short&lt;br /&gt;
you need to&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$datafortemplate-&amp;gt;mylist =  array_values($myarraywithnonnumerickeys)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
you could also use &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$datafortemplate-&amp;gt;mylist = new ArrayIterator($myarraywithnonnumerickeys);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
BUT this fails when $myarraywithnonnumerickeys is empty and you try to use&lt;br /&gt;
&amp;lt;code html5&amp;gt;&lt;br /&gt;
{{#mylist}}&lt;br /&gt;
with an array iterator if mylist is empty this block will not run&lt;br /&gt;
{{/mylist}}&lt;br /&gt;
{{^mylist}}&lt;br /&gt;
with an array iterator mylist will not run this block either because it is not quite empty&lt;br /&gt;
{{/mylist}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Test for not empty array in a mustache template===&lt;br /&gt;
&lt;br /&gt;
Short answer you can&#039;t. Include a specific property in the context like &amp;quot;hasusers&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
In php &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{{#users.0}} blah {{/users.0}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
will work but please don&#039;t use this syntax, it will break if the template is used in javascript AND it mucks up the context.&lt;br /&gt;
&lt;br /&gt;
In javascript&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{{#users.length}} blah {{/users.length}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
will work but please don&#039;t use this syntax, it will break if the template is used in php.&lt;br /&gt;
&lt;br /&gt;
===Squash whitespace in a template===&lt;br /&gt;
Sometimes whitespace is significant, for example inside a link it will show with an underline. If you want 2 mustache tags from separate lines to be rendered with no whitespace between them you can use mustache comments to squash the whitespace.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;blah&amp;quot;&amp;gt;{{!&lt;br /&gt;
}}{{icon}}{{!&lt;br /&gt;
}}{{name}}&amp;lt;/a&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How do I write core templates? ==&lt;br /&gt;
Core templates should ideally be simple generic components that can be used within other templates to create more complex page layouts. They should be flexible enough for developers and themers to easily use without having to replace the template. The templates should attempt to encapsulate some core structure for the element as well as key classes while allowing the content to be easily overridden. Ultimately we want to avoid having duplicate HTML copied from template to template where possible, particularly if the HTML element has some classes associated with it.&lt;br /&gt;
&lt;br /&gt;
Mustache relies on variables to substitute context data into the template but unfortunately it&#039;s very unlikely that the the names of the context data will match what the template is expecting for all the places that the template might be used. So in order to allow easy extensibility and avoid having to duplicate templates just to rename the variables we can wrap them in block variables which would allow the template that is including our template to replace that variable with one from it&#039;s own context inline.&lt;br /&gt;
&lt;br /&gt;
There are a few key points to keep in mind when writing a core template:&lt;br /&gt;
* Consider how your template will actually be used. Try writing a test page that uses your template to help discover some of the assumptions you might have in the template.&lt;br /&gt;
* The example context you provide in the template is mostly just for showing the template in the template library and is likely not how your template will actually be used. Most uses of the template will have a different context all together.&lt;br /&gt;
* Try to enforce a core structure but avoid enforcing a specific context. Content should be overridable.&lt;br /&gt;
* Use block variables to indicate sections of your template that people are likely to want to change. Typically where they will be wanting to substitute in their own content.&lt;br /&gt;
* Try to keep any javascript that accompanies the template as decoupled from the HTML / CSS structure of the template as possible. Instead of relying on the existence of certain HTML elements or CSS classes it is generally better to leverage data-attributes which can be added to any element.&lt;br /&gt;
&lt;br /&gt;
=== An example: tabs ===&lt;br /&gt;
Let&#039;s go through an example to illustrate how you might build a core template. For the example we&#039;ll be building a tabs template, since it&#039;s a fairly complex component that requires the use of block variables and javascript.&lt;br /&gt;
&lt;br /&gt;
First we can create a basic template to get the general structure down, let&#039;s call it tabs. Here&#039;s what it might look like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{ uniqid }}-tab-container&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ul role=&amp;quot;tablist&amp;quot; class=&amp;quot;nav nav-tabs&amp;quot;&amp;gt;&lt;br /&gt;
		{{# tabs }}&lt;br /&gt;
			&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
					data-target=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
					data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
					aria-controls=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
					aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
				&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;{{{ name }}}&amp;lt;/a&amp;gt;&lt;br /&gt;
			&amp;lt;/li&amp;gt;&lt;br /&gt;
		{{/ tabs }}&lt;br /&gt;
	&amp;lt;/ul&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;tab-content&amp;quot;&amp;gt;&lt;br /&gt;
		{{# tabs }}&lt;br /&gt;
			&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
				class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
				id=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
				{{{ content }}}&lt;br /&gt;
			&amp;lt;/div&amp;gt;&lt;br /&gt;
		{{/ tabs }}&lt;br /&gt;
	&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    require([&#039;jquery&#039;,&#039;core/tabs&#039;], function($, tabs) {&lt;br /&gt;
&lt;br /&gt;
        var container = $(&amp;quot;#{{ uniqid }}-tab-container&amp;quot;);&lt;br /&gt;
        tabs.create(container);&lt;br /&gt;
    });&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The template requires a context that looks something like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;tabs&amp;quot;: [&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab1&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Tab 1&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;This is tab 1 content &amp;lt;a href=\&amp;quot;#\&amp;quot;&amp;gt;test&amp;lt;/a&amp;gt;&amp;quot;},&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab2&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Tab 2&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;This is tab 2 content &amp;lt;a href=\&amp;quot;#\&amp;quot;&amp;gt;test&amp;lt;/a&amp;gt;&amp;quot;},&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab3&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Tab 3&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;This is tab 3 content &amp;lt;a href=\&amp;quot;#\&amp;quot;&amp;gt;test&amp;lt;/a&amp;gt;&amp;quot;}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The javascript required to power the tabs element (keyboard navigation, show / hide panels etc) is written as an AMD module and is included by the template. The javascript is a little too large to go through here, but some key points to consider when writing it are:&lt;br /&gt;
It should ideally be independent of the HTML structure, so if someone wants to completely rewrite the tabs to be different elements (e.g. buttons or a set of divs) then the same javascript can be used without needing to change it. In order to achieve this it is important to identify the key components of the template.&lt;br /&gt;
&lt;br /&gt;
In this case it is a tab list, a tab and it&#039;s content. One way to identify these components would be to inspect the structure of the DOM, for example you might say &amp;quot;find me the ul element&amp;quot; when looking for the tab list and then &amp;quot;find my the child li elements&amp;quot; to find the tabs. While this would work, it couples your javascript to the HTML structure and makes it difficult to change later. A different approach would be to use the element attributes, for example you might say &amp;quot;find my the element with the role &#039;tablist&#039;&amp;quot; to get the tab list and then &amp;quot;find me the elements with the role &#039;tab&#039;&amp;quot; to get the tabs. This allows the HTML structure to change without breaking the javascript (as long as the correct attributes are set, of course).&lt;br /&gt;
&lt;br /&gt;
Another point of consideration for this example is what class to apply to a tab when it is selected. It makes sense to just apply something like &amp;quot;active&amp;quot; in the javascript, but that once again couples it to a particular CSS framework which makes it more difficult to change without modifying the javascript. In this case I chose to add a data attribute to the element to indicate which class will be set when the tab is selected. This means the javascript doesn&#039;t have to guess what the appropriate class is, it can just get it from the template.&lt;br /&gt;
&lt;br /&gt;
Ok, so we&#039;ve got our basic template. It&#039;s time to use it! Let&#039;s say we want to create a simple user profile page that might show 2 tabs, the first tab will be the user&#039;s name and the second tab will be the user&#039;s email address (please excuse the contrived example).&lt;br /&gt;
&lt;br /&gt;
Here&#039;s what the page might look like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
    &amp;lt;header&amp;gt;&amp;lt;title&amp;gt;User Profile&amp;lt;/title&amp;gt;&amp;lt;/header&amp;gt;&lt;br /&gt;
    &amp;lt;body&amp;gt;&lt;br /&gt;
        {{&amp;lt; core/tabs }}&lt;br /&gt;
    &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That looks pretty simple! The only problem is, how do I get my content there? I would have to supply a context like this in order to display the tabs I want:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;tabs&amp;quot;: [&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab1&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Name&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;Your name is Mr. Test User.&amp;quot;},&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab2&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Email&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;Your email is testuser@example.com&amp;quot;},&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume that the context for this page doesn&#039;t match what the tabs template is expecting though (as will be the case most of the time). Let&#039;s assume the tabs template is being rendered with this context:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;:&amp;quot;Mr. Test User&amp;quot;,&lt;br /&gt;
	&amp;quot;email&amp;quot;:&amp;quot;testuser@example.com&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unfortunately, we&#039;ll almost certainly never have complete control over all of the contexts that our template will be rendered in which means we&#039;ll be expecting people to write new webservices to supply the same data in different formats every time they want to use a template. It becomes an unmanageable problem.&lt;br /&gt;
&lt;br /&gt;
Enter blocks! We can make the template more flexible by defining sections of the template that can be overriden when they are included. Pretty neat! This will allow us to enforce a certain core structure but not enforce a context on the template that is including the tabs.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s have another go at that template, this time leverging blocks:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{ uniqid }}-tab-container&amp;quot;&amp;gt;&lt;br /&gt;
	{{$ tabheader }}&lt;br /&gt;
		&amp;lt;ul role=&amp;quot;tablist&amp;quot; class=&amp;quot;nav nav-tabs&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
							data-target=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
							data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
							aria-controls=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
							aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
						&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;{{{ name }}}&amp;lt;/a&amp;gt;&lt;br /&gt;
					&amp;lt;/li&amp;gt;&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
		&amp;lt;/ul&amp;gt;&lt;br /&gt;
	{{/ tabheader }}&lt;br /&gt;
	{{$ tabbody }}&lt;br /&gt;
		&amp;lt;div class=&amp;quot;tab-content&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
						class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
						id=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
						{{{ content }}}&lt;br /&gt;
					&amp;lt;/div&amp;gt;&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		&amp;lt;/div&amp;gt;&lt;br /&gt;
	{{/ tabbody }}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    require([&#039;jquery&#039;,&#039;core/tabs&#039;], function($, tabs) {&lt;br /&gt;
&lt;br /&gt;
        var container = $(&amp;quot;#{{ uniqid }}-tab-container&amp;quot;);&lt;br /&gt;
        tabs.create(container);&lt;br /&gt;
    });&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A summary of what we&#039;ve changed:&lt;br /&gt;
* Added a $tabheader block around the tab list, in case someone wants to change the ul element to something else.&lt;br /&gt;
* Added a $tablist block around the group of tabs to allow them to be overriden on incldue.&lt;br /&gt;
* Added a $tabbody block around the content, in case someone wants to change the content elements from divs.&lt;br /&gt;
* Added a $tabcontent block around the tab variable for the content to allow the content to be overriden on inlcude.&lt;br /&gt;
&lt;br /&gt;
Now let&#039;s see what using this template looks like for your User Profile page:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
    &amp;lt;header&amp;gt;&amp;lt;title&amp;gt;User Profile&amp;lt;/title&amp;gt;&amp;lt;/header&amp;gt;&lt;br /&gt;
    &amp;lt;body&amp;gt;&lt;br /&gt;
        {{&amp;gt; core/tabs }}&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
						data-target=&amp;quot;{{ uniqid }}-tab1&amp;quot;&lt;br /&gt;
						data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
						aria-controls=&amp;quot;{{ uniqid }}-tab1&amp;quot;&lt;br /&gt;
						aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Name&amp;lt;/a&amp;gt;&lt;br /&gt;
				&amp;lt;/li&amp;gt;&lt;br /&gt;
				&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
						data-target=&amp;quot;{{ uniqid }}-tab2&amp;quot;&lt;br /&gt;
						data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
						aria-controls=&amp;quot;{{ uniqid }}-tab2&amp;quot;&lt;br /&gt;
						aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Email&amp;lt;/a&amp;gt;&lt;br /&gt;
				&amp;lt;/li&amp;gt;&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
					class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
					id=&amp;quot;{{ uniqid }}-tab1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					Your name is {{ name }}.&lt;br /&gt;
				&amp;lt;/div&amp;gt;&lt;br /&gt;
				&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
					class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
					id=&amp;quot;{{ uniqid }}-tab2&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					Your email address is {{ email }}.&lt;br /&gt;
				&amp;lt;/div&amp;gt;&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		{{/ core/tabs }}&lt;br /&gt;
    &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That looks a bit better! Now we&#039;ve been able to use the blocks to successfully change the template to use the context available to this page, we no longer need a &amp;quot;tabs&amp;quot; array with &amp;quot;name&amp;quot; and &amp;quot;content&amp;quot;. Even the javascript will continue to work because we&#039;ve kept the correct element attributes. &lt;br /&gt;
&lt;br /&gt;
We&#039;ve still got a slight problem though... In order to change the data for the template we&#039;ve had to copy &amp;amp; paste the HTML from the original template into our blocks as we do the override. While this works fine in this example, it means we don&#039;t quite get the encapsulation we want within the templates since we&#039;re leaking internal implementation details. If we ever wanted to change the CSS framework we use for Moodle (say from bootstrap 2 to boostrap 3 or 4) we&#039;d have to find all the places in the code where this tabs template is used and make sure that the HTML is correct in their block overrides.&lt;br /&gt;
&lt;br /&gt;
With that in mind, let&#039;s take one more pass at this template and see if we can improve it slightly again. This time we&#039;re doing to split the template out into 3 templates.&lt;br /&gt;
&lt;br /&gt;
tabs.mustache:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{ uniqid }}-tab-container&amp;quot;&amp;gt;&lt;br /&gt;
	{{$ tabheader }}&lt;br /&gt;
		&amp;lt;ul role=&amp;quot;tablist&amp;quot; class=&amp;quot;nav nav-tabs&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					{{&amp;gt; core/tab_header_item }}		&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
		&amp;lt;/ul&amp;gt;&lt;br /&gt;
	{{/ tabheader }}&lt;br /&gt;
	{{$ tabbody }}&lt;br /&gt;
		&amp;lt;div class=&amp;quot;tab-content&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					{{&amp;gt; core/tab_content_item }}&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		&amp;lt;/div&amp;gt;&lt;br /&gt;
	{{/ tabbody }}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    require([&#039;jquery&#039;,&#039;core/tabs&#039;], function($, tabs) {&lt;br /&gt;
&lt;br /&gt;
        var container = $(&amp;quot;#{{ uniqid }}-tab-container&amp;quot;);&lt;br /&gt;
        tabs.create(container);&lt;br /&gt;
    });&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
tab_header_item.mustache&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
		data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
		aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
	&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;{{$ tabname }}{{{ name }}}{{/ tabname }}&amp;lt;/a&amp;gt;&lt;br /&gt;
&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
tab_content_item.mustache&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
	class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	{{$ tabpanelcontent }}{{{ content }}}{{/ tabpanelcontent }}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A summary of the changes:&lt;br /&gt;
* Split the template into 3, moving the tab into it&#039;s own template and the content into it&#039;s own and then including them in the tabs template.&lt;br /&gt;
* Removed the ids from the tabs and content. The javascript would be updating to assign these ids at runtime so that they don&#039;t need to be provided as part of the template context.&lt;br /&gt;
* Added a $tabname block for in the tab_header_item template to make the name flexible on import.&lt;br /&gt;
* Added a $tabpanelcontant block in the tab_content_item template to make the content flexible on import.&lt;br /&gt;
&lt;br /&gt;
Cool, so let&#039;s see what that looks like in our example now:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
    &amp;lt;header&amp;gt;&amp;lt;title&amp;gt;User Profile&amp;lt;/title&amp;gt;&amp;lt;/header&amp;gt;&lt;br /&gt;
    &amp;lt;body&amp;gt;&lt;br /&gt;
        {{&amp;gt; core/tabs }}&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				{{&amp;lt; core/tab_header_item }}&lt;br /&gt;
					{{$ tabname }}Name{{/ tabname }}&lt;br /&gt;
				{{/ core/tab_header_item }}&lt;br /&gt;
				{{&amp;lt; core/tab_header_item }}&lt;br /&gt;
					{{$ tabname }}Email{{/ tabname }}&lt;br /&gt;
				{{/ core/tab_header_item }}&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				{{&amp;lt; core/tab_content_item }}&lt;br /&gt;
					{{$ tabpanelcontent }}Your name is {{ name }}.{{/ tabpanelcontent }}&lt;br /&gt;
				{{/ core/tab_content_item }}&lt;br /&gt;
				{{&amp;lt; core/tab_content_item }}&lt;br /&gt;
					{{$ tabpanelcontent }}Your email address is {{ email }}.{{/ tabpanelcontent }}&lt;br /&gt;
				{{/ core/tab_content_item }}&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		{{/ core/tabs }}&lt;br /&gt;
    &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And we&#039;re done! After making the changes above we&#039;ve been able to keep the benefits of the previous change to allow the context changes but we&#039;ve also removed the need to copy &amp;amp; paste the HTML everywhere. Instead we&#039;re able to use the child templates with a few additional blocks defined to get the content in there.&lt;br /&gt;
&lt;br /&gt;
Now if we want to change tabs HTML or CSS frameworks we can just change the core tabs templates and this page will receive the updates for free.&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53118</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=53118"/>
		<updated>2017-10-20T14:14:49Z</updated>

		<summary type="html">&lt;p&gt;Howardsmiller: /* Minimum (getting started) module for plugins */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/. Moodle currently requires node &amp;quot;v4&amp;quot; and does not work with the latest node &amp;quot;v6&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Development mode ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing. &lt;br /&gt;
Or, &amp;quot;No define call for theme_XXX/loader&amp;quot; as you are probably missing the &#039;src&#039; folder with relevant JS files. which might happen when you turn debugging ON on a theme that was bought, without &#039;src&#039; folder :-(&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
Note: Once you have run grunt and built your code, you will then need to purge Moodle caches otherwise the changes made to your minified files may not be picked up by Moodle.&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
&lt;br /&gt;
            // Put whatever you like here. $ is available&lt;br /&gt;
            // to you as normal.&lt;br /&gt;
            $(&amp;quot;.someclass&amp;quot;).change(function() {&lt;br /&gt;
                alert(&amp;quot;It changed!!&amp;quot;);&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;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;[[Frankenstyle]]&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    return {&lt;br /&gt;
        init: function(first, last) {&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&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;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
            return factory(a0);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($, c) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&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;
2. Put the javascript into a mustache template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Call the javascript directly from php (although who would want to put javascript into php? ergh):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* [https://assets.moodlemoot.org/sites/15/20171004085436/JavaScript-AMD-with-RequireJS-presented-by-Daniel-Roperto-Catalyst.pdf JavaScript AMD with RequireJS] presented by Daniel Roperto, Catalyst. (MoodleMOOT AU 2017)&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Howardsmiller</name></author>
	</entry>
</feed>