<?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=Tim+Hunt</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=Tim+Hunt"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Tim_Hunt"/>
	<updated>2026-06-07T01:48:30Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Question_bank_improvements_for_Moodle_4.0&amp;diff=58979</id>
		<title>Question bank improvements for Moodle 4.0</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Question_bank_improvements_for_Moodle_4.0&amp;diff=58979"/>
		<updated>2021-06-18T08:12:52Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Question bank improvements for Moodle 4.0&lt;br /&gt;
|state = Development in progress&lt;br /&gt;
|tracker = MDL-70329&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=417599&lt;br /&gt;
|assignee = Project instigated by [[User:Thomas_Korner|Thomas Korner]], [[User:Luca_B%C3%B6sch|Luca Bösch]], [[User:Tim Hunt|Tim Hunt]], Antonia Bonaccorso. Implementation by Catalyst AU&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle_4.0}}&lt;br /&gt;
&lt;br /&gt;
This is a project to largely re-develop the question bank for Moodle 4.0 (if possible). The aim is to&lt;br /&gt;
&lt;br /&gt;
* add some important new features, like question versioning, and making it easier to track where each question is used.&lt;br /&gt;
* improve how the code is organised, for example by introducing a new &#039;Question bank plugin&#039; type (qbank_).&lt;br /&gt;
* and fix some long-standing bugs, for example the problems with question backup and restore.&lt;br /&gt;
&lt;br /&gt;
== Technical overview of the plans ==&lt;br /&gt;
&lt;br /&gt;
What we are planning to do is outlined in [https://moodle.org/mod/forum/discuss.php?d=417599 this forum thread]. That thread is quite long, with discussion of the plan, but the key posts which describe the plan are:&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1682674 Setting the scope]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1683127 Question bank will be made up of plugins]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1687624 List of the plugins that will be created initially]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1683186 Question bank will store the version history of each question]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1688163 How the question bank will track the places (quizzes) where questions are used]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1685143 Some initial UI wireframes]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=417599#p1705076 Question (and following discussion) about how complicated the filtering/searching system needs to be at first]&lt;br /&gt;
&lt;br /&gt;
== Project support ==&lt;br /&gt;
&lt;br /&gt;
This project is proceeding thanks to the support of [[Question_bank_improvements_for_Moodle_4.0#Contributors|a number of universities and other organisations]] who crowd-funded the development.&lt;br /&gt;
&lt;br /&gt;
== Work-in-progress ==&lt;br /&gt;
&lt;br /&gt;
In time, we will and more information here, for example links to a prototype site.&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
=== Contributors (initiators) ===&lt;br /&gt;
* The Open University&lt;br /&gt;
* ETH Zurich&lt;br /&gt;
* BFH Bern University of Applied Sciences&lt;br /&gt;
&lt;br /&gt;
=== Contributors (financially) ===&lt;br /&gt;
* Berlin Institute of Technology (Technische Universität Berlin), Germany&lt;br /&gt;
* Canton of Zurich Office for Middle and vocational schools, Switzerland&lt;br /&gt;
* Dublin City University, Ireland&lt;br /&gt;
* ETH (Swiss Federal Institute of Technology) Zurich, Department of Medicine, Switzerland&lt;br /&gt;
* Hochschule Hannover - University of Applied Sciences and Arts, Germany &amp;lt;sup&amp;gt;*&amp;lt;/sup&amp;gt;&lt;br /&gt;
* Johannes Kepler University Linz, Austria&lt;br /&gt;
* University of Applied Sciences and Arts Northwestern Switzerland, Switzerland&lt;br /&gt;
* Zurich University of Applied Sciences, Switzerland &amp;lt;sup&amp;gt;*&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Contributors (workforce) ===&lt;br /&gt;
* Catalyst Australia&lt;br /&gt;
* Yvonne Wolf&lt;br /&gt;
&lt;br /&gt;
=== Overflowing funds from the SEB deeper integration project ===&lt;br /&gt;
These institutions are credited here since they participated in the Safe Exam Browser deeper integration project. The remaining funds were allowed to be used in this project here.&lt;br /&gt;
&lt;br /&gt;
* Berlin School of Economics and Law, Germany&lt;br /&gt;
* Neubrandenburg University of Applied Sciences, Germany&lt;br /&gt;
* Ruhr-University Bochum, Germany&lt;br /&gt;
* University of Applied Sciences Upper Austria, Austria&lt;br /&gt;
&lt;br /&gt;
&amp;lt;sup&amp;gt;*&amp;lt;/sup&amp;gt; Institutions funding in both SEB deeper integration as well as Question bank improvements for Moodle 4.0 project&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Developer_meeting_June_2021&amp;diff=58961</id>
		<title>Developer meeting June 2021</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Developer_meeting_June_2021&amp;diff=58961"/>
		<updated>2021-06-08T19:26:41Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Developer meetings]] &amp;gt; June 2021 meeting &lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Date&lt;br /&gt;
| Tuesday 8 June 2021 at 07:00 UTC &lt;br /&gt;
|-&lt;br /&gt;
| Meeting recording&lt;br /&gt;
| https://moodle.org/mod/bigbluebuttonbn/view.php?id=8596&lt;br /&gt;
|-&lt;br /&gt;
| Discussion&lt;br /&gt;
| [https://moodle.org/mod/forum/discuss.php?d=422642 Developer meeting Tuesday 8 June 2021]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Agenda ==&lt;br /&gt;
&lt;br /&gt;
# Latest #moodledev news - [https://moodle.org/user/view.php?id=2356736&amp;amp;course=5 Sander Bangma] (Starts at about the beginning of the recording).&lt;br /&gt;
# JavaScript events - [https://moodle.org/user/profile.php?id=268794&amp;amp;course=5 Andrew Lyons] (Starts at about 20:45 in the recording).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
If there is any topic that you would like to present or discuss at a developer meeting, please contact [https://moodle.org/user/profile.php?id=1601 David Mudrák].&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.11_release_notes&amp;diff=58788</id>
		<title>Moodle 3.11 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.11_release_notes&amp;diff=58788"/>
		<updated>2021-05-11T16:18:39Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* For developers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Release date: Not yet released - scheduled for 17 May 2021&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.11%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.11].&lt;br /&gt;
&lt;br /&gt;
If you are upgrading from a previous version, please see [[:en:Upgrading|Upgrading]] in the user docs.&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.6 or later&lt;br /&gt;
* PHP version: minimum PHP 7.3.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.10&#039;&#039;. PHP 7.4.x is supported too. [[Moodle and PHP|PHP 8.0 support]] is being implemented (see MDL-70745) and &#039;&#039;&#039;not ready for production&#039;&#039;&#039; yet.&lt;br /&gt;
* PHP extension &#039;&#039;&#039;sodium&#039;&#039;&#039; is recommended. It will be required in Moodle 4.2. For further details, see [https://docs.moodle.org/311/en/Environment_-_PHP_extension_sodium Environment - PHP extension sodium].&lt;br /&gt;
* PHP setting &#039;&#039;&#039;max_input_vars&#039;&#039;&#039; is recommended to be &amp;gt;= 5000 for PHP 7.x installations. It&#039;s a requirement for PHP 8.x installations. For further details, see [https://docs.moodle.org/311/en/Environment_-_max_input_vars Environment - max input vars].&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.6  &lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.7 &lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 10.2.29 &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;
| 2017 (increased since Moodle 3.10)&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;
&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;
&lt;br /&gt;
&#039;&#039;Note: Moodle 3.10 does NOT support Internet Explorer 11.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Safari 7 and below has known compatibility issues with Moodle 3.10.&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://www.whatsmybrowser.org/&lt;br /&gt;
&lt;br /&gt;
==Warnings==&lt;br /&gt;
&lt;br /&gt;
* If you have a site running on MariaDB / MySQL with many users, you may experience database issues after the upgrade step 2021042100. The upgrade attempts to drop several columns from the user table (after they were converted to new user profile fields - MDL-28452). This typically requires copying all the rows into a new tablespace and rebuilding all indexes. Which may eventually lead to time-outs and &#039;&#039;&amp;quot;MySQL server has gone away&amp;quot;&#039;&#039; errors. It should be enough and safe to simply re-run the upgrade until the upgrade step 2021042100.01 finishes successfully.&lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
==Other highlights==&lt;br /&gt;
&lt;br /&gt;
===For administrators===&lt;br /&gt;
&lt;br /&gt;
* MDL-67748 - Web services configuration is now located in &#039;&#039;Site administration &amp;gt; Server&#039;&#039; section. Tokens management was improved to allow searching and filtering.&lt;br /&gt;
&lt;br /&gt;
==Security issues==&lt;br /&gt;
 &lt;br /&gt;
A number of security related issues were resolved. Details of these issues will be released after a period of approximately one week to allow system administrators to safely update to the latest version.&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
&lt;br /&gt;
===API changes===&lt;br /&gt;
&lt;br /&gt;
* MDL-45242 - Allow user profile fields to be specified as user identity fields - New code is backwards-compatible, but report-code should be updated.&lt;br /&gt;
&lt;br /&gt;
===Web service additions and updates===&lt;br /&gt;
&lt;br /&gt;
* MDL-71169 - All new external functions implementation classes should use &amp;lt;tt&amp;gt;execute&amp;lt;/tt&amp;gt; as the method name, in which case the &amp;lt;tt&amp;gt;methodname&amp;lt;/tt&amp;gt; property should not be specified in db/services.php file&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.10 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.11]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.11]]&lt;br /&gt;
[[es:Notas de Moodle 3.11]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=58601</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=58601"/>
		<updated>2021-03-26T11:35:35Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Human-readable and relative dates */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints on writing behat tests for Moodle core, and for plugins. The focus of the documentation is on behat tests for plugins. Behat Features and Scenarios are written in a natural language, and should &lt;br /&gt;
describe how a user would interact with Moodle.&lt;br /&gt;
 &lt;br /&gt;
Each test consists of several stages which are categorised by the terms &#039;&#039;&#039;Given&#039;&#039;&#039;, &#039;&#039;&#039;When&#039;&#039;&#039;, and &#039;&#039;&#039;Then&#039;&#039;&#039;:&lt;br /&gt;
* &#039;&#039;&#039;Given&#039;&#039;&#039;: These steps allow you to perform a a test set-up. Typically Given steps are used to set configuration, create users, courses and plugin instances, and generally prepare the site for testing&lt;br /&gt;
* &#039;&#039;&#039;When&#039;&#039;&#039;: When steps are used to get the test environment to the point at which you wish to test the conditions. This may include logging in, and then performing a range of actions like submitting an assignment and grading it for example&lt;br /&gt;
* &#039;&#039;&#039;Then&#039;&#039;&#039;: Then steps are used to check that your plugin behaved as expected. They typically check very simple things like if certain elements or text are visible or not. For example after submitting an assignemtn in the When stage, you may have a step which checks that a notice was shown to state that the submission was successful.&lt;br /&gt;
&lt;br /&gt;
These three stages match the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which is performed by Moodle between each test and does not need to be explicitly defined in your test.&lt;br /&gt;
&lt;br /&gt;
Note: Each test should have only one use of &#039;&#039;&#039;Given&#039;&#039;&#039;, &#039;&#039;&#039;When&#039;&#039;&#039;, and &#039;&#039;&#039;Then&#039;&#039;&#039;.&lt;br /&gt;
See MDLSITE-3778 for information and the policy decision on why you should not have multiple Given, When, and Then steps.&lt;br /&gt;
&lt;br /&gt;
Where you have several Given, When, and Then steps you should use the words &#039;&#039;&#039;And&#039;&#039;&#039;, and &#039;&#039;&#039;But&#039;&#039;&#039;, for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
Given the following user exists:&lt;br /&gt;
  | username   | ccolon             |&lt;br /&gt;
  | First name | Colin              |&lt;br /&gt;
  | Last name  | Colon              |&lt;br /&gt;
  | email      | ccolon@example.com |&lt;br /&gt;
And the following course exists:&lt;br /&gt;
  | Name      | Jump Judging (Level 1) |&lt;br /&gt;
  | Shortname | sjea1                  |&lt;br /&gt;
When I log in as &amp;quot;ccolon&amp;quot;&lt;br /&gt;
And I navigate to &amp;quot;Site home &amp;gt; Jump Judging (Level 1)&amp;quot;&lt;br /&gt;
Then I should see &amp;quot;You are not enrolled in this course&amp;quot;&lt;br /&gt;
But I should see &amp;quot;Enrol now&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to http://php.net/manual/en/datetime.formats.php.&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: http://php.net/manual/en/datetime.formats.relative.php&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in http://php.net/manual/en/function.date.php.&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
&lt;br /&gt;
===== Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&lt;br /&gt;
 And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is. When the test runs, this gets converted to an XPath expression, which is what the Behat system acutally uses to locate the right element on the page.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class. The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
The reasons you might want to do this are:&lt;br /&gt;
* It makes your tests easier to read, which makes it easier to be sure that the test is testing the right thing, and being able to read the tests helps people understand your features.&lt;br /&gt;
* If the HTML structure you output changes, then you only need to update the selector definition in one place.&lt;br /&gt;
&lt;br /&gt;
===== Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&lt;br /&gt;
 Given I am on the &amp;quot;Quiz 1&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
 Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you you needs to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about hwo this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
There are two reasons why it is good to use these steps:&lt;br /&gt;
* You are trying to test that your feature works, not Moodle navigation. In the pase we have had many occasions when Moodle navigation changed, and lots of tests failed and had to be fixed. It is better for your tests to start on your feature. (Except, perhpas, it might be appropriate to have one test for the expected method for users to navigate to your feature.)&lt;br /&gt;
* It is much faster because you load fewer irrelevant pages, and in particular the normal loggi step leaves you on the Dashboard page, which is &#039;&#039;&#039;very&#039;&#039;&#039; slow to load.&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#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;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Question_bank_improvements_for_Moodle_4.0&amp;diff=58459</id>
		<title>Question bank improvements for Moodle 4.0</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Question_bank_improvements_for_Moodle_4.0&amp;diff=58459"/>
		<updated>2021-03-09T13:20:59Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Question bank improvements for Moodle 4.0&lt;br /&gt;
|state = Development in progress&lt;br /&gt;
|tracker = MDL-70329&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=417599&lt;br /&gt;
|assignee = Project instigated by Thomas Korner, Luca Bösch, [[User:Tim Hunt|Tim Hunt]], Antonia Bonaccorso. Implementation by Catalyst AU&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
This is a project to largely re-develop the question bank for Moodle 4.0 (if possible). The aim is to&lt;br /&gt;
&lt;br /&gt;
* add some important new features, like question versionning, and making it easier to track where each question is used.&lt;br /&gt;
* improve how the code is organised, for example by introducing a new &#039;Question bank plugin&#039; type (qbank_).&lt;br /&gt;
* and fix some long-standing bugs, for example the problems with question backup and restore.&lt;br /&gt;
&lt;br /&gt;
The project has been explained in detail in [https://moodle.org/mod/forum/discuss.php?d=417599 this forum thread], so for more details, please read that.&lt;br /&gt;
&lt;br /&gt;
== Project support ==&lt;br /&gt;
&lt;br /&gt;
This project is proceeding thanks to the support of an number of universities and other organisations who crowed-funded the development.&lt;br /&gt;
&lt;br /&gt;
== Work-in-progress ==&lt;br /&gt;
&lt;br /&gt;
In time, we will and more information here, for example links to a prototype site.&lt;br /&gt;
&lt;br /&gt;
{{Moodle_4.0}}&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Question_bank_improvements_for_Moodle_4.0&amp;diff=58456</id>
		<title>Question bank improvements for Moodle 4.0</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Question_bank_improvements_for_Moodle_4.0&amp;diff=58456"/>
		<updated>2021-03-09T12:51:00Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: Created page with &amp;quot;{{Infobox Project |name = Question bank improvements for Moodle 4.0 |state = Development in progress |tracker = MDL-70329 |discussion = https://moodle.org/mod/forum/discuss.ph...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Question bank improvements for Moodle 4.0&lt;br /&gt;
|state = Development in progress&lt;br /&gt;
|tracker = MDL-70329&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=417599&lt;br /&gt;
|assignee = Project instigated by Thomas Korner, Luca Bösch, [[User:Tim Hunt|Tim Hunt]], Antonia Bonaccorso. Implementation by Catalyst AU&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
This is a project to largely re-develop the question bank for Moodle 4.0 (if possible). The aim is to&lt;br /&gt;
&lt;br /&gt;
* add some important new features, like question versionning, and making it easier to track where each question is used.&lt;br /&gt;
* improve how the code is organised, for example by introducing a new &#039;Question bank plugin&#039; type (qbank_).&lt;br /&gt;
* and fix some long-standing bugs, for example the problems with question backup and restore.&lt;br /&gt;
&lt;br /&gt;
The project has been explained in detail in [https://moodle.org/mod/forum/discuss.php?d=417599 this forum thread], so for more details, please read that.&lt;br /&gt;
&lt;br /&gt;
== Project support ==&lt;br /&gt;
&lt;br /&gt;
This project is proceeding thanks to the support of an number of universities and other organisations who crowed-funded the development.&lt;br /&gt;
&lt;br /&gt;
== Work-in-progress ==&lt;br /&gt;
&lt;br /&gt;
In time, we will and more information here, for example links to a prototype site.&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_4.0_navigation_improvements&amp;diff=58369</id>
		<title>Moodle 4.0 navigation improvements</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_4.0_navigation_improvements&amp;diff=58369"/>
		<updated>2021-02-25T14:36:52Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|state = In progress&lt;br /&gt;
|name = Update general Moodle navigation&lt;br /&gt;
|tracker = MDL-69588 (epic)&lt;br /&gt;
|discussion = [https://moodle.org/mod/forum/discuss.php?d=418328 Navigation for Moodle 4.0]&lt;br /&gt;
|assignee = Team Alpha&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 4.0}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
With the incoming release of Moodle 4.0, it is our desire to improve the user experience surrounding the navigation of courses, modules and other key areas by reducing the cognitive load upon users when navigating. These improvements will only be made to the existing primary Moodle theme: Boost. Whilst the primary focus is upon Boost it is our goal that the theme Classic will retain its current functionality.&lt;br /&gt;
The overall aim of this project is to create a simplified navigation hierarchy where:&lt;br /&gt;
* Users no longer feel overwhelmed by numerous pathways to get to a single destination&lt;br /&gt;
* Navigation pathways follow common patterns all throughout Moodle&lt;br /&gt;
* Navigation options presented to the user are contextually relevant; so that users intuitively and quickly learn how to get to where they need to be&lt;br /&gt;
* Navigation has a consistent look and feel between desktop sized viewports &amp;amp; mobile sized viewports&lt;br /&gt;
The focus of this project is on the student and teacher navigation experience.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Prototype / mock-ups ==&lt;br /&gt;
&lt;br /&gt;
=== Primary navigation ===&lt;br /&gt;
&lt;br /&gt;
Across all of Moodle the header of the site will be changed for 4.0 with important navigation elements being rendered within the header of Moodle.&lt;br /&gt;
With this change to navigation new active and hover states will be created to inform users of where they currently are and where they are navigating to at a glance.&lt;br /&gt;
&lt;br /&gt;
When the amount of navigation items exceeds the width of the viewport width then a new dynamic menu item will appear that navigation elements overflow into.&lt;br /&gt;
&lt;br /&gt;
On mobile devices it is expected that within Moodle the primary navigation will be rendered as a navigation drawer in the same fashion as the current navigation drawer.&lt;br /&gt;
&lt;br /&gt;
The image below shows a prototype for Moodle 4.0 with navigation within the header for the page, it also includes custom menu elements to demonstrate that existing functionality is being accounted for.&lt;br /&gt;
&lt;br /&gt;
==== Desktop primary navigation ====&lt;br /&gt;
&lt;br /&gt;
[[File:Primary.png]]&lt;br /&gt;
&lt;br /&gt;
==== Mobile primary navigation ====&lt;br /&gt;
&lt;br /&gt;
Primary navigation (mobile device inactive)&lt;br /&gt;
[[File:Primary_mobile_inactive.png]]&lt;br /&gt;
&lt;br /&gt;
Primary navigation (mobile device active)&lt;br /&gt;
[[File:Primary_mobile_active.png]]&lt;br /&gt;
&lt;br /&gt;
=== Secondary navigation ===&lt;br /&gt;
&lt;br /&gt;
Navigation items will now appear as tabs within the context header (Course header, Site admin header, etc...) and similar to the primary navigation the active element will have an indicator below the active tab and have hover styling that indicates the potential tab item that’ll be navigated to.&lt;br /&gt;
&lt;br /&gt;
Also similar to the primary navigation a dynamic “More” menu will be implemented to accommodate navigation elements when the viewport width reduces to a point where elements can not feasibly be shown within the menu area.&lt;br /&gt;
&lt;br /&gt;
The below image shows the secondary navigation within the page header, this will look similar to the above primary navigation image.&lt;br /&gt;
&lt;br /&gt;
==== Desktop secondary navigation ====&lt;br /&gt;
&lt;br /&gt;
[[File:Secondary.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Page header (Context header) ===&lt;br /&gt;
&lt;br /&gt;
Work here will primarily focus on user interactions with the context header such as automatic hiding and displaying of elements depending on the users&#039; scrolling and page position. Other changes will include rearranging elements, modifying font sizing, weight and spacing around the current page name.&lt;br /&gt;
&lt;br /&gt;
The below image shows the page header on a mobile device as the desktop viewport examples can be seen in the prior images.&lt;br /&gt;
&lt;br /&gt;
==== Mobile view page header ====&lt;br /&gt;
&lt;br /&gt;
[[File:Mobile_context.png]]&lt;br /&gt;
&lt;br /&gt;
== User stories ==&lt;br /&gt;
&lt;br /&gt;
These are the user stories that this project aims to address.&lt;br /&gt;
&lt;br /&gt;
=== For the student ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;Student user stories&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Acceptance criteria / confirmation&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| As a learner, I want to easily find the menu item, so that I can move between courses and activities.&lt;br /&gt;
| I should be able to easily see my list of courses in a predictable place and be able to jump from one course to another at speed.&lt;br /&gt;
|- &lt;br /&gt;
| As a learner, I want to view my grades within a course, so that I can review my grades or find actionable items&lt;br /&gt;
| Within the course context I should be able to click straight through to my grade overview&lt;br /&gt;
|- &lt;br /&gt;
| As a learner, I can want to view my grades for a forum activity, so that I can discover if my participation is satisfactory&lt;br /&gt;
| Within a forum activity I should still have the ability to view my grade using the existing forum grading functionality&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== For the course creator / teacher ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;Course creator / teacher user stories&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Acceptance criteria / confirmation&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| As a teacher, I want to easily find the menu item, so that I can move between courses and activities&lt;br /&gt;
| I should be able to easily see my list of courses in a predictable place and be able to jump from one course to another at speed&lt;br /&gt;
|-&lt;br /&gt;
| As a teacher, I want to edit a courses&#039; settings, so that I can improve my learners experience&lt;br /&gt;
| I should be able to within a course context easily go to my courses’ settings and make changes&lt;br /&gt;
|- &lt;br /&gt;
| As a teacher, I want to easily edit a course module, so that I can deliver my course content in my preferred method&lt;br /&gt;
| Within a course module, I should be able to easily navigate to the module settings to make required changes&lt;br /&gt;
|- &lt;br /&gt;
| As a teacher, I want to view the reports for a course, so that I can any actionable items&lt;br /&gt;
| Within a course I should be able to easily navigate to the reporting area within a course&lt;br /&gt;
|- &lt;br /&gt;
| As a teacher, I can quickly find an overview of learners&#039; grades, so that I can find learners to assist&lt;br /&gt;
| As a teacher I can quickly find the learners grades without getting lost&lt;br /&gt;
|- &lt;br /&gt;
| As a teacher, I can need to grade a learners forum activity, so that I can provide my learners with feedback on their activity&lt;br /&gt;
| Within a forum module instance I can open the forum grading functionality and grade users’ participation within the forum&lt;br /&gt;
|- &lt;br /&gt;
| As a teacher, I can turn editing on within a course, so that I can add new content or modify existing content with a course&lt;br /&gt;
| I can easily turn on course editing within the course to add new modules&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== For the administrator ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;Administrator user stories&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Acceptance criteria / confirmation&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| As a site admin, I want to modify my Moodle instances’ settings, so that I can deliver a better experience for my users&lt;br /&gt;
| Links to the site administration settings still appear in easily locatable locations for site administrators&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Common user stories ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! &#039;&#039;&#039;User story&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Acceptance criteria / confirmation&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I can see other participants within a course, so that I can either enrol learners or find fellow learners within a course&lt;br /&gt;
| Within the course I can easily find a navigation item for participants&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I can use the secondary navigation whilst only using user accessibility tools, so that I can access the site&lt;br /&gt;
| The new navigation meets WCAG 2.1 AA &amp;amp; can be used with screen readers &amp;amp; other assistive technologies&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I want to see an overview of all of my courses, so that I can navigate around the site&lt;br /&gt;
| I can navigate to a dashboard of sorts that contains the courses I am currently enrolled in or have access to&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I want to easily navigate to an enrolled course, so that I can view my content&lt;br /&gt;
| I can easily find my desired course to review its content&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I want to quickly navigate to the site home page, so that I can find other relevant content&lt;br /&gt;
| I am able to navigate to the sites home easily &amp;amp; quickly&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I want to use the primary navigation whilst only using user accessibility tools, so that I can access the site&lt;br /&gt;
| The new navigation meets WCAG 2.1 AA &amp;amp; can be used with screen readers &amp;amp; other assistive technologies&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I want to navigate back to the course with the navigation bar, so that I can quickly go to a different piece of learning content&lt;br /&gt;
| As any user, I should be able to use the context header bar within a module to navigate back to the start of the course&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I want to navigate back to the section the module is in from the navigation bar, so that I can find other pieces of learning content that are similar to the current content I am reviewing&lt;br /&gt;
| As any user, I should be able to use the context header bar within a module to navigate back to the section of the course that the current module is placed in&lt;br /&gt;
|-&lt;br /&gt;
| As any user, I can search a forum that I am in, so that I can find relevant posts or discussions before making a new post&lt;br /&gt;
| I can still search forums in a similar or identical way as I currently can (pre Moodle 4.0)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Code architecture ==&lt;br /&gt;
&lt;br /&gt;
Navigation views are a way to distinguish from the actual navigation of the system. Global and settings navigation are a true source of the internal navigation system. The views are for display purposes and can be interpreted by the different themes as they please.&lt;br /&gt;
&lt;br /&gt;
As a developer you can register your new navigation item against different views for example &lt;br /&gt;
* &#039;primary&#039; navigation items across the top of the page with the site header&lt;br /&gt;
* &#039;secondary&#039; navigation items across the top of the page content within the context header&lt;br /&gt;
&lt;br /&gt;
As seen in the diagram below, the current idea is to create a view based class system where  *_navigation_view classes extend the existing navigation_node class. The idea behind this new structure is that the system and third party theme developers can easily fetch a representation of the current navigation within a given context and immediately render out only the view they want as opposed to fetching the entire navigation for the page.&lt;br /&gt;
&lt;br /&gt;
New classes that extend existing classes&lt;br /&gt;
[[File:view_diagram.jpg&lt;br /&gt;
&lt;br /&gt;
=== Technical design of *_navigation_view ===&lt;br /&gt;
&lt;br /&gt;
The primary &amp;amp; secondary navigation view classes are representations of the navigation within a given context. The instances of these navigation views will be capable of accepting navigation items appended to them within plugins; however, plugins will not be able to remove existing navigation_node‘s.&lt;br /&gt;
&lt;br /&gt;
Custom label added via plugin&lt;br /&gt;
[[File:Primary.png]]&lt;br /&gt;
&lt;br /&gt;
An example of the above would be the following where &amp;quot;Custom label&amp;quot; has been added by an arbitrary plugin to the primary navigation of a Moodle instance:&lt;br /&gt;
&lt;br /&gt;
Note: In this example we will be using simple strings, During implementation the navigation_node class should be used.&lt;br /&gt;
&lt;br /&gt;
$primarynav = $PAGE-&amp;gt;secondarynavigationview&lt;br /&gt;
$customlabel = $secondnav-&amp;gt;register(&#039;Custom label&#039;)&lt;br /&gt;
&lt;br /&gt;
To add further navigation items underneath the &amp;quot;Custom label&amp;quot; navigation item you could then do the following:&lt;br /&gt;
&lt;br /&gt;
$customlabel-&amp;gt;register_child(&#039;Secondary label&#039;)&lt;br /&gt;
$doclabels = $customlabel-&amp;gt;register_child(&#039;Docs&#039;)&lt;br /&gt;
$doclabels-&amp;gt;register(&#039;Indepth docs&#039;)&lt;br /&gt;
&lt;br /&gt;
After the following the following would be represented within the primary navigation after the default navigation items:&lt;br /&gt;
&lt;br /&gt;
* Custom label&lt;br /&gt;
** Secondary label&lt;br /&gt;
** Docs&lt;br /&gt;
*** Indepth docs&lt;br /&gt;
&lt;br /&gt;
=== Consideration given to third party plugins ===&lt;br /&gt;
It is highly likely that the new navigation classes will be using functions from settings_navigation such as load_module_settings(). This provides access to callbacks that are already implemented for both course module plugins and local plugins that modify what navigation_node’s populate the either navigation view.&lt;br /&gt;
&lt;br /&gt;
This means that both core and third party Moodle theme plugins will not be able to mutate / modify any received new navigation views as the only accessible functions publicly defined would be to fetch a representation of the navigation to be output by the theme in a template.&lt;br /&gt;
&lt;br /&gt;
The process for calling these new navigation views is similar to existing methods of calling navigation, there&#039;ll be a new properties added to the lib/pagelib.php called primarynavigationview &amp;amp; secondarynavigationview.  These properties will be used in conjunction with magic_get_*navview() to create a new primary_navigation_view or secondary_navigation_view and trigger the initialization method.&lt;br /&gt;
To view a similar calling method, view how the navigation is called within any of the columns files within the boost theme where it calls flatnav.&lt;br /&gt;
&lt;br /&gt;
=== Further reading ===&lt;br /&gt;
More technical discussion can be found within issues in the navigation epic MDL-69588 or in the specification document (releasing soon)&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Github_actions_integration&amp;diff=58361</id>
		<title>Github actions integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Github_actions_integration&amp;diff=58361"/>
		<updated>2021-02-20T17:00:24Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Moodle plugins */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Moodle core ==&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
&lt;br /&gt;
Moodle is regularly tested against a matrix of Databases, PHP Versions, and operating systems, however many developers do not have the resources available to run on many of these combinations before pushing an issue for integration as they are time-consuming to both set up and to run.&lt;br /&gt;
&lt;br /&gt;
There are many Continuous Integration tools available to developers, and [https://github.com/features/actions GitHub Actions] is just one of those available to the Open Source community.&lt;br /&gt;
&lt;br /&gt;
Since November 2020 (Moodle 3.5.16 and up), Moodle includes a GitHub Actions configuration file in its repository. This configuration file configures and controls a GitHub build across a matrix of testing environments. This allows developers pushing patches to Moodle to have their code unit tested before it reaches integration. The hope is that the availability of this integration should reduce the number of unit test failures seen during Integration.&lt;br /&gt;
&lt;br /&gt;
Note: Moodle HQ uses the Jenkins CI platform and this should be seen as the [https://ci.moodle.org/ canonical CI server for Moodle]. The GitHub Actions integration aims is to provide early warning to developers of any issues with their code.&lt;br /&gt;
&lt;br /&gt;
=== Usage ===&lt;br /&gt;
&lt;br /&gt;
GitHub Actions are available and enabled by default for all public repositories @ GitHub. You can enable or disable them going to your moodle.git clone page, then settings tab -&amp;gt; actions . Please use the &amp;quot;Allow all&amp;quot; option as far as the integration reuses actions from different sources.&lt;br /&gt;
&lt;br /&gt;
[[File:enable_disable_github_actions.png|Enabling and disabling GitHub Actions for your repos]]&lt;br /&gt;
&lt;br /&gt;
For the purpose of this documentation, it is assumed that you are pushing to a public Moodle repository on GitHub. Other integrations are supported, but the service available from GitHub Actions is only available to github public repositories.&lt;br /&gt;
&lt;br /&gt;
=== How do I start a build? ===&lt;br /&gt;
&lt;br /&gt;
Whenever you push a change to any branch in your repository... a build will be queued and executed. You can see how all your builds are progressing by visiting your moodle.git clone page at GitHub, then selecting the actions tab. From there you can filter by branch, by status, access to every build, see failures, relaunch jobs...&lt;br /&gt;
&lt;br /&gt;
[[File:actions_dashboard.png|GitHub Actions &amp;quot;dashboard&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
You will receive notifications (by email or web) whenever a build finish (can be configured from your account notification settings). Also, if your branches match the branch names in any issue in the Tracker, you will get there the corresponding pass/fail badges [[File:github_passing.png|GitHub passing badge]]. Clicking on them you will access straight to the job details @ GitHub.&lt;br /&gt;
&lt;br /&gt;
And that&#039;s all, pretty nice, simple and effective.&lt;br /&gt;
&lt;br /&gt;
== Moodle plugins ==&lt;br /&gt;
&lt;br /&gt;
See [https://moodlehq.github.io/moodle-plugin-ci/#github-actions the instructions in the Moodle Plugin CI repository].&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Travis integration]]: For information about the, also supported, integration with Travis CI facilities.&lt;br /&gt;
&lt;br /&gt;
[[Category:Developer tools]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Travis_integration&amp;diff=58359</id>
		<title>Travis integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Travis_integration&amp;diff=58359"/>
		<updated>2021-02-19T21:03:40Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Given the two notes beliw, you probably don&#039;t want to read this page. [[Github actions integration]] is much more likely to be what you want.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;note&amp;quot;&amp;gt;Travis [https://mailchi.mp/3d439eeb1098/travis-ciorg-is-moving-to-travis-cicom has announced] that &#039;&#039;&#039;travis-ci.org will be closed down completely on December 31st 2020&#039;&#039;&#039;, with travis-ci.com becoming the unified place for all projects. More information can be found at:&lt;br /&gt;
* [https://docs.travis-ci.com/user/migrate/open-source-repository-migration Migrating repositories to travis-ci.com]&lt;br /&gt;
* Moodle Tracker: MDLSITE-6246&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;note&amp;quot;&amp;gt;Travis [https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing has announced], November 2, 2020, that &#039;&#039;&#039;travis-ci.com won&#039;t be (unlimitedly) free anymore&#039;&#039;&#039;, with everybody getting some credits (processing time) once and, after using them, you must change to some of their (paid) plans. Few weeks later, November 24, 2020, [https://blog.travis-ci.com/oss-announcement they have published an update] specifically for Open Source. There, they recommend to contact them &#039;&#039;&amp;quot;for anything relating with your open source account&amp;quot;&#039;&#039;. Up to you!&lt;br /&gt;
More information can be found at:&lt;br /&gt;
* Moodle Tracker: MDL-70265&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Moodle core ==&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
&lt;br /&gt;
Moodle is regularly tested against a matrix of Databases, PHP Versions, and operating systems, however many developers do not have the resources available to run on many of these combinations before pushing an issue for integration as they are time-consuming to both set up and to run.&lt;br /&gt;
&lt;br /&gt;
There are many Continuous Integration tools available to developers, and Travis-CI is just one of those available to the Open Source community.&lt;br /&gt;
&lt;br /&gt;
Since version 3.0, Moodle includes a Travis configuration file in its repository. This configuration file configures and controls a Travis build across a matrix of testing environments. This allows developers pushing patches to Moodle to have their code unit tested before it reaches integration. The hope is that the availability of this integration should reduce the number of unit test failures seen during Integration.&lt;br /&gt;
&lt;br /&gt;
Note: Moodle HQ uses the Jenkins CI platform and this should be seen as the [https://ci.moodle.org/ canonical CI server for Moodle]. The Travis integration aims is to provide early warning to developers of any issues with their code.&lt;br /&gt;
&lt;br /&gt;
=== Usage ===&lt;br /&gt;
&lt;br /&gt;
Travis-CI is available and is usually configured to run automatically when pushing code, but it must be configured before first use.&lt;br /&gt;
&lt;br /&gt;
For the purpose of this documentation, it is assumed that you are pushing to a public Moodle repository on GitHub. Other integrations are supported, but the service available from Travis is only available to public repositories.&lt;br /&gt;
&lt;br /&gt;
=== Setup ===&lt;br /&gt;
&lt;br /&gt;
# [https://travis-ci.com/auth Sign in to Travis CI] using your GitHub account&lt;br /&gt;
# Once you’re signed in, and the initial synchronisation of your GitHub repositories has completed, go to your [https://travis-ci.com/profile profile page]&lt;br /&gt;
# Enable Travis CI for your clone of the Moodle repository [[File:moodle-travis-enable.png]]&lt;br /&gt;
# Click on the cog icon to configure the Integration&lt;br /&gt;
# Ensure that &amp;quot;Build pushed branches&amp;quot; is enabled [[File:moodle-travis-settings-2020.png]]&lt;br /&gt;
# Email notifications will be sent by default to the committer and the commit author. If you want to turn off email notifications for this repository, you can define an environment variable with name MOODLE_EMAIL and &amp;quot;no&amp;quot; as value.&lt;br /&gt;
# By default, the following jobs will be executed for each branch sent to github:&lt;br /&gt;
## GRUNT: 1 job that will check that all your javascript and scss has been properly built (see [[Grunt]]) and is part of the patch.&lt;br /&gt;
## CITESTS: 1 job that will perform a lightweight linting of your branch.&lt;br /&gt;
## PHPUNIT: 1 job that will run all the unit tests on your branch (using the lowest PHP version supported by the branch and PostgreSQL as database).&lt;br /&gt;
# If you want to change those defaults, you can use the following environment variables:&lt;br /&gt;
## MOODLE_DATABASE: With value &amp;quot;mysqli&amp;quot; to switch your runs to that database. With value &amp;quot;all&amp;quot; to run both PostgreSQL and MySQL unit tests.&lt;br /&gt;
## MOODLE_PHP: With value &amp;quot;all&amp;quot;, to run the unit tests both with the lowest and the highest PHP versions supported by the branch.&lt;br /&gt;
Note that adding more jobs will increase the time needed (and the credits consumed) for processing each branch, so be careful picking your fav configuration.&lt;br /&gt;
[[File:environment_variables.png|Define an environment variable]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== How do I start a build? ===&lt;br /&gt;
&lt;br /&gt;
It won&#039;t build immediately after setup. Builds start automatically when you push a change.&lt;br /&gt;
&lt;br /&gt;
== Moodle plugins ==&lt;br /&gt;
&lt;br /&gt;
See [https://moodlehq.github.io/moodle-plugin-ci/ Moodle Plugin CI repository] for setup instructions.&lt;br /&gt;
&lt;br /&gt;
Related discussions:&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=323384 Adding Travis CI support into your plugin]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=389744 Recent Travis-CI issues with plugins]&lt;br /&gt;
&lt;br /&gt;
=== Ignoring files and folders ===&lt;br /&gt;
&lt;br /&gt;
For some of the code analysis tools, it is important to ignore some files within the plugin because they might not be fixable, like a third party library. The all code analysis commands in this project ignore files and directories listed in the thirdpartylibs.xml plugin file. See https://docs.moodle.org/dev/Plugin_files#thirdpartylibs.xml for more info.&lt;br /&gt;
&lt;br /&gt;
==== Ignoring files ====&lt;br /&gt;
&lt;br /&gt;
Specifically for the codechecker command, you can ignore a single line, a section of a file or the whole file by using specific PHP comments. For details see this [[CodeSniffer]] wiki page.&lt;br /&gt;
In addition, you can ignore additional files by defining IGNORE_PATHS and/or IGNORE_NAMES environment variables in your .travis.yml file. These environment variables wont work for Grunt tasks, but will for everything else. Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
env:&lt;br /&gt;
 global:&lt;br /&gt;
  - MOODLE_BRANCH=MOODLE_35_STABLE&lt;br /&gt;
  - IGNORE_PATHS=vendor/widget,javascript/min-lib.js&lt;br /&gt;
  - IGNORE_NAMES=*-m.js,bad_lib.php&lt;br /&gt;
 matrix:&lt;br /&gt;
  - DB=pgsql&lt;br /&gt;
  - DB=mysqli&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Both environment variables take a CSV value. For IGNORE_PATHS, it takes relative file paths to ignore. File paths can be a simple string like foo/bar or a regular expression like /^foo\/bar/. For IGNORE_NAMES, it takes file names to ignore. File names can be a simple string like foo.php, a glob like *.php or a regular expression like /\.php$/.&lt;br /&gt;
&lt;br /&gt;
If you need to specify ignore paths for a specific command, then you can define additional environment variables. The variable names are the same as above, but prefixed with COMMANDNAME_. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
env:&lt;br /&gt;
 global:&lt;br /&gt;
  - MOODLE_BRANCH=MOODLE_35_STABLE&lt;br /&gt;
  - IGNORE_PATHS=vendor/widget,javascript/min-lib.js&lt;br /&gt;
  - IGNORE_NAMES=*-m.js,bad_lib.php&lt;br /&gt;
  - PHPUNIT_IGNORE_PATHS=$IGNORE_PATHS,cli&lt;br /&gt;
 matrix:&lt;br /&gt;
  - DB=pgsql&lt;br /&gt;
  - DB=mysqli&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
In the above example, we are adding the cli path to our ignore paths for the PHPUnit command (this is also how you can ignore files for code coverage). Please note that this is a complete override and there is no merging with IGNORE_PATHS and IGNORE_NAMES. So, in the above, the PHPUnit command would not ignore the file names defined in IGNORE_NAMES.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Github actions integration]]: For information about the, also supported, integration with GitHub Actions.&lt;br /&gt;
&lt;br /&gt;
[[Category:Developer tools]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.9.4_release_notes&amp;diff=58220</id>
		<title>Moodle 3.9.4 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.9.4_release_notes&amp;diff=58220"/>
		<updated>2021-01-19T12:54:28Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Release date: 18 January 2021&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.9.4%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.9.4].&lt;br /&gt;
 &lt;br /&gt;
==Warning==&lt;br /&gt;
If you use a custom course format, and your courses have a lots of sections, then be aware of the MDL-69431 change. Course formats used to have a method get_max_sections which used to be ignored, so you probably never implemented it, so you get the default return value of 52. With Moodle 3.9.4 you wont be able to edit activties in your big courses until you have fixed your custom course format plugin.&lt;br /&gt;
&lt;br /&gt;
==General fixes and improvements==&lt;br /&gt;
* MDL-54907 - Automatically submitted quiz attempts: finish time is set to when cron ran, not when the attempt ended&lt;br /&gt;
* MDL-69964 - The &amp;quot;Select all X users&amp;quot; button doesn&#039;t activate the drop-down menu in Participants Page&lt;br /&gt;
* MDL-68896 - SCORM error in Chrome because of &amp;quot;XHR in page dismissal&amp;quot; policy change&lt;br /&gt;
* MDL-67623 - Course overview (my courses block) pagination is broken beyond the second page&lt;br /&gt;
* MDL-56119 - Rubric display layout issue, after students feedback is released&lt;br /&gt;
* MDL-50955 - Lesson module error on save - Cannot find grade item for &#039;lesson&#039;&lt;br /&gt;
* MDL-65941 - Redis server issues break cache configuration page&lt;br /&gt;
* MDL-70157 - AWS Aurora MySQL support for Moodle (backport of MDL-58931)&lt;br /&gt;
* MDL-70285 - The MDL-69687 upgrade step kills large databases&lt;br /&gt;
* MDL-69526 - Custom field values in course overview block follow incorrect order&lt;br /&gt;
* MDL-65852 - Non-editing teacher should be able to download course participants list&lt;br /&gt;
* MDL-70265 - Reduce the number of phpunit runs in core&#039;s .travis.yml&lt;br /&gt;
* MDL-70386 - Illegible css coloring of correct/incorrect div&lt;br /&gt;
* MDL-69930 - Duplication items in drag-onto-image question&lt;br /&gt;
* MDL-70276 - Add support for github actions to moodle.git&lt;br /&gt;
* MDL-70355 - Multilang Filters not applied to Calendar block&lt;br /&gt;
* MDL-70063 - [Youtube Plugin] Selecting a category results in &amp;lt;data could not be obtained&amp;gt; error&lt;br /&gt;
* MDL-67513 - View conversation link does not work when grading in full screen mode&lt;br /&gt;
* MDL-70558 - Available language packs unsorted, difficult to locate&lt;br /&gt;
* MDL-69868 - H5P corrupts USER object, causing forum error&lt;br /&gt;
* MDL-70426 - Drag-drop markers questions: infinite markers keep duplicating&lt;br /&gt;
* MDL-70065 - Quiz add questions from question bank: problem with paging &amp;amp; show all&lt;br /&gt;
* MDL-62707 - codingerror in Global Search when &amp;quot;search within enrolled courses only&amp;quot; is set&lt;br /&gt;
* MDL-70148 - Write new keyboard steps for Behat&lt;br /&gt;
* MDL-69955 - Question type Drag and Drop: drop zone disappear in special case&lt;br /&gt;
* MDL-70320 - Incorrect HTML escaping on the override permissions screen&lt;br /&gt;
* MDL-70261 - Upload Courses tool breaks on locked custom fields&lt;br /&gt;
* MDL-70436 - On mobile, the Quiz confirmation modal has it&#039;s close button cut off&lt;br /&gt;
* MDL-70373 - Atto HTML editor lacks border outside Moodle forms (e.g. Essay questions)&lt;br /&gt;
* MDL-70374 - Layout of multiple choice questions not well aligned&lt;br /&gt;
* MDL-70520 - Moodle upgrade resets  scheduled tasks lastruntime&lt;br /&gt;
* MDL-70117 - PDF dataformat export: content can overflow when page headers are involved&lt;br /&gt;
* MDL-70072 - Date in message system  (always in Gregorian)&lt;br /&gt;
* MDL-70248 - Drag and Drop onto images: Drop zones have UI issue in Editing form&lt;br /&gt;
* MDL-70080 - Users should be able to contact the site&#039;s support via the Moodle App (Backport of MDL-69810)&lt;br /&gt;
* MDL-67636 - Locking grade category exposes hidden item grades on user report&lt;br /&gt;
* MDL-70352 - Modal forms stay on the screen if you have multiple modals on one page&lt;br /&gt;
* MDL-70567 - Task logs page doesn&#039;t respect result filter when moving through the pagination&lt;br /&gt;
* MDL-70009 - Course with H5P element in content bank can not be deleted by Manager/Teacher role (with appropriate rights)&lt;br /&gt;
&lt;br /&gt;
==Accessibility improvements==&lt;br /&gt;
* MDL-69841 - Edit Quiz, click on help icon under review options group will check / uncheck the checkbox&lt;br /&gt;
* MDL-69422 - HTML validation and accessibility problems on database export page&lt;br /&gt;
* MDL-69301 - Focus order in tabs&lt;br /&gt;
* MDL-70094 - Question preview: Technical info section expands if you click the help icon there&lt;br /&gt;
 &lt;br /&gt;
==Security improvements==&lt;br /&gt;
* MDL-69877 - Set up a security.txt file in Moodle LMS&lt;br /&gt;
&lt;br /&gt;
==Security fixes==&lt;br /&gt;
 	&lt;br /&gt;
Details of any security issues will be released after a period of approximately one week to allow system administrators to safely update to the latest version.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.9.3 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.9]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.9.4]]&lt;br /&gt;
[[es:Notas de Moodle 3.9.4]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.8.6_release_notes&amp;diff=58084</id>
		<title>Moodle 3.8.6 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.8.6_release_notes&amp;diff=58084"/>
		<updated>2020-11-23T21:11:45Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&#039;&#039;&#039;This version of Moodle is no longer supported for general bug fixes.&#039;&#039;&#039; You are encouraged to [[:en:Upgrading|upgrade]] to a supported version of Moodle.&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Release date: 9 November 2020&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.8.6%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.8.6].&lt;br /&gt;
 &lt;br /&gt;
==Warning==&lt;br /&gt;
&lt;br /&gt;
If you have a large database, the upgrade step added in MDL-69687 may be very, very slow. To avoid excessive down-time when you grade, you may want to test for this. A fix is being developed in MDL-70285.&lt;br /&gt;
&lt;br /&gt;
==General fixes and improvements==&lt;br /&gt;
* MDL-68722 - Atto Equation Editor Symbols missing&lt;br /&gt;
* MDL-68070 - Messaging breaks when &amp;quot;Personal messages between users&amp;quot; is disabled&lt;br /&gt;
* MDL-68900 - Attempting to grade forums outside of their display period causes invalid response value error&lt;br /&gt;
* MDL-65792 - Timed/Scheduled Posts are displaying create/modified time instead of release time&lt;br /&gt;
* MDL-69667 - Competencies count always 0 in competencyframeworks&lt;br /&gt;
* MDL-69772 - Incorrect &#039;allcountrycodes&#039; field prevents country selection during registration&lt;br /&gt;
* MDL-69641 - Fix Course gradebook slow query due to cross join on full user table (backport of MDL-69190)&lt;br /&gt;
* MDL-62387 - Cohort sync dropdown contains redundant entries&lt;br /&gt;
* MDL-69342 - &#039;Delete picture&#039; checkbox deletes also the new profile picture when editing profile&lt;br /&gt;
* MDL-69359 - Add option to show only contributed plugins in uninstall script (backport of MDL-69260)&lt;br /&gt;
* MDL-67654 - Forum inline reply does not use formchangechecker&lt;br /&gt;
* MDL-69791 - Grader report doesn&#039;t show an error message when an invalid grade is entered in AJAX mode&lt;br /&gt;
* MDL-69818 - Restoring a feedback activity doesn&#039;t restore item dependency&lt;br /&gt;
* MDL-67650 - Forced $CFG config checkbox, select, textarea are not disabled in GUI&lt;br /&gt;
* MDL-68438 - Changing notification email format fails if messaging is disabled&lt;br /&gt;
* MDL-68284 - Locking invisible quiz in gradebook setup makes it visible (but only on gradebook setup page)&lt;br /&gt;
* MDL-69805 - Database activity shows the comments option even if comments are disabled at site level&lt;br /&gt;
&lt;br /&gt;
==Accessibility improvements==&lt;br /&gt;
* MDL-65074 - Quiz navigation buttons use part of btn-secondary styles, can disappear&lt;br /&gt;
* MDL-70004 - Invalid role attribute in the label for the &amp;quot;Clear my choice&amp;quot; option&lt;br /&gt;
* MDL-69392 - Colour contrast issues in quiz&lt;br /&gt;
* MDL-68766 - Login form: &amp;quot;Log in using your account on:&amp;quot; should be h3, not h6&lt;br /&gt;
* MDL-69395 - Insufficient colour contrast between form control borders and background&lt;br /&gt;
* MDL-69649 - Missing labels in restore page&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
* MDL-52407 - Travis: Start sending e-mail notifications&lt;br /&gt;
&lt;br /&gt;
==Security improvements==&lt;br /&gt;
* MDL-68292 - admin/modules.php exposes CSRF token (sesskey) in url&lt;br /&gt;
* MDL-69014 - User preferences not removed when tours are deleted&lt;br /&gt;
* MDL-69807 - Editing a block exposes the CSRF token (sesskey) in the url&lt;br /&gt;
&lt;br /&gt;
==Security fixes==&lt;br /&gt;
 	&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413935 MSA-20-0016] Teacher is able to unenrol users without permission using course restore&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413936 MSA-20-0017] Privilege escalation within a course when restoring role overrides&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413938 MSA-20-0018] Some database module web services did not respect group settings&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413939 MSA-20-0019] tool_uploadcourse creates new enrol instances unexpectedly in some circumstances&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413941 MSA-20-0021] The participants table download feature did not respect the site&#039;s &amp;quot;show user identity&amp;quot; configuration&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.8.5 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.8]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.8.6]]&lt;br /&gt;
[[es:Notas de Moodle 3.8.6]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.10_release_notes&amp;diff=58083</id>
		<title>Moodle 3.10 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.10_release_notes&amp;diff=58083"/>
		<updated>2020-11-23T21:11:34Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Release date: 9 November 2020&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.10%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.10].&lt;br /&gt;
&lt;br /&gt;
If you are upgrading from a previous version, please see [[:en:Upgrading|Upgrading]] in the user docs.&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.5 or later&lt;br /&gt;
* PHP version: minimum PHP 7.2.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.8&#039;&#039;. PHP 7.3.x and 7.4.x are supported too. See [[Moodle and PHP]] for details.&lt;br /&gt;
* PHP extension &#039;&#039;&#039;mbstring&#039;&#039;&#039; is required (it was previously only recommended) &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.6  (increased since Moodle 3.9)&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.7 (increased since Moodle 3.9)&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 10.2.29 (increased since Moodle 3.8)&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;
| 2012&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;
&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;
&lt;br /&gt;
&#039;&#039;Note: Moodle 3.10 does NOT support Internet Explorer 11.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Safari 7 and below has known compatibility issues with Moodle 3.10.&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://www.whatsmybrowser.org/&lt;br /&gt;
&lt;br /&gt;
==Warning==&lt;br /&gt;
&lt;br /&gt;
If you have a large database, the upgrade step added in MDL-69687 may be very, very slow. To avoid excessive down-time when you grade, you may want to test for this. A fix is being developed in MDL-70285.&lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===Download course content===&lt;br /&gt;
* MDL-69548 - Add ZipStream library to core&lt;br /&gt;
* MDL-69549 - Create course content export API&lt;br /&gt;
* MDL-69559 - Course content download - add site admin and course level settings, implement in course user interface&lt;br /&gt;
&lt;br /&gt;
===Payment subsystem===&lt;br /&gt;
* MDL-69166 - Add payment as subsystem supporting payment gateways&lt;br /&gt;
&lt;br /&gt;
===H5P updates and improvements===&lt;br /&gt;
* MDL-69087 - Add the option to personalize H5P styles&lt;br /&gt;
* MDL-69207 - Add library file caching to h5p&lt;br /&gt;
* MDL-69174 - Method of saving embedded H5P content grades in the gradebook&lt;br /&gt;
* MDL-69520 - Add Example and Tutorial links to the H5P editor&lt;br /&gt;
* MDL-68909 - Clean up temporary H5P editor files&lt;br /&gt;
&lt;br /&gt;
===Content bank===&lt;br /&gt;
* MDL-69269 - Download content from the content bank&lt;br /&gt;
* MDL-69270 - Replace content file from content bank&lt;br /&gt;
* MDL-68688 - Add a notification when the content bank is empty&lt;br /&gt;
* MDL-68975 - Add the author to the content bank &amp;quot;file details&amp;quot; view&lt;br /&gt;
&lt;br /&gt;
===Quiz and questions===&lt;br /&gt;
* MDL-45002 - New quiz completion option: At least one (or N) attempt completed&lt;br /&gt;
* MDL-66587 - Scrolling quiz timer&lt;br /&gt;
* MDL-68562 - Qtype_essay: Adding file-size limit to the attachment files&lt;br /&gt;
&lt;br /&gt;
===Accessibility improvements===&lt;br /&gt;
* MDL-68390 - WCAG 4.1.2: aria-hidden elements contain focusable elements&lt;br /&gt;
* MDL-67687 - Add Behat step to verify WCAG A and WCAG AA compliance&lt;br /&gt;
&lt;br /&gt;
===External tool (IMS-LTI)===&lt;br /&gt;
* MDL-67473 - LTI Advantage: Content Item flow to support creating multiple links&lt;br /&gt;
* MDL-67301 - Implement LTI 1.3 Dynamic Registration&lt;br /&gt;
* MDL-66934 - LTI: support substitution parameter for course history&lt;br /&gt;
&lt;br /&gt;
===Usability improvements===&lt;br /&gt;
* MDL-28501 - For folder resource, allow files to be opened in the browser rather than being downloaded&lt;br /&gt;
* MDL-65959 - Let users define their preferred backpack&lt;br /&gt;
* MDL-56041 - Cleanup custom 404 page and more easily support custom 50x error pages&lt;br /&gt;
* MDL-69192 - Assignment grading page: &amp;quot;Changes saved&amp;quot; should not be modal dialog&lt;br /&gt;
* MDL-33981 - Add ability to copy to Equella repository&lt;br /&gt;
* MDL-60621 - Improvement of modal UI when modal exceeds the height of the browser&lt;br /&gt;
* MDL-53966 - Lesson: Allow maximum number of attempts to be unlimited&lt;br /&gt;
* MDL-69613 - Grade report single view - confirm message if Override None is selected&lt;br /&gt;
* MDL-69454 - Use a consistent search input field across all Moodle searches&lt;br /&gt;
* MDL-67278 - Use autocomplete widget for course category selector&lt;br /&gt;
* MDL-68107 - Boost: Make images in topic descriptions scale with the browser window&lt;br /&gt;
* MDL-68702 - Option to not include legacy course files in backup and restore process&lt;br /&gt;
* MDL-69630 - Social activity course format should allow for using the activity chooser&lt;br /&gt;
* MDL-63387 - Show original role name of renamed roles when enrolling users&lt;br /&gt;
&lt;br /&gt;
==Other highlights==&lt;br /&gt;
&lt;br /&gt;
===Functional changes===&lt;br /&gt;
* MDL-59510 - Keep OAuth 2 connections alive across users&#039; sessions&lt;br /&gt;
* MDL-66716 - Timeline block shows incorrect date of due items&lt;br /&gt;
* MDL-48391 - tool_uploadcourse should check if enrolment method can be disabled/deleted&lt;br /&gt;
* MDL-69739 - User tours: Add tour-level CSS selector&lt;br /&gt;
* MDL-69464 - Option to allow HTML in the page headings (skip applying format_string)&lt;br /&gt;
* MDL-67419 - Set language in user profile during account auto-creation based on browser language instead of admin setting&lt;br /&gt;
* MDL-37745 - Control the display of available spaces in limited choices&lt;br /&gt;
&lt;br /&gt;
===For administrators===&lt;br /&gt;
* MDL-67211 - Tasks: Show information about running tasks, allow tasks to be disabled&lt;br /&gt;
* MDL-45849 - New capability to self enrol in course&lt;br /&gt;
* MDL-65451 - User upload via CLI&lt;br /&gt;
* MDL-69307 - Add CLI script to restore a course from backup file&lt;br /&gt;
* MDL-69583 - Add import to tool_customlang&lt;br /&gt;
* MDL-69582 - Add export to tool_customlang&lt;br /&gt;
* MDL-69260 - Add option to show only contributed plugins in uninstall script&lt;br /&gt;
* MDL-69513 - Add ability to add dkim signatures using phpmailer&lt;br /&gt;
* MDL-69265 - Have a way to append fixed arbitrary headers to all emails&lt;br /&gt;
* MDL-69600 - Expose divertallemailsto and divertallemailsexcept in the admin settings GUI&lt;br /&gt;
* MDL-69718 - Add support for terabytes and petabytes in the display_size function&lt;br /&gt;
&lt;br /&gt;
===Mobile===&lt;br /&gt;
* MDL-65976 - Add a new message provider for course completed&lt;br /&gt;
* MDL-68406 - Add option for &amp;quot;sign-out&amp;quot; only for the Moodle app&lt;br /&gt;
* MDL-68797 - Config setting for mobile file type exclusion list&lt;br /&gt;
* MDL-67841 - Update mobile app connected message so it is not misleading when the user has not used the app for a time&lt;br /&gt;
* MDL-69810 - WebService: Users should be able to contact the site&#039;s support via the Moodle App&lt;br /&gt;
&lt;br /&gt;
===Performance===&lt;br /&gt;
* MDL-69760 - Performance improvement on Moodle Event table&lt;br /&gt;
* MDL-60583 - external_tokens table will benefit from index on token field&lt;br /&gt;
* MDL-68665 - Improve cacheability of assignfeedback_editpdf/stamps&lt;br /&gt;
* MDL-64818 - Improve efficiency of blocks_for_region()&lt;br /&gt;
* MDL-69746 - tool_replace: additional skip tables&lt;br /&gt;
* MDL-68729 - Search: Allow query on one Solr server and indexing on another&lt;br /&gt;
* MDL-68726 - Search: Stop Solr &#039;optimize&#039; behaviour&lt;br /&gt;
* MDL-68690 - Search: Allow Solr to add documents in batches&lt;br /&gt;
&lt;br /&gt;
==Security improvements==&lt;br /&gt;
* MDL-66222 - Add admin options for how to handle detected viruses&lt;br /&gt;
* MDL-68820 - Add a Referrer-Policy header setting to the security admin settings&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
* MDL-58931 - AWS Aurora MySQL support for Moodle&lt;br /&gt;
* MDL-41492 - Allow alternate MUC cache config class (eg allow setup in pure $CFG / config.php)&lt;br /&gt;
* MDL-38350 - PHP Warning when purging all caches: race condition?&lt;br /&gt;
* MDL-68874 - New optional SQL debug mode which instruments SQL with the calling PHP code&lt;br /&gt;
* MDL-69117 - Improve theme designer mode - part 2&lt;br /&gt;
* MDL-67673 - Upgrade phpunit to 8.5.x&lt;br /&gt;
* MDL-68564 - Update before_footer hook to allow for output to be added to the page&lt;br /&gt;
* MDL-69050 - Rename terms to use inclusive language&lt;br /&gt;
* MDL-65743 - Upgrade XMPPHP to latest version&lt;br /&gt;
* MDL-69418 - Allow plugins to attach data to grade items during backup and restore &lt;br /&gt;
* MDL-68928 - Add a way to decide what plugin will show in the activity chooser footer&lt;br /&gt;
&lt;br /&gt;
===Web service additions and updates===&lt;br /&gt;
* MDL-67306 - Create API for grade category (gradebook)&lt;br /&gt;
* MDL-55971 - Dataformat - Store to filearea support&lt;br /&gt;
* MDL-69486 - Add user idnumber and gradeitem idnumber to gradereport_user_get_grade_items webservice &lt;br /&gt;
* MDL-63805 - New Web Service mod_glossary_update_entry&lt;br /&gt;
* MDL-69776 - New Web Service core_files_delete_draft_files&lt;br /&gt;
* MDL-69283 - Allow specifying a timezone when calling WebServices&lt;br /&gt;
* MDL-63806 - New Web Service mod_glossary_delete_entry&lt;br /&gt;
* MDL-68845 - Create new Web Service for retrieving the user calendar via iCal&lt;br /&gt;
* MDL-69577 - Add courseId and forumId info to mod_forum_get_discussion_posts web service&lt;br /&gt;
&lt;br /&gt;
===Deprecations===&lt;br /&gt;
* MDL-67594 - Deprecate supports_recursion() &amp;amp; extend_lock() in the Lock API&lt;br /&gt;
* MDL-67735 - Remove Bootstrap 2 and Bootstrap 4 alpha compatibility files&lt;br /&gt;
* MDL-69238 - Final removal of lib/coursecatlib.php&lt;br /&gt;
* MDL-63261 - Final deprecation of web services in message/externallib.php&lt;br /&gt;
* MDL-62982 - Remove the lib/form/htmleditor.php element&lt;br /&gt;
* MDL-63254 - Final deprecation of the events message_contact_blocked and message_contact_unblocked&lt;br /&gt;
* MDL-63004 - Final deprecation: I navigate to &amp;quot;ITEM&amp;quot; node in &amp;quot;MAINNODE &amp;gt; PATH&amp;quot; behat step&lt;br /&gt;
* MDL-55192 - Final deprecation of add_to_log()&lt;br /&gt;
* MDL-63167 - Final deprecation of the gradingform_provider interface&lt;br /&gt;
&lt;br /&gt;
===Component API updates===&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/backup/upgrade.txt backup/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/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/grade/grading/form/upgrade.txt grade/grading/form/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/h5p/upgrade.txt h5p/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/message/upgrade.txt message/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/glossary/upgrade.txt mod/glossary/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/mod/lti/upgrade.txt mod/lti/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/question/behaviour/upgrade.txt question/behaviour/upgrade.txt]&lt;br /&gt;
* [https://git.in.moodle.com/moodle/moodle/blob/master/search/upgrade.txt search/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/webservice/upgrade.txt webservice/upgrade.txt]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.9 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.10]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.10]]&lt;br /&gt;
[[es:Notas de Moodle 3.10]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.9.3_release_notes&amp;diff=58082</id>
		<title>Moodle 3.9.3 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.9.3_release_notes&amp;diff=58082"/>
		<updated>2020-11-23T21:10:38Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Release date: 9 November 2020&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.9.3%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.9.3].&lt;br /&gt;
 &lt;br /&gt;
==Warning==&lt;br /&gt;
&lt;br /&gt;
If you have a large database, the upgrade step added in MDL-69687 may be very, very slow. To avoid excessive down-time when you grade, you may want to test for this. A fix is being developed in MDL-70285.&lt;br /&gt;
&lt;br /&gt;
==General fixes and improvements==&lt;br /&gt;
* MDL-68722 - Atto Equation Editor Symbols missing&lt;br /&gt;
* MDL-68070 - Messaging breaks when &amp;quot;Personal messages between users&amp;quot; is disabled&lt;br /&gt;
* MDL-69355 - Download of files bigger than 10 MB fails&lt;br /&gt;
* MDL-68900 - Attempting to grade forums outside of their display period causes invalid response value error&lt;br /&gt;
* MDL-69257 - H5P Interactive video should comply with maxbytes file upload limits&lt;br /&gt;
* MDL-65792 - Timed/Scheduled Posts are displaying create/modified time instead of release time&lt;br /&gt;
* MDL-69266 - Drag and drop questions with &#039;unlimited&#039; options fail in Moodle 3.9&lt;br /&gt;
* MDL-69736 - H5P Interactive book does not show a submit button&lt;br /&gt;
* MDL-69667 - Competencies count always 0 in competencyframeworks&lt;br /&gt;
* MDL-69772 - Incorrect &#039;allcountrycodes&#039; field prevents country selection during registration&lt;br /&gt;
* MDL-69657 - Method of saving embedded H5P content grades in the gradebook (Backport of MDL-69174)&lt;br /&gt;
* MDL-69641 - Fix Course gradebook slow query due to cross join on full user table (backport of MDL-69190)&lt;br /&gt;
* MDL-62387 - Cohort sync dropdown contains redundant entries&lt;br /&gt;
* MDL-69342 - &#039;Delete picture&#039; checkbox deletes also the new profile picture when editing profile&lt;br /&gt;
* MDL-69359 - Add option to show only contributed plugins in uninstall script (backport of MDL-69260)&lt;br /&gt;
* MDL-69156 - Course copy: idnumber field is missing if not permitted&lt;br /&gt;
* MDL-67654 - Forum inline reply does not use formchangechecker&lt;br /&gt;
* MDL-69791 - Grader report doesn&#039;t show an error message when an invalid grade is entered in AJAX mode&lt;br /&gt;
* MDL-69818 - Restoring a feedback activity doesn&#039;t restore item dependency&lt;br /&gt;
* MDL-69751 - Activity chooser does not display if site contains invalid user&lt;br /&gt;
* MDL-67650 - Forced $CFG config checkbox, select, textarea are not disabled in GUI&lt;br /&gt;
* MDL-68438 - Changing notification email format fails if messaging is disabled&lt;br /&gt;
* MDL-70093 - PDF dataformat export is misformatted when a cell height is greater than a page height&lt;br /&gt;
* MDL-69698 - List of licenses is not displayed in the user&#039;s  preferred language&lt;br /&gt;
* MDL-69729 - Clean up temporary H5P editor files (backport of MDL-68909)&lt;br /&gt;
* MDL-68284 - Locking invisible quiz in gradebook setup makes it visible (but only on gradebook setup page)&lt;br /&gt;
* MDL-69805 - Database activity shows the comments option even if comments are disabled at site level&lt;br /&gt;
&lt;br /&gt;
==Accessibility improvements==&lt;br /&gt;
* MDL-65074 - Quiz navigation buttons use part of btn-secondary styles, can disappear&lt;br /&gt;
* MDL-68167 - Fix core/form_autocomplete accessibility issues&lt;br /&gt;
* MDL-69390 - Insufficient colour contrast for focused/mouseover action menu items&lt;br /&gt;
* MDL-70004 - Invalid role attribute in the label for the &amp;quot;Clear my choice&amp;quot; option&lt;br /&gt;
* MDL-69392 - Colour contrast issues in quiz&lt;br /&gt;
* MDL-68766 - Login form: &amp;quot;Log in using your account on:&amp;quot; should be h3, not h6&lt;br /&gt;
* MDL-69395 - Insufficient colour contrast between form control borders and background&lt;br /&gt;
* MDL-69649 - Missing labels in restore page&lt;br /&gt;
* MDL-69644 - Focus outline of the &amp;quot;contact the privacy officer&amp;quot; link inconsistent with rest of page&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
* MDL-52407 - Travis: Start sending e-mail notifications&lt;br /&gt;
&lt;br /&gt;
==Security improvements==&lt;br /&gt;
* MDL-68292 - admin/modules.php exposes CSRF token (sesskey) in url&lt;br /&gt;
* MDL-69014 - User preferences not removed when tours are deleted&lt;br /&gt;
* MDL-69807 - Editing a block exposes the CSRF token (sesskey) in the url&lt;br /&gt;
&lt;br /&gt;
==Security fixes==&lt;br /&gt;
 	&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413935 MSA-20-0016] Teacher is able to unenrol users without permission using course restore&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413936 MSA-20-0017] Privilege escalation within a course when restoring role overrides&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413938 MSA-20-0018] Some database module web services did not respect group settings&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413939 MSA-20-0019] tool_uploadcourse creates new enrol instances unexpectedly in some circumstances&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413940 MSA-20-0020] Stored XSS possible when renaming content bank items&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=413941 MSA-20-0021] The participants table download feature did not respect the site&#039;s &amp;quot;show user identity&amp;quot; configuration&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.9.2 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.9]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.9.3]]&lt;br /&gt;
[[es:Notas de Moodle 3.9.3]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=lib/formslib.php_Form_Definition&amp;diff=58036</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=58036"/>
		<updated>2020-11-09T20:06:23Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* autocomplete */&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;
);&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;
===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>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=57993</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=57993"/>
		<updated>2020-11-05T12:39:31Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &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;
First, you should have a look at [[Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium#Working_combinations_of_OS.2BBrowser.2Bselenium|working combinations of OS+Browser+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/]. Ensure you have the right version of the Chrome driver - see [[#Trouble_shooting| Trouble shooting]]. (Firefox is currently problematic. See [[Actual_Selenium_with_old_Firefox_47.0.1]] if you need to try to make it work.)&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;vendor/bin/behat&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. Use comma-separated list like &#039;&amp;lt;tt&amp;gt;@some_thing,@some_thing_else&amp;lt;/tt&amp;gt;&#039; to run tests from multiple areas. See upstream documentation on Gherkin filters for advanced syntax and more complex examples.&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;
# At the end of each line it shows you the number of steps that have completed so far.&lt;br /&gt;
#* If you are doing a full run, then as of November 2020 there are about 63,000 steps, which took around 25 hours to complete on one test machine (when not using the parallel running feature). Moodle HQ manage complete runs in ~5 hours on a well-optmised set-up, with 3 parallel workers.&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=&amp;lt;number&amp;gt; or --parallel=&amp;lt;number&amp;gt;&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m=&amp;lt;number&amp;gt; or --maxruns=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;name&amp;gt; or --add-core-features-to-theme=&amp;lt;name&amp;gt;&#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;
&lt;br /&gt;
You almost always want to limit the number of tests that are run. To run all the tests in one plugin:&lt;br /&gt;
&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 --tags mod_myplugin&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run all the tests in one .feature file:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run a single scenario:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature:40&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;40&#039; is the line-number of the feature file where the Scenario starts.&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/run.php&lt;br /&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;
# 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;
Example: Initialise and run Behat tests for a custom plugin under a custom theme:&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme=&amp;quot;mytheme&amp;quot;&lt;br /&gt;
 php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;mytheme&amp;quot;&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; iframe&#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 --add-core-features-to-theme={THEME_NAME}, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php --add-core-features-to-theme=clean&lt;br /&gt;
vendor/bin/behat --suite=clean --tags=&amp;quot;@enrol_foobar&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is a core theme but it will work with custom theme. No = or quotes needed around the theme name.&lt;br /&gt;
&lt;br /&gt;
Make sure that &amp;lt;tt&amp;gt;$CFG-&amp;gt;theme&amp;lt;/tt&amp;gt; is &#039;&#039;&#039;not set&#039;&#039;&#039; in your config.php.&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;
=== Write new tests and behat methods ===&lt;br /&gt;
&lt;br /&gt;
If you want to write tests for your own integration, you can do so by creating new tests with format .feature. Follow instructions in [[Writing_acceptance_tests|this page]] to write new tests. &lt;br /&gt;
&lt;br /&gt;
It is also possible to add new steps the moodle behat integration. In order to do so, you will have to create a new .php class with the prefix &#039;&#039;&#039;behat_&#039;&#039;&#039;. Copy the format from &#039;&#039;&#039;lib\behat\behat_base.php&#039;&#039;&#039;, but set your class to extend the behat_base class instead of the MinkExtension. You can define new behat steps by declaring functions with the appropriate heading. &lt;br /&gt;
&lt;br /&gt;
You will not be  able to use these steps and features right away. Check [[Running_acceptance_test#New_step_definitions_or_features_are_not_executed|this section]] for instructions on how to update the behat integration. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For further information on how to create new steps definitions, check [[Acceptance testing/Custom acceptance steps]].&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
&lt;br /&gt;
If you follow the steps above, Behat will run with Chrome.&lt;br /&gt;
&lt;br /&gt;
You can get it to run with other browsers. The basic idea is to expand the $CFG-&amp;gt;behat_profiles array in config.php to list more browsers.&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;
&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;
Alternative way of running three Selenium servers in parallel:&lt;br /&gt;
&lt;br /&gt;
 $ printf %d\\n {4444..4446} | xargs -n 1 -P 3 java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port&lt;br /&gt;
&lt;br /&gt;
=== Run tests directly in Chrome, with no Selenium ===&lt;br /&gt;
&lt;br /&gt;
Historically, the tests would talk to a Selenium server, which would then tell the target browser what to do. More and more, the browsers themselves contain such a server and you can talk to them directly, which is faster and easier. Work was done to get this working with Chrome in MDL-58948, but it still needs some further setup to get it working, which is detailed in the bug ticket, and which I&#039;ll copy below:&lt;br /&gt;
&lt;br /&gt;
Replace your current behat config with the abve, the api_url is where you&#039;ll talk directly to your Chrome browser.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_config = [&lt;br /&gt;
    &#039;default&#039; =&amp;gt; [&lt;br /&gt;
        &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
            &#039;DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension&#039; =&amp;gt; [],&lt;br /&gt;
            &#039;Behat\MinkExtension&#039;                                          =&amp;gt; [&lt;br /&gt;
                &#039;browser_name&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                &#039;base_url&#039;     =&amp;gt; $CFG-&amp;gt;behat_wwwroot,&lt;br /&gt;
                &#039;goutte&#039;       =&amp;gt; null,&lt;br /&gt;
                &#039;selenium2&#039;    =&amp;gt; null,&lt;br /&gt;
                &#039;sessions&#039;     =&amp;gt; [&lt;br /&gt;
                    &#039;javascript&#039; =&amp;gt; [&lt;br /&gt;
                        &#039;chrome&#039; =&amp;gt; [&lt;br /&gt;
                            &#039;api_url&#039; =&amp;gt; &#039;http://localhost:9222&#039;&lt;br /&gt;
                        ]&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;
Use composer to install two more required libraries:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
composer require --dev dmore/behat-chrome-extension&lt;br /&gt;
composer require --dev dmore/chrome-mink-driver&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you try to run the tests it will tell you that Chrome isn&#039;t running, in a second tab run the following to start Chrome (or chromium-browser on Ubuntu)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chrome --disable-gpu --headless --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is running headless so you won&#039;t see any windows pop up as the tests run, though it runs in the other mode too.&lt;br /&gt;
&lt;br /&gt;
Behat should now run as before, but faster.&lt;br /&gt;
&lt;br /&gt;
==== Run Chrome locally with Behat and Moodle on a remote server ====&lt;br /&gt;
If you have Moodle running on a remote (headless) server, but don&#039;t want to install a window manager, you can do the following:&lt;br /&gt;
&lt;br /&gt;
1. Go through all the steps from above&lt;br /&gt;
&lt;br /&gt;
2. Establish an SSH SOCKS5 proxy connection to your server:&lt;br /&gt;
&amp;lt;code&amp;gt;ssh -D VARIABLE-PORT-A -N -q -C USER@SERVER&amp;lt;/code&amp;gt;&lt;br /&gt;
3. Forward the port to connect to the DevTools-API of your browser:&lt;br /&gt;
&amp;lt;code&amp;gt;ssh -R VARIABLE-PORT-B:localhost:VARIABLE-PORT-B USER@SERVER -N -q -C&amp;lt;/code&amp;gt;&lt;br /&gt;
4. Tell your local Chrome to use custom settings:&lt;br /&gt;
&amp;lt;code&amp;gt;/path/to/Google\ Chrome [--disable-gpu] --remote-debugging-address=0.0.0.0 --remote-debugging-port=VARIABLE-PORT-B --proxy-server=socks://127.0.0.1:VARIABLE-PORT-A --proxy-bypass-list=&#039;&amp;lt;-loopback&amp;gt;&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, all in one command:&lt;br /&gt;
&amp;lt;code&amp;gt;ssh -D VARIABLE-PORT-A -N -q -C -f USER@SERVER &amp;amp;&amp;amp; ssh -R VARIABLE-PORT-B:localhost:VARIABLE-PORT-B USER@SERVER -N -q -C -f &amp;amp;&amp;amp; /path/to/Google\ Chrome [--disable-gpu] --remote-debugging-address=0.0.0.0 --remote-debugging-port=VARIABLE-PORT-B --proxy-server=socks://127.0.0.1:VARIABLE-PORT-A --proxy-bypass-list=&#039;&amp;lt;-loopback&amp;gt;&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation for the SSH settings:&lt;br /&gt;
* VARIABLE-PORT-B &amp;amp; VARIABLE-PORT-A : Choose these as you like. Bear in mind that you will need root rights if the port number is 1023 or lower&lt;br /&gt;
* -N: Tells SSH not to open an actual command prompt&lt;br /&gt;
* -C: Compress all data passed through the tunnel&lt;br /&gt;
* -q: Quiet mode. Causes most warning and diagnostic messages to be suppressed&lt;br /&gt;
*-R: Open a tunnel binding to localhost on the remote machine, going to localhost on the local machine&lt;br /&gt;
* -f: Fork the process into background&lt;br /&gt;
&lt;br /&gt;
Explanation for the Chrome settings:&lt;br /&gt;
* --proxy-bypass-list=&#039;&amp;lt;-loopback&amp;gt;&#039; : Tells Chrome to send requests to localhost through the tunnel&lt;br /&gt;
* --proxy-server=socks://127.0.0.1:VARIABLE-PORT-A : To use the SOCKS5 tunnel we just set up&lt;br /&gt;
* --remote-debugging-port=VARIABLE-PORT-B &amp;amp; --remote-debugging-address=0.0.0.0 : To use the SSH tunnel we just set up&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;
=== Increasing timeouts ===&lt;br /&gt;
&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
Javascript code and/or AJAX requests are not ready after 10 seconds. &lt;br /&gt;
There is a Javascript error or the code is extremely slow.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it could just mean that the browser was not yet ready. You may find that the test works if you run it again. If you get this error frequently, it might be useful to increase the timeout.&lt;br /&gt;
&lt;br /&gt;
It is possible to increase this timeout by adding a line in your config.php. (Requires Moodle versions 3.5 (from 3.5.6), 3.6 (from 3.6.4), or 3.7+.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_increasetimeout = 2;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will increase all the timeouts by a factor of 2; if that isn&#039;t sufficient, you could use 3. &lt;br /&gt;
&lt;br /&gt;
Increasing timeouts will make tests run a bit slower (because there are points where Behat waits up to a timeout to make sure something doesn&#039;t happen) so don&#039;t do this unless you need to.&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;
=== 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;
== Trouble shooting ==&lt;br /&gt;
&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;
&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your browser 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;
=== The tests are failing, and the error message is completely useless ===&lt;br /&gt;
&lt;br /&gt;
For example, it just says &amp;quot;Error writing to database&amp;quot; with no stack trace.&lt;br /&gt;
&lt;br /&gt;
Add -vv command-line option to get very verbose output.&lt;br /&gt;
&lt;br /&gt;
=== Errors during setup (before test are launched) ===&lt;br /&gt;
Typical errors are:&lt;br /&gt;
* Behat requirement not satisfied: http://127.0.0.1/m/stable_master is not available, ensure you specified correct url and that the server is set up and started.&lt;br /&gt;
* Behat is configured but not enabled on this test site.&lt;br /&gt;
&lt;br /&gt;
In order to fix those errors please check that: the behat_dataroot has correct write permissions and that the $CFG-&amp;gt;behat* variables are placed before the lib/setup.php include:&lt;br /&gt;
 require_once(__DIR__ . &#039;/lib/setup.php&#039;);&lt;br /&gt;
&lt;br /&gt;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&lt;br /&gt;
&lt;br /&gt;
If you are using chrome, you need to ensure that the driver matches the version of the installed chrome browser – which may change on OS updates/upgrades.  Moodle or Selenium will not give the appropriate message – see [https://tracker.moodle.org/browse/MDL-67659/ MDL-67659].  One solution is the one suggested in the issue and use Andrew Nicols’ [https://github.com/andrewnicols/chromedriver-wrapper/  Chromedriver Wrapper] which will ensure you have the appropriate driver before running the tests.&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;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Developer_meeting_October_2020&amp;diff=57921</id>
		<title>Developer meeting October 2020</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Developer_meeting_October_2020&amp;diff=57921"/>
		<updated>2020-10-13T11:52:16Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Developer meetings]] &amp;gt; October 2020 meeting &lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Date&lt;br /&gt;
| Tuesday 13 October 2020 at 07:00 UTC ([https://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+developer+meeting&amp;amp;iso=20201013T07&amp;amp;p1=1440&amp;amp;ah=1 Check this time in your location])&lt;br /&gt;
|-&lt;br /&gt;
| Meeting recording&lt;br /&gt;
| https://moodle.org/mod/bigbluebuttonbn/view.php?id=8596&lt;br /&gt;
|-&lt;br /&gt;
| Discussion&lt;br /&gt;
| https://moodle.org/mod/forum/discuss.php?d=410039&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Agenda ==&lt;br /&gt;
&lt;br /&gt;
# Latest #moodledev news - [https://moodle.org/user/view.php?id=2356736&amp;amp;course=5 Sander Bangma]&lt;br /&gt;
#* [[:File:Community_Dev_Meeting_-_LMS_Update_-_Oct_2020a.pdf|Sander&#039;s presentation slides]]&lt;br /&gt;
# The Question bank: how it works, the history of how it got there; the problems that causes; and what we might do in future - special guest [https://moodle.org/user/view.php?id=93821&amp;amp;course=5 Tim Hunt], The Open University, UK&lt;br /&gt;
#* [[:File:Moodle question bank dev meeting.pdf|Tim&#039;s presentation slides]]&lt;br /&gt;
#* To be kept in the loop or to depose needs, offer funds or other contributions please make yourself heard here: [https://forms.gle/ug5kUo342ubJRGZE8 https://forms.gle/ug5kUo342ubJRGZE8]&lt;br /&gt;
#* [https://moodle.org/mod/forum/discuss.php?d=412117 Quiz forum discussion about the proposed changes].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
If there is any topic that you would like to present or discuss at a developer meeting, please contact [https://moodle.org/user/profile.php?id=1601 David Mudrák].&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Developer_meeting_October_2020&amp;diff=57801</id>
		<title>Developer meeting October 2020</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Developer_meeting_October_2020&amp;diff=57801"/>
		<updated>2020-08-25T12:39:06Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Developer meetings]] &amp;gt; October 2020 meeting &lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Date&lt;br /&gt;
| Tuesday 13 October 2020 at 07:00 UTC ([https://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+developer+meeting&amp;amp;iso=20201013T07&amp;amp;p1=1440&amp;amp;ah=1 Check this time in your location])&lt;br /&gt;
|-&lt;br /&gt;
| Meeting room&lt;br /&gt;
| https://moodle.org/mod/bigbluebuttonbn/view.php?id=8596&lt;br /&gt;
|-&lt;br /&gt;
| Discussion&lt;br /&gt;
| To be added&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Agenda ==&lt;br /&gt;
&lt;br /&gt;
# Latest #moodledev news - [https://moodle.org/user/view.php?id=2356736&amp;amp;course=5 Sander Bangma]&lt;br /&gt;
# The Question bank: how it works, the history of how it got there; the problems that causes; and what we might do in future - special guest [https://moodle.org/user/view.php?id=93821&amp;amp;course=5 Tim Hunt], The Open University, UK&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
If there is any topic that you would like to present or discuss at a developer meeting, please contact [https://moodle.org/user/profile.php?id=1601 David Mudrák].&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Availability_conditions&amp;diff=57800</id>
		<title>Availability conditions</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Availability_conditions&amp;diff=57800"/>
		<updated>2020-08-25T08:05:10Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* yui (folder) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Availability conditions allow teachers to restrict an activity or section so that only certain users can access it. These are accessed via the [[Availability API]].&lt;br /&gt;
&lt;br /&gt;
Some of the conditions included in standard Moodle are:&lt;br /&gt;
&lt;br /&gt;
* Date (users can only access activity after specified date)&lt;br /&gt;
* Grade (users can only access activity if they have a certain grade in another activity)&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Availability condition plugins were introduced with Moodle 2.7. In previous versions, the older conditional availability API did not support plugins and was limited to the predefined conditions.&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
&lt;br /&gt;
A relatively simple example is the grouping condition which can be found in /availability/condition/grouping. This is the best one to look at, or base your new code on, when starting to implement a new condition.&lt;br /&gt;
&lt;br /&gt;
To see this condition in action:&lt;br /&gt;
&lt;br /&gt;
* Ensure &amp;lt;tt&amp;gt;enableavailability&amp;lt;/tt&amp;gt; option is turned on in the Admin -&amp;gt; Advanced features page.&lt;br /&gt;
* Go to a course and edit any section.&lt;br /&gt;
* Expand the &#039;&#039;&#039;Restrict access&#039;&#039;&#039; heading.&lt;br /&gt;
* Click the &#039;&#039;&#039;Add restriction&#039;&#039;&#039; button.&lt;br /&gt;
* Click &#039;&#039;&#039;Grouping&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== File structure ==&lt;br /&gt;
&lt;br /&gt;
All files for an availability condition plugin go within /availability/condition/name, where &#039;name&#039; is the name of the condition plugin. (The examples below assume this folder, with the plugin called availability_name.)&lt;br /&gt;
&lt;br /&gt;
=== version.php ===&lt;br /&gt;
&lt;br /&gt;
Standard [[version.php]] for the component.&lt;br /&gt;
&lt;br /&gt;
=== lang/en/availability_name.php ===&lt;br /&gt;
&lt;br /&gt;
Language strings for the plugin. Required strings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;pluginname&#039;&#039;&#039; - name of plugin.&lt;br /&gt;
* &#039;&#039;&#039;title&#039;&#039;&#039; - text of button for adding this type of plugin.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; - explanatory text that goes alongside the button in the &#039;add restriction&#039; dialog. &lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;description&#039;] = &#039;Silly example plugin that just has a checkbox for whether to allow access&#039;;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Restriction by silly checkbox&#039;;&lt;br /&gt;
$string[&#039;title&#039;] = &#039;Test plugin&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will usually need to add your own strings for two main purposes:&lt;br /&gt;
&lt;br /&gt;
* Creating suitable form controls for users who are editing the activity settings.&lt;br /&gt;
* Displaying information about the condition.&lt;br /&gt;
&lt;br /&gt;
=== classes/condition.php ===&lt;br /&gt;
&lt;br /&gt;
This PHP class implements the back-end of the condition; in other words, this class contains the code which decides whether a user is allowed to access an activity that uses this condition, or not.&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an outline of the code (with standard PHPdoc comments omitted to save space) for a simple example in which there is a boolean value that controls whether access is allowed or not.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// You must use the right namespace (matching your plugin component name).&lt;br /&gt;
namespace availability_name;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class condition extends \core_availability\condition {&lt;br /&gt;
    // Any data associated with the condition can be stored in member&lt;br /&gt;
    // variables. Here&#039;s an example variable:&lt;br /&gt;
    protected $allow;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($structure) {&lt;br /&gt;
        // Retrieve any necessary data from the $structure here. The&lt;br /&gt;
        // structure is extracted from JSON data stored in the database&lt;br /&gt;
        // as part of the tree structure of conditions relating to an&lt;br /&gt;
        // activity or section.&lt;br /&gt;
        // For example, you could obtain the &#039;allow&#039; value:&lt;br /&gt;
        $this-&amp;gt;allow = $structure-&amp;gt;allow;&lt;br /&gt;
&lt;br /&gt;
        // It is also a good idea to check for invalid values here and&lt;br /&gt;
        // throw a coding_exception if the structure is wrong.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function save() {&lt;br /&gt;
        // Save back the data into a plain array similar to $structure above.&lt;br /&gt;
        return (object)array(&#039;type&#039; =&amp;gt; &#039;name&#039;, &#039;allow&#039; =&amp;gt; $this-&amp;gt;allow);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function is_available($not,&lt;br /&gt;
            \core_availability\info $info, $grabthelot, $userid) {&lt;br /&gt;
        // This function needs to check whether the condition is true&lt;br /&gt;
        // or not for the user specified in $userid. &lt;br /&gt;
&lt;br /&gt;
        // The value $not should be used to negate the condition. Other&lt;br /&gt;
        // parameters provide data which can be used when evaluating the&lt;br /&gt;
        // condition.&lt;br /&gt;
&lt;br /&gt;
        // For this trivial example, we will just use $allow to decide&lt;br /&gt;
        // whether it is allowed or not. In a real condition you would&lt;br /&gt;
        // do some calculation depending on the specified user.&lt;br /&gt;
        $allow = $this-&amp;gt;allow;&lt;br /&gt;
        if ($not) {&lt;br /&gt;
            $allow = !$allow;&lt;br /&gt;
        }&lt;br /&gt;
        return $allow;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_description($full, $not, \core_availability\info $info) {&lt;br /&gt;
        // This function just returns the information that shows about&lt;br /&gt;
        // the condition on editing screens. Usually it is similar to&lt;br /&gt;
        // the information shown if the user doesn&#039;t meet the&lt;br /&gt;
        // condition (it does not depend on the current user).&lt;br /&gt;
        $allow = $not ? !$this-&amp;gt;allow : $this-&amp;gt;allow;&lt;br /&gt;
        return $allow ? &#039;Users are allowed&#039; : &#039;Users not allowed&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_debug_string() {&lt;br /&gt;
        // This function is only normally used for unit testing and&lt;br /&gt;
        // stuff like that. Just make a short string representation&lt;br /&gt;
        // of the values of the condition, suitable for developers.&lt;br /&gt;
        return $this-&amp;gt;allow ? &#039;YES&#039; : &#039;NO&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are other functions you might also want to implement. For example, if your condition should apply to lists of users (in general, conditions which are &#039;permanent&#039; such as group conditions apply to lists, whereas those which are &#039;temporary&#039; such as date or grade conditions do not) then you should also implement is_applied_to_user_lists and filter_user_list functions. To see the full list, look at the PHPdoc for the condition and tree_node classes inside availability/classes.&lt;br /&gt;
&lt;br /&gt;
=== classes/frontend.php ===&lt;br /&gt;
&lt;br /&gt;
You will also need to write a frontend.php class which defines the behaviour of your plugin within the editing form (when a teacher is editing the activity settings).&lt;br /&gt;
&lt;br /&gt;
The class is required, but all the functions are theoretically optional; you can leave them out if you don&#039;t need any special behaviour for that function. In practice it&#039;s likely you will need at least one of them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
namespace availability_name;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class frontend extends \core_availability\frontend {&lt;br /&gt;
&lt;br /&gt;
    protected function get_javascript_strings() {&lt;br /&gt;
        // You can return a list of names within your language file and the&lt;br /&gt;
        // system will include them here. (Should you need strings from another&lt;br /&gt;
        // language file, you can also call $PAGE-&amp;gt;requires-&amp;gt;strings_for_js&lt;br /&gt;
        // manually from here.)&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_javascript_init_params($course, \cm_info $cm = null,&lt;br /&gt;
            \section_info $section = null) {&lt;br /&gt;
        // If you want, you can add some parameters here which will be&lt;br /&gt;
        // passed into your JavaScript init method. If you don&#039;t include&lt;br /&gt;
        // this function, there will be no parameters.&lt;br /&gt;
        return array(&#039;frog&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function allow_add($course, \cm_info $cm = null,&lt;br /&gt;
            \section_info $section = null) {&lt;br /&gt;
        // This function lets you control whether the &#039;add&#039; button for your&lt;br /&gt;
        // plugin appears. For example, the grouping plugin does not appear&lt;br /&gt;
        // if there are no groupings on the course. This helps to simplify&lt;br /&gt;
        // the user interface. If you don&#039;t include this function, it will&lt;br /&gt;
        // appear.&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== yui (folder) ===&lt;br /&gt;
&lt;br /&gt;
These conditions use YUI Shifter to generate JavaScript code. Although JavaScript standards in Moodle have moved on, the core avaiability system is implemented in YUI, so for now, the plugins need to use YUI too. (Please, someone, do MDL-69566!)&lt;br /&gt;
&lt;br /&gt;
Unfortunately that does mean you need to create a few layers of boilerplate folders. If you are unfamiliar with Shifter, here is a quick summary:&lt;br /&gt;
&lt;br /&gt;
# Install it according to the instructions here: [[YUI/Shifter]]&lt;br /&gt;
# In a command prompt, change to the directory that contains your plugin and run: &amp;lt;tt&amp;gt;shifter --watch&amp;lt;/tt&amp;gt;&lt;br /&gt;
# Create or modify the files described below.&lt;br /&gt;
&lt;br /&gt;
Shifter will then automatically build your files whenever you change them. You have to remember to run it whenever you make a change. (If you accidentally make a change while it&#039;s not running, run it and then make another change.)&lt;br /&gt;
&lt;br /&gt;
=== yui/src/form/meta/form.json ===&lt;br /&gt;
&lt;br /&gt;
Metadata for Shifter relating to the YUI module that contains the JavaScript for your code. You can edit this file to add any additional required YUI modules.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;moodle-availability_name-form&amp;quot;: {&lt;br /&gt;
    &amp;quot;requires&amp;quot;: [&lt;br /&gt;
        &amp;quot;base&amp;quot;,&lt;br /&gt;
        &amp;quot;node&amp;quot;,&lt;br /&gt;
        &amp;quot;event&amp;quot;,&lt;br /&gt;
        &amp;quot;moodle-core_availability-form&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;
=== yui/src/form/build.json ===&lt;br /&gt;
&lt;br /&gt;
Metadata for Shifter about how to build this module. You will probably not need to change this file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;moodle-availability_name-form&amp;quot;,&lt;br /&gt;
  &amp;quot;builds&amp;quot;: {&lt;br /&gt;
    &amp;quot;moodle-availability_name-form&amp;quot;: {&lt;br /&gt;
      &amp;quot;jsfiles&amp;quot;: [&lt;br /&gt;
        &amp;quot;form.js&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;
=== yui/src/form/js/form.js ===&lt;br /&gt;
&lt;br /&gt;
This is the actual JavaScript code for your plugin. It should follow the below format in order to integrate with the core JavaScript. (Of course, you can add extra functions as needed.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
M.availability_name = M.availability_name || {};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form = Y.Object(M.core_availability.plugin);&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.initInner = function(param) {&lt;br /&gt;
    // The &#039;param&#039; variable is the parameter passed through from PHP (you&lt;br /&gt;
    // can have more than one if required).&lt;br /&gt;
&lt;br /&gt;
    // Using the PHP code above it&#039;ll show &#039;The param was: frog&#039;.&lt;br /&gt;
    console.log(&#039;The param was: &#039; + param);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.getNode = function(json) {&lt;br /&gt;
    // This function does the main work. It gets called after the user&lt;br /&gt;
    // chooses to add an availability restriction of this type. You have&lt;br /&gt;
    // to return a YUI node representing the HTML for the plugin controls.&lt;br /&gt;
&lt;br /&gt;
    // Example controls contain only one tickbox.&lt;br /&gt;
    var strings = M.str.availability_name;&lt;br /&gt;
    var html = &#039;&amp;lt;label&amp;gt;&#039; + strings.title + &#039; &amp;lt;input type=&amp;quot;checkbox&amp;quot;/&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
    var node = Y.Node.create(&#039;&amp;lt;span&amp;gt;&#039; + html + &#039;&amp;lt;/span&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
    // Set initial values based on the value from the JSON data in Moodle&lt;br /&gt;
    // database. This will have values undefined if creating a new one.&lt;br /&gt;
    if (json.allow) {&lt;br /&gt;
        node.one(&#039;input&#039;).set(&#039;checked&#039;, true);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Add event handlers (first time only). You can do this any way you&lt;br /&gt;
    // like, but this pattern is used by the existing code.&lt;br /&gt;
    if (!M.availability_name.form.addedEvents) {&lt;br /&gt;
        M.availability_name.form.addedEvents = true;&lt;br /&gt;
        var root = Y.one(&#039;#fitem_id_availabilityconditionsjson&#039;);&lt;br /&gt;
        root.delegate(&#039;click&#039;, function() {&lt;br /&gt;
            // The key point is this update call. This call will update&lt;br /&gt;
            // the JSON data in the hidden field in the form, so that it&lt;br /&gt;
            // includes the new value of the checkbox.&lt;br /&gt;
            M.core_availability.form.update();&lt;br /&gt;
        }, &#039;.availability_name input&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return node;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.fillValue = function(value, node) {&lt;br /&gt;
    // This function gets passed the node (from above) and a value&lt;br /&gt;
    // object. Within that object, it must set up the correct values&lt;br /&gt;
    // to use within the JSON data in the form. Should be compatible&lt;br /&gt;
    // with the structure used in the __construct and save functions&lt;br /&gt;
    // within condition.php.&lt;br /&gt;
    var checkbox = node.one(&#039;input&#039;);&lt;br /&gt;
    value.allow = checkbox.get(&#039;checked&#039;) ? true : false;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.fillErrors = function(errors, node) {&lt;br /&gt;
    // If the user has selected something invalid, this optional&lt;br /&gt;
    // function can be included to report an error in the form. The&lt;br /&gt;
    // error will show immediately as a &#039;Please set&#039; tag, and if the&lt;br /&gt;
    // user saves the form with an error still in place, they&#039;ll see&lt;br /&gt;
    // the actual error text.&lt;br /&gt;
&lt;br /&gt;
    // In this example an error is not possible...&lt;br /&gt;
    if (false) {&lt;br /&gt;
        // ...but this is how you would add one if required. This is&lt;br /&gt;
        // passing your component name (availability_name) and the&lt;br /&gt;
        // name of a string within your lang file (error_message)&lt;br /&gt;
        // which will be shown if they submit the form.&lt;br /&gt;
        errors.push(&#039;availability_name:error_message&#039;);&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== tests/condition_test.php (optional) ===&lt;br /&gt;
&lt;br /&gt;
Normally you would write a unit test for the condition inside this file. See existing conditions for examples.&lt;br /&gt;
&lt;br /&gt;
=== tests/behat/availability_name.feature (optional) ===&lt;br /&gt;
&lt;br /&gt;
Normally you would write a Behat test for the condition in this file. See existing conditions for examples.&lt;br /&gt;
&lt;br /&gt;
=== Other files (optional) ===&lt;br /&gt;
&lt;br /&gt;
You can also include other files; standard Moodle files such as styles.css, and arbitrary PHP and JavaScript files as necessary.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Conditional activities API]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Availability_conditions&amp;diff=57799</id>
		<title>Availability conditions</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Availability_conditions&amp;diff=57799"/>
		<updated>2020-08-25T07:45:04Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: Undo revision 57792 by Tim Hunt (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Availability conditions allow teachers to restrict an activity or section so that only certain users can access it. These are accessed via the [[Availability API]].&lt;br /&gt;
&lt;br /&gt;
Some of the conditions included in standard Moodle are:&lt;br /&gt;
&lt;br /&gt;
* Date (users can only access activity after specified date)&lt;br /&gt;
* Grade (users can only access activity if they have a certain grade in another activity)&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Availability condition plugins were introduced with Moodle 2.7. In previous versions, the older conditional availability API did not support plugins and was limited to the predefined conditions.&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
&lt;br /&gt;
A relatively simple example is the grouping condition which can be found in /availability/condition/grouping. This is the best one to look at, or base your new code on, when starting to implement a new condition.&lt;br /&gt;
&lt;br /&gt;
To see this condition in action:&lt;br /&gt;
&lt;br /&gt;
* Ensure &amp;lt;tt&amp;gt;enableavailability&amp;lt;/tt&amp;gt; option is turned on in the Admin -&amp;gt; Advanced features page.&lt;br /&gt;
* Go to a course and edit any section.&lt;br /&gt;
* Expand the &#039;&#039;&#039;Restrict access&#039;&#039;&#039; heading.&lt;br /&gt;
* Click the &#039;&#039;&#039;Add restriction&#039;&#039;&#039; button.&lt;br /&gt;
* Click &#039;&#039;&#039;Grouping&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== File structure ==&lt;br /&gt;
&lt;br /&gt;
All files for an availability condition plugin go within /availability/condition/name, where &#039;name&#039; is the name of the condition plugin. (The examples below assume this folder, with the plugin called availability_name.)&lt;br /&gt;
&lt;br /&gt;
=== version.php ===&lt;br /&gt;
&lt;br /&gt;
Standard [[version.php]] for the component.&lt;br /&gt;
&lt;br /&gt;
=== lang/en/availability_name.php ===&lt;br /&gt;
&lt;br /&gt;
Language strings for the plugin. Required strings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;pluginname&#039;&#039;&#039; - name of plugin.&lt;br /&gt;
* &#039;&#039;&#039;title&#039;&#039;&#039; - text of button for adding this type of plugin.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; - explanatory text that goes alongside the button in the &#039;add restriction&#039; dialog. &lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;description&#039;] = &#039;Silly example plugin that just has a checkbox for whether to allow access&#039;;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Restriction by silly checkbox&#039;;&lt;br /&gt;
$string[&#039;title&#039;] = &#039;Test plugin&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will usually need to add your own strings for two main purposes:&lt;br /&gt;
&lt;br /&gt;
* Creating suitable form controls for users who are editing the activity settings.&lt;br /&gt;
* Displaying information about the condition.&lt;br /&gt;
&lt;br /&gt;
=== classes/condition.php ===&lt;br /&gt;
&lt;br /&gt;
This PHP class implements the back-end of the condition; in other words, this class contains the code which decides whether a user is allowed to access an activity that uses this condition, or not.&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an outline of the code (with standard PHPdoc comments omitted to save space) for a simple example in which there is a boolean value that controls whether access is allowed or not.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// You must use the right namespace (matching your plugin component name).&lt;br /&gt;
namespace availability_name;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class condition extends \core_availability\condition {&lt;br /&gt;
    // Any data associated with the condition can be stored in member&lt;br /&gt;
    // variables. Here&#039;s an example variable:&lt;br /&gt;
    protected $allow;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($structure) {&lt;br /&gt;
        // Retrieve any necessary data from the $structure here. The&lt;br /&gt;
        // structure is extracted from JSON data stored in the database&lt;br /&gt;
        // as part of the tree structure of conditions relating to an&lt;br /&gt;
        // activity or section.&lt;br /&gt;
        // For example, you could obtain the &#039;allow&#039; value:&lt;br /&gt;
        $this-&amp;gt;allow = $structure-&amp;gt;allow;&lt;br /&gt;
&lt;br /&gt;
        // It is also a good idea to check for invalid values here and&lt;br /&gt;
        // throw a coding_exception if the structure is wrong.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function save() {&lt;br /&gt;
        // Save back the data into a plain array similar to $structure above.&lt;br /&gt;
        return (object)array(&#039;type&#039; =&amp;gt; &#039;name&#039;, &#039;allow&#039; =&amp;gt; $this-&amp;gt;allow);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function is_available($not,&lt;br /&gt;
            \core_availability\info $info, $grabthelot, $userid) {&lt;br /&gt;
        // This function needs to check whether the condition is true&lt;br /&gt;
        // or not for the user specified in $userid. &lt;br /&gt;
&lt;br /&gt;
        // The value $not should be used to negate the condition. Other&lt;br /&gt;
        // parameters provide data which can be used when evaluating the&lt;br /&gt;
        // condition.&lt;br /&gt;
&lt;br /&gt;
        // For this trivial example, we will just use $allow to decide&lt;br /&gt;
        // whether it is allowed or not. In a real condition you would&lt;br /&gt;
        // do some calculation depending on the specified user.&lt;br /&gt;
        $allow = $this-&amp;gt;allow;&lt;br /&gt;
        if ($not) {&lt;br /&gt;
            $allow = !$allow;&lt;br /&gt;
        }&lt;br /&gt;
        return $allow;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_description($full, $not, \core_availability\info $info) {&lt;br /&gt;
        // This function just returns the information that shows about&lt;br /&gt;
        // the condition on editing screens. Usually it is similar to&lt;br /&gt;
        // the information shown if the user doesn&#039;t meet the&lt;br /&gt;
        // condition (it does not depend on the current user).&lt;br /&gt;
        $allow = $not ? !$this-&amp;gt;allow : $this-&amp;gt;allow;&lt;br /&gt;
        return $allow ? &#039;Users are allowed&#039; : &#039;Users not allowed&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_debug_string() {&lt;br /&gt;
        // This function is only normally used for unit testing and&lt;br /&gt;
        // stuff like that. Just make a short string representation&lt;br /&gt;
        // of the values of the condition, suitable for developers.&lt;br /&gt;
        return $this-&amp;gt;allow ? &#039;YES&#039; : &#039;NO&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are other functions you might also want to implement. For example, if your condition should apply to lists of users (in general, conditions which are &#039;permanent&#039; such as group conditions apply to lists, whereas those which are &#039;temporary&#039; such as date or grade conditions do not) then you should also implement is_applied_to_user_lists and filter_user_list functions. To see the full list, look at the PHPdoc for the condition and tree_node classes inside availability/classes.&lt;br /&gt;
&lt;br /&gt;
=== classes/frontend.php ===&lt;br /&gt;
&lt;br /&gt;
You will also need to write a frontend.php class which defines the behaviour of your plugin within the editing form (when a teacher is editing the activity settings).&lt;br /&gt;
&lt;br /&gt;
The class is required, but all the functions are theoretically optional; you can leave them out if you don&#039;t need any special behaviour for that function. In practice it&#039;s likely you will need at least one of them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
namespace availability_name;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class frontend extends \core_availability\frontend {&lt;br /&gt;
&lt;br /&gt;
    protected function get_javascript_strings() {&lt;br /&gt;
        // You can return a list of names within your language file and the&lt;br /&gt;
        // system will include them here. (Should you need strings from another&lt;br /&gt;
        // language file, you can also call $PAGE-&amp;gt;requires-&amp;gt;strings_for_js&lt;br /&gt;
        // manually from here.)&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_javascript_init_params($course, \cm_info $cm = null,&lt;br /&gt;
            \section_info $section = null) {&lt;br /&gt;
        // If you want, you can add some parameters here which will be&lt;br /&gt;
        // passed into your JavaScript init method. If you don&#039;t include&lt;br /&gt;
        // this function, there will be no parameters.&lt;br /&gt;
        return array(&#039;frog&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function allow_add($course, \cm_info $cm = null,&lt;br /&gt;
            \section_info $section = null) {&lt;br /&gt;
        // This function lets you control whether the &#039;add&#039; button for your&lt;br /&gt;
        // plugin appears. For example, the grouping plugin does not appear&lt;br /&gt;
        // if there are no groupings on the course. This helps to simplify&lt;br /&gt;
        // the user interface. If you don&#039;t include this function, it will&lt;br /&gt;
        // appear.&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== yui (folder) ===&lt;br /&gt;
&lt;br /&gt;
These conditions use YUI Shifter to generate JavaScript code. Unfortunately that does mean you need to create a few layers of boilerplate folders.&lt;br /&gt;
&lt;br /&gt;
If you are unfamiliar with Shifter, here is a quick summary:&lt;br /&gt;
&lt;br /&gt;
# Install it according to the instructions here: [[YUI/Shifter]]&lt;br /&gt;
# In a command prompt, change to the directory that contains your plugin and run: &amp;lt;tt&amp;gt;shifter --watch&amp;lt;/tt&amp;gt;&lt;br /&gt;
# Create or modify the files described below.&lt;br /&gt;
&lt;br /&gt;
Shifter will then automatically build your files whenever you change them. You have to remember to run it whenever you make a change. (If you accidentally make a change while it&#039;s not running, run it and then make another change.)&lt;br /&gt;
&lt;br /&gt;
=== yui/src/form/meta/form.json ===&lt;br /&gt;
&lt;br /&gt;
Metadata for Shifter relating to the YUI module that contains the JavaScript for your code. You can edit this file to add any additional required YUI modules.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;moodle-availability_name-form&amp;quot;: {&lt;br /&gt;
    &amp;quot;requires&amp;quot;: [&lt;br /&gt;
        &amp;quot;base&amp;quot;,&lt;br /&gt;
        &amp;quot;node&amp;quot;,&lt;br /&gt;
        &amp;quot;event&amp;quot;,&lt;br /&gt;
        &amp;quot;moodle-core_availability-form&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;
=== yui/src/form/build.json ===&lt;br /&gt;
&lt;br /&gt;
Metadata for Shifter about how to build this module. You will probably not need to change this file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;moodle-availability_name-form&amp;quot;,&lt;br /&gt;
  &amp;quot;builds&amp;quot;: {&lt;br /&gt;
    &amp;quot;moodle-availability_name-form&amp;quot;: {&lt;br /&gt;
      &amp;quot;jsfiles&amp;quot;: [&lt;br /&gt;
        &amp;quot;form.js&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;
=== yui/src/form/js/form.js ===&lt;br /&gt;
&lt;br /&gt;
This is the actual JavaScript code for your plugin. It should follow the below format in order to integrate with the core JavaScript. (Of course, you can add extra functions as needed.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
M.availability_name = M.availability_name || {};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form = Y.Object(M.core_availability.plugin);&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.initInner = function(param) {&lt;br /&gt;
    // The &#039;param&#039; variable is the parameter passed through from PHP (you&lt;br /&gt;
    // can have more than one if required).&lt;br /&gt;
&lt;br /&gt;
    // Using the PHP code above it&#039;ll show &#039;The param was: frog&#039;.&lt;br /&gt;
    console.log(&#039;The param was: &#039; + param);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.getNode = function(json) {&lt;br /&gt;
    // This function does the main work. It gets called after the user&lt;br /&gt;
    // chooses to add an availability restriction of this type. You have&lt;br /&gt;
    // to return a YUI node representing the HTML for the plugin controls.&lt;br /&gt;
&lt;br /&gt;
    // Example controls contain only one tickbox.&lt;br /&gt;
    var strings = M.str.availability_name;&lt;br /&gt;
    var html = &#039;&amp;lt;label&amp;gt;&#039; + strings.title + &#039; &amp;lt;input type=&amp;quot;checkbox&amp;quot;/&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
    var node = Y.Node.create(&#039;&amp;lt;span&amp;gt;&#039; + html + &#039;&amp;lt;/span&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
    // Set initial values based on the value from the JSON data in Moodle&lt;br /&gt;
    // database. This will have values undefined if creating a new one.&lt;br /&gt;
    if (json.allow) {&lt;br /&gt;
        node.one(&#039;input&#039;).set(&#039;checked&#039;, true);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Add event handlers (first time only). You can do this any way you&lt;br /&gt;
    // like, but this pattern is used by the existing code.&lt;br /&gt;
    if (!M.availability_name.form.addedEvents) {&lt;br /&gt;
        M.availability_name.form.addedEvents = true;&lt;br /&gt;
        var root = Y.one(&#039;#fitem_id_availabilityconditionsjson&#039;);&lt;br /&gt;
        root.delegate(&#039;click&#039;, function() {&lt;br /&gt;
            // The key point is this update call. This call will update&lt;br /&gt;
            // the JSON data in the hidden field in the form, so that it&lt;br /&gt;
            // includes the new value of the checkbox.&lt;br /&gt;
            M.core_availability.form.update();&lt;br /&gt;
        }, &#039;.availability_name input&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return node;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.fillValue = function(value, node) {&lt;br /&gt;
    // This function gets passed the node (from above) and a value&lt;br /&gt;
    // object. Within that object, it must set up the correct values&lt;br /&gt;
    // to use within the JSON data in the form. Should be compatible&lt;br /&gt;
    // with the structure used in the __construct and save functions&lt;br /&gt;
    // within condition.php.&lt;br /&gt;
    var checkbox = node.one(&#039;input&#039;);&lt;br /&gt;
    value.allow = checkbox.get(&#039;checked&#039;) ? true : false;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.fillErrors = function(errors, node) {&lt;br /&gt;
    // If the user has selected something invalid, this optional&lt;br /&gt;
    // function can be included to report an error in the form. The&lt;br /&gt;
    // error will show immediately as a &#039;Please set&#039; tag, and if the&lt;br /&gt;
    // user saves the form with an error still in place, they&#039;ll see&lt;br /&gt;
    // the actual error text.&lt;br /&gt;
&lt;br /&gt;
    // In this example an error is not possible...&lt;br /&gt;
    if (false) {&lt;br /&gt;
        // ...but this is how you would add one if required. This is&lt;br /&gt;
        // passing your component name (availability_name) and the&lt;br /&gt;
        // name of a string within your lang file (error_message)&lt;br /&gt;
        // which will be shown if they submit the form.&lt;br /&gt;
        errors.push(&#039;availability_name:error_message&#039;);&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== tests/condition_test.php (optional) ===&lt;br /&gt;
&lt;br /&gt;
Normally you would write a unit test for the condition inside this file. See existing conditions for examples.&lt;br /&gt;
&lt;br /&gt;
=== tests/behat/availability_name.feature (optional) ===&lt;br /&gt;
&lt;br /&gt;
Normally you would write a Behat test for the condition in this file. See existing conditions for examples.&lt;br /&gt;
&lt;br /&gt;
=== Other files (optional) ===&lt;br /&gt;
&lt;br /&gt;
You can also include other files; standard Moodle files such as styles.css, and arbitrary PHP and JavaScript files as necessary.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Conditional activities API]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Availability_conditions&amp;diff=57792</id>
		<title>Availability conditions</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Availability_conditions&amp;diff=57792"/>
		<updated>2020-08-24T09:50:56Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* yui (folder) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Availability conditions allow teachers to restrict an activity or section so that only certain users can access it. These are accessed via the [[Availability API]].&lt;br /&gt;
&lt;br /&gt;
Some of the conditions included in standard Moodle are:&lt;br /&gt;
&lt;br /&gt;
* Date (users can only access activity after specified date)&lt;br /&gt;
* Grade (users can only access activity if they have a certain grade in another activity)&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Availability condition plugins were introduced with Moodle 2.7. In previous versions, the older conditional availability API did not support plugins and was limited to the predefined conditions.&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
&lt;br /&gt;
A relatively simple example is the grouping condition which can be found in /availability/condition/grouping. This is the best one to look at, or base your new code on, when starting to implement a new condition.&lt;br /&gt;
&lt;br /&gt;
To see this condition in action:&lt;br /&gt;
&lt;br /&gt;
* Ensure &amp;lt;tt&amp;gt;enableavailability&amp;lt;/tt&amp;gt; option is turned on in the Admin -&amp;gt; Advanced features page.&lt;br /&gt;
* Go to a course and edit any section.&lt;br /&gt;
* Expand the &#039;&#039;&#039;Restrict access&#039;&#039;&#039; heading.&lt;br /&gt;
* Click the &#039;&#039;&#039;Add restriction&#039;&#039;&#039; button.&lt;br /&gt;
* Click &#039;&#039;&#039;Grouping&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== File structure ==&lt;br /&gt;
&lt;br /&gt;
All files for an availability condition plugin go within /availability/condition/name, where &#039;name&#039; is the name of the condition plugin. (The examples below assume this folder, with the plugin called availability_name.)&lt;br /&gt;
&lt;br /&gt;
=== version.php ===&lt;br /&gt;
&lt;br /&gt;
Standard [[version.php]] for the component.&lt;br /&gt;
&lt;br /&gt;
=== lang/en/availability_name.php ===&lt;br /&gt;
&lt;br /&gt;
Language strings for the plugin. Required strings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;pluginname&#039;&#039;&#039; - name of plugin.&lt;br /&gt;
* &#039;&#039;&#039;title&#039;&#039;&#039; - text of button for adding this type of plugin.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; - explanatory text that goes alongside the button in the &#039;add restriction&#039; dialog. &lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;description&#039;] = &#039;Silly example plugin that just has a checkbox for whether to allow access&#039;;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Restriction by silly checkbox&#039;;&lt;br /&gt;
$string[&#039;title&#039;] = &#039;Test plugin&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will usually need to add your own strings for two main purposes:&lt;br /&gt;
&lt;br /&gt;
* Creating suitable form controls for users who are editing the activity settings.&lt;br /&gt;
* Displaying information about the condition.&lt;br /&gt;
&lt;br /&gt;
=== classes/condition.php ===&lt;br /&gt;
&lt;br /&gt;
This PHP class implements the back-end of the condition; in other words, this class contains the code which decides whether a user is allowed to access an activity that uses this condition, or not.&lt;br /&gt;
&lt;br /&gt;
Here&#039;s an outline of the code (with standard PHPdoc comments omitted to save space) for a simple example in which there is a boolean value that controls whether access is allowed or not.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// You must use the right namespace (matching your plugin component name).&lt;br /&gt;
namespace availability_name;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class condition extends \core_availability\condition {&lt;br /&gt;
    // Any data associated with the condition can be stored in member&lt;br /&gt;
    // variables. Here&#039;s an example variable:&lt;br /&gt;
    protected $allow;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($structure) {&lt;br /&gt;
        // Retrieve any necessary data from the $structure here. The&lt;br /&gt;
        // structure is extracted from JSON data stored in the database&lt;br /&gt;
        // as part of the tree structure of conditions relating to an&lt;br /&gt;
        // activity or section.&lt;br /&gt;
        // For example, you could obtain the &#039;allow&#039; value:&lt;br /&gt;
        $this-&amp;gt;allow = $structure-&amp;gt;allow;&lt;br /&gt;
&lt;br /&gt;
        // It is also a good idea to check for invalid values here and&lt;br /&gt;
        // throw a coding_exception if the structure is wrong.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function save() {&lt;br /&gt;
        // Save back the data into a plain array similar to $structure above.&lt;br /&gt;
        return (object)array(&#039;type&#039; =&amp;gt; &#039;name&#039;, &#039;allow&#039; =&amp;gt; $this-&amp;gt;allow);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function is_available($not,&lt;br /&gt;
            \core_availability\info $info, $grabthelot, $userid) {&lt;br /&gt;
        // This function needs to check whether the condition is true&lt;br /&gt;
        // or not for the user specified in $userid. &lt;br /&gt;
&lt;br /&gt;
        // The value $not should be used to negate the condition. Other&lt;br /&gt;
        // parameters provide data which can be used when evaluating the&lt;br /&gt;
        // condition.&lt;br /&gt;
&lt;br /&gt;
        // For this trivial example, we will just use $allow to decide&lt;br /&gt;
        // whether it is allowed or not. In a real condition you would&lt;br /&gt;
        // do some calculation depending on the specified user.&lt;br /&gt;
        $allow = $this-&amp;gt;allow;&lt;br /&gt;
        if ($not) {&lt;br /&gt;
            $allow = !$allow;&lt;br /&gt;
        }&lt;br /&gt;
        return $allow;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_description($full, $not, \core_availability\info $info) {&lt;br /&gt;
        // This function just returns the information that shows about&lt;br /&gt;
        // the condition on editing screens. Usually it is similar to&lt;br /&gt;
        // the information shown if the user doesn&#039;t meet the&lt;br /&gt;
        // condition (it does not depend on the current user).&lt;br /&gt;
        $allow = $not ? !$this-&amp;gt;allow : $this-&amp;gt;allow;&lt;br /&gt;
        return $allow ? &#039;Users are allowed&#039; : &#039;Users not allowed&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_debug_string() {&lt;br /&gt;
        // This function is only normally used for unit testing and&lt;br /&gt;
        // stuff like that. Just make a short string representation&lt;br /&gt;
        // of the values of the condition, suitable for developers.&lt;br /&gt;
        return $this-&amp;gt;allow ? &#039;YES&#039; : &#039;NO&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are other functions you might also want to implement. For example, if your condition should apply to lists of users (in general, conditions which are &#039;permanent&#039; such as group conditions apply to lists, whereas those which are &#039;temporary&#039; such as date or grade conditions do not) then you should also implement is_applied_to_user_lists and filter_user_list functions. To see the full list, look at the PHPdoc for the condition and tree_node classes inside availability/classes.&lt;br /&gt;
&lt;br /&gt;
=== classes/frontend.php ===&lt;br /&gt;
&lt;br /&gt;
You will also need to write a frontend.php class which defines the behaviour of your plugin within the editing form (when a teacher is editing the activity settings).&lt;br /&gt;
&lt;br /&gt;
The class is required, but all the functions are theoretically optional; you can leave them out if you don&#039;t need any special behaviour for that function. In practice it&#039;s likely you will need at least one of them.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
namespace availability_name;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class frontend extends \core_availability\frontend {&lt;br /&gt;
&lt;br /&gt;
    protected function get_javascript_strings() {&lt;br /&gt;
        // You can return a list of names within your language file and the&lt;br /&gt;
        // system will include them here. (Should you need strings from another&lt;br /&gt;
        // language file, you can also call $PAGE-&amp;gt;requires-&amp;gt;strings_for_js&lt;br /&gt;
        // manually from here.)&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_javascript_init_params($course, \cm_info $cm = null,&lt;br /&gt;
            \section_info $section = null) {&lt;br /&gt;
        // If you want, you can add some parameters here which will be&lt;br /&gt;
        // passed into your JavaScript init method. If you don&#039;t include&lt;br /&gt;
        // this function, there will be no parameters.&lt;br /&gt;
        return array(&#039;frog&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function allow_add($course, \cm_info $cm = null,&lt;br /&gt;
            \section_info $section = null) {&lt;br /&gt;
        // This function lets you control whether the &#039;add&#039; button for your&lt;br /&gt;
        // plugin appears. For example, the grouping plugin does not appear&lt;br /&gt;
        // if there are no groupings on the course. This helps to simplify&lt;br /&gt;
        // the user interface. If you don&#039;t include this function, it will&lt;br /&gt;
        // appear.&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== amd (folder) ===&lt;br /&gt;
&lt;br /&gt;
These conditions use JavaScript code.&lt;br /&gt;
&lt;br /&gt;
TODO, update the below to be relevant to AMD, not YUI.&lt;br /&gt;
&lt;br /&gt;
If you are unfamiliar with Shifter, here is a quick summary:&lt;br /&gt;
&lt;br /&gt;
# Install it according to the instructions here: [[YUI/Shifter]]&lt;br /&gt;
# In a command prompt, change to the directory that contains your plugin and run: &amp;lt;tt&amp;gt;shifter --watch&amp;lt;/tt&amp;gt;&lt;br /&gt;
# Create or modify the files described below.&lt;br /&gt;
&lt;br /&gt;
Shifter will then automatically build your files whenever you change them. You have to remember to run it whenever you make a change. (If you accidentally make a change while it&#039;s not running, run it and then make another change.)&lt;br /&gt;
&lt;br /&gt;
=== yui/src/form/meta/form.json ===&lt;br /&gt;
&lt;br /&gt;
Metadata for Shifter relating to the YUI module that contains the JavaScript for your code. You can edit this file to add any additional required YUI modules.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;moodle-availability_name-form&amp;quot;: {&lt;br /&gt;
    &amp;quot;requires&amp;quot;: [&lt;br /&gt;
        &amp;quot;base&amp;quot;,&lt;br /&gt;
        &amp;quot;node&amp;quot;,&lt;br /&gt;
        &amp;quot;event&amp;quot;,&lt;br /&gt;
        &amp;quot;moodle-core_availability-form&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;
=== yui/src/form/build.json ===&lt;br /&gt;
&lt;br /&gt;
Metadata for Shifter about how to build this module. You will probably not need to change this file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;moodle-availability_name-form&amp;quot;,&lt;br /&gt;
  &amp;quot;builds&amp;quot;: {&lt;br /&gt;
    &amp;quot;moodle-availability_name-form&amp;quot;: {&lt;br /&gt;
      &amp;quot;jsfiles&amp;quot;: [&lt;br /&gt;
        &amp;quot;form.js&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;
=== yui/src/form/js/form.js ===&lt;br /&gt;
&lt;br /&gt;
This is the actual JavaScript code for your plugin. It should follow the below format in order to integrate with the core JavaScript. (Of course, you can add extra functions as needed.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
M.availability_name = M.availability_name || {};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form = Y.Object(M.core_availability.plugin);&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.initInner = function(param) {&lt;br /&gt;
    // The &#039;param&#039; variable is the parameter passed through from PHP (you&lt;br /&gt;
    // can have more than one if required).&lt;br /&gt;
&lt;br /&gt;
    // Using the PHP code above it&#039;ll show &#039;The param was: frog&#039;.&lt;br /&gt;
    console.log(&#039;The param was: &#039; + param);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.getNode = function(json) {&lt;br /&gt;
    // This function does the main work. It gets called after the user&lt;br /&gt;
    // chooses to add an availability restriction of this type. You have&lt;br /&gt;
    // to return a YUI node representing the HTML for the plugin controls.&lt;br /&gt;
&lt;br /&gt;
    // Example controls contain only one tickbox.&lt;br /&gt;
    var strings = M.str.availability_name;&lt;br /&gt;
    var html = &#039;&amp;lt;label&amp;gt;&#039; + strings.title + &#039; &amp;lt;input type=&amp;quot;checkbox&amp;quot;/&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
    var node = Y.Node.create(&#039;&amp;lt;span&amp;gt;&#039; + html + &#039;&amp;lt;/span&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
    // Set initial values based on the value from the JSON data in Moodle&lt;br /&gt;
    // database. This will have values undefined if creating a new one.&lt;br /&gt;
    if (json.allow) {&lt;br /&gt;
        node.one(&#039;input&#039;).set(&#039;checked&#039;, true);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Add event handlers (first time only). You can do this any way you&lt;br /&gt;
    // like, but this pattern is used by the existing code.&lt;br /&gt;
    if (!M.availability_name.form.addedEvents) {&lt;br /&gt;
        M.availability_name.form.addedEvents = true;&lt;br /&gt;
        var root = Y.one(&#039;#fitem_id_availabilityconditionsjson&#039;);&lt;br /&gt;
        root.delegate(&#039;click&#039;, function() {&lt;br /&gt;
            // The key point is this update call. This call will update&lt;br /&gt;
            // the JSON data in the hidden field in the form, so that it&lt;br /&gt;
            // includes the new value of the checkbox.&lt;br /&gt;
            M.core_availability.form.update();&lt;br /&gt;
        }, &#039;.availability_name input&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return node;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.fillValue = function(value, node) {&lt;br /&gt;
    // This function gets passed the node (from above) and a value&lt;br /&gt;
    // object. Within that object, it must set up the correct values&lt;br /&gt;
    // to use within the JSON data in the form. Should be compatible&lt;br /&gt;
    // with the structure used in the __construct and save functions&lt;br /&gt;
    // within condition.php.&lt;br /&gt;
    var checkbox = node.one(&#039;input&#039;);&lt;br /&gt;
    value.allow = checkbox.get(&#039;checked&#039;) ? true : false;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
M.availability_name.form.fillErrors = function(errors, node) {&lt;br /&gt;
    // If the user has selected something invalid, this optional&lt;br /&gt;
    // function can be included to report an error in the form. The&lt;br /&gt;
    // error will show immediately as a &#039;Please set&#039; tag, and if the&lt;br /&gt;
    // user saves the form with an error still in place, they&#039;ll see&lt;br /&gt;
    // the actual error text.&lt;br /&gt;
&lt;br /&gt;
    // In this example an error is not possible...&lt;br /&gt;
    if (false) {&lt;br /&gt;
        // ...but this is how you would add one if required. This is&lt;br /&gt;
        // passing your component name (availability_name) and the&lt;br /&gt;
        // name of a string within your lang file (error_message)&lt;br /&gt;
        // which will be shown if they submit the form.&lt;br /&gt;
        errors.push(&#039;availability_name:error_message&#039;);&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== tests/condition_test.php (optional) ===&lt;br /&gt;
&lt;br /&gt;
Normally you would write a unit test for the condition inside this file. See existing conditions for examples.&lt;br /&gt;
&lt;br /&gt;
=== tests/behat/availability_name.feature (optional) ===&lt;br /&gt;
&lt;br /&gt;
Normally you would write a Behat test for the condition in this file. See existing conditions for examples.&lt;br /&gt;
&lt;br /&gt;
=== Other files (optional) ===&lt;br /&gt;
&lt;br /&gt;
You can also include other files; standard Moodle files such as styles.css, and arbitrary PHP and JavaScript files as necessary.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Conditional activities API]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57787</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57787"/>
		<updated>2020-08-19T09:19:17Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Custom selectors (... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
&lt;br /&gt;
===== Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&lt;br /&gt;
 And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is. When the test runs, this gets converted to an XPath expression, which is what the Behat system acutally uses to locate the right element on the page.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class. The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
The reasons you might want to do this are:&lt;br /&gt;
* It makes your tests easier to read, which makes it easier to be sure that the test is testing the right thing, and being able to read the tests helps people understand your features.&lt;br /&gt;
* If the HTML structure you output changes, then you only need to update the selector definition in one place.&lt;br /&gt;
&lt;br /&gt;
===== Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&lt;br /&gt;
 Given I am on the &amp;quot;Quiz 1&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
 Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you you needs to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about hwo this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
There are two reasons why it is good to use these steps:&lt;br /&gt;
* You are trying to test that your feature works, not Moodle navigation. In the pase we have had many occasions when Moodle navigation changed, and lots of tests failed and had to be fixed. It is better for your tests to start on your feature. (Except, perhpas, it might be appropriate to have one test for the expected method for users to navigate to your feature.)&lt;br /&gt;
* It is much faster because you load fewer irrelevant pages, and in particular the normal loggi step leaves you on the Dashboard page, which is &#039;&#039;&#039;very&#039;&#039;&#039; slow to load.&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#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;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57786</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57786"/>
		<updated>2020-08-19T09:16:23Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Custom navigation targets (And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
&lt;br /&gt;
===== Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&lt;br /&gt;
 And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class.&lt;br /&gt;
&lt;br /&gt;
The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&lt;br /&gt;
 Given I am on the &amp;quot;Quiz 1&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
 Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you you needs to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about hwo this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
There are two reasons why it is good to use these steps:&lt;br /&gt;
* You are trying to test that your feature works, not Moodle navigation. In the pase we have had many occasions when Moodle navigation changed, and lots of tests failed and had to be fixed. It is better for your tests to start on your feature. (Except, perhpas, it might be appropriate to have one test for the expected method for users to navigate to your feature.)&lt;br /&gt;
* It is much faster because you load fewer irrelevant pages, and in particular the normal loggi step leaves you on the Dashboard page, which is &#039;&#039;&#039;very&#039;&#039;&#039; slow to load.&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#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;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57785</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57785"/>
		<updated>2020-08-19T09:12:33Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Custom navigation targets (And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
&lt;br /&gt;
===== Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&lt;br /&gt;
 And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class.&lt;br /&gt;
&lt;br /&gt;
The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&lt;br /&gt;
 Given I am on the &amp;quot;Quiz 1&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
 Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you you needs to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about hwo this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#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;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57782</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57782"/>
		<updated>2020-08-18T13:23:19Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Writing your own steps */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
&lt;br /&gt;
===== Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&lt;br /&gt;
 And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class.&lt;br /&gt;
&lt;br /&gt;
The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&lt;br /&gt;
 Given I am on the &amp;quot;iCMA51&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
 Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you you needs to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about hwo this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#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;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57781</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57781"/>
		<updated>2020-08-18T13:22:57Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Writing your own steps */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
&lt;br /&gt;
===== Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&lt;br /&gt;
 And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class.&lt;br /&gt;
&lt;br /&gt;
The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&lt;br /&gt;
 Given I am on the &amp;quot;iCMA51&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
 Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you you needs to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about hwo this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;) =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#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;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Peer_reviewing&amp;diff=57635</id>
		<title>Peer reviewing</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Peer_reviewing&amp;diff=57635"/>
		<updated>2020-06-19T14:37:13Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Documentation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Process=&lt;br /&gt;
&lt;br /&gt;
Peer review process helps to prepare the issue for integration. The peer reviewer is another developer who was not involved in the development process on the issue and therefore can take a fresh look and notice something that original developer might have forgotten during development. It is important to check that the bug actually is present and the code fixes it without creating new regressions. &lt;br /&gt;
&lt;br /&gt;
==Completing Peer review as a community member==&lt;br /&gt;
&lt;br /&gt;
Any other developer can review any change. That is why it is called &#039;peer&#039; review. However, not everyone has the necessary permissions in the tracker to click the buttons &#039;Start peer review&#039;, &#039;Finish peer review&#039; etc. This should not discourage you from looking at other contributors code and providing comments and feedback. The issue will still need to wait for someone with the right permissions to come along and click the buttons, but they can read your review and then need do no more than double-check some points, which will save a lot of time.&lt;br /&gt;
&lt;br /&gt;
To provide feedback to the developer, leave the issue in &amp;quot;Waiting for Peer Review&amp;quot; (since you don&#039;t have permission to do anything else, and also that makes it easy for someone with sufficient permissions to find the issue and move it forwards). Review the code using the checklist below, including any appropriate comments.  Once finished, please post a comment clearly stating the outcome of your review.  If you think it needs more work then indicate what needs to be changed.  If you are happy with the patch, leave a clear comment that you believe this is ready to be made part of Moodle.  This can then easily be seen by a HQ developer or component lead and they can quickly take appropriate action.&lt;br /&gt;
&lt;br /&gt;
If a followup review happens to identify something you did not find, you have an opportunity to expand your knowledge and provide better reviews in the future as well as having saved everybody else some time.&lt;br /&gt;
&lt;br /&gt;
Feedback to indicate the issue is ready to progress might look like the following;&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
Thanks again for your contribution. I have reviewed the patch:&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
[Copy and paste the checklist here, and complete it]&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
I don&#039;t have permission to use the peer review buttons on this issue. I hope that someone with sufficient permissions will move the issue forwards soon.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Feedback to indicate the issue requires further work might look like the following;&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
Thanks for providing a patch. I think the following points require further work&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
[Copy and paste the checklist here, and complete it]&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Please indicate If you are willing to continue working on this issue and complete the solution.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Can you help with peer reviewing? If so, please see the [https://tracker.moodle.org/issues/?filter=13607 list of issues waiting for peer review].&lt;br /&gt;
&lt;br /&gt;
==Peer review for development by HQ or a known common contributor==&lt;br /&gt;
&lt;br /&gt;
When code comes from a HQ developer or external developer who has been contributing significantly to Moodle and is well acquainted with Moodle standards, peer review is limited to checking the code according to the Checklist below. &lt;br /&gt;
&lt;br /&gt;
If everything is fine, the peer reviewer submits the issue for integration. &lt;br /&gt;
&lt;br /&gt;
If some additional work is needed or the author specifically asked not to submit for integration yet, the peer reviewer clicks on “Finish peer review” and the issue state returns to “Development in progress”. Usually the name of the peer reviewer stays on the issue and if a second peer review is requested it is expected that the same Peer reviewer picks it up. If the peer reviewer is not able to do the second review, they should remove their name and comment about it. Otherwise the issue does not appear on “waiting for peer review” dashboard. Please remember that not all jira users have permission to submit for integration.&lt;br /&gt;
&lt;br /&gt;
==Peer review for external developers==&lt;br /&gt;
&lt;br /&gt;
When the code has come from an external developer, the peer reviewer will also help the developer to lead the issue to integration. In this case the peer reviewer should not use “Finish peer review” button. &lt;br /&gt;
&lt;br /&gt;
If the issue needs additional work, the peer reviewer comments about the suggested changes but does not change the status of the issue and it remains as “Peer review in progress”. If the author of the patch does not reply in 4 days, the peer reviewer may select either to complete the patch themselves or reopen the issue. The decision should be based on the amount of work required to complete the solution, for example, changing coding style or commit message, rebasing, backporting, adding testing instructions, etc. should be performed by the peer reviewer. This may require picking the commits into reviewer’s git branch. &lt;br /&gt;
&lt;br /&gt;
It is very important to give the credit to the author of the code by either keeping their authorship on the commit or adding “Thanks to XXXX for providing the patch” to the commit message. If the author of the patch is not in jira-developers group the special user &#039;moodle.com&#039; should be entered in Assignee field.&lt;br /&gt;
&lt;br /&gt;
There could be situations when the patch is incomplete, does not fix the original issue, creates regressions or otherwise is not correct. As stated above, the peer reviewer should comment about it and wait for the feedback from the author. If there is no feedback, or the author can not work on this issue any more, and the peer reviewer also does not find it easy and important enough to complete, the issue needs to be reopened. Button “Fail peer review” (available to HQ developers only) will reset the issue status to “Reopened”. On this screen peer reviewer should remove assignee, peer reviewer and “patch” label from the issue. This way issue will become available again for anybody who want to work on it. All communication will remain in comments and issue history.&lt;br /&gt;
&lt;br /&gt;
If the issue has passed peer review but the integrator or tester has raised some questions about it, then normally the developer who created the patch would be expected to respond. If they do no respond quickly enough, then the peer reviewer is expected to step in and take responsibility for the change they reviewed.&lt;br /&gt;
&lt;br /&gt;
Once the issue is ready for integration, you can submit it to integration on behalf of the developer. Most external developers (those who are not in the jira-developers group) do not have permission to submit their own issues to integration so cannot do it themselves.&lt;br /&gt;
&lt;br /&gt;
==Replies templates==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
Thanks for providing a patch.&amp;lt;br/&amp;gt;&lt;br /&gt;
I have reviewed your code and can confirm that it addresses the reported issue. We would like to include it in core. Moodle values its contributors and tries to give them credit when possible. If you are interested in your name appearing on the https://moodle.org/dev/contributions.php page you can create a git commit that we will then pull into Moodle. You can learn more about Git and how Moodle uses it at &amp;lt;nowiki&amp;gt;[Git for developers|https://docs.moodle.org/dev/Git_for_developers]&amp;lt;/nowiki&amp;gt; page. Please let me know if you want to prepare a git branch. Or if you don’t have time to go through the whole process at the moment I can pick your patch myself.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
Thanks for providing a patch.&amp;lt;br/&amp;gt;&lt;br /&gt;
Your code looks almost ready for integration into Moodle. There are just some little things that need to be changed to comply with Moodle standards. Can you please take a look at the review results below and tell me if you are able to modify your branch to address them.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
I have reviewed your patch, it addresses the problem and complies with Moodle standards. I&#039;m pushing this issue for integration. Following Moodle &amp;lt;nowiki&amp;gt;[Process|https://docs.moodle.org/dev/Process]&amp;lt;/nowiki&amp;gt; it will go through integration review and testing before being included in the product. There might be additional questions from an integrator and/or a tester at those stages. It would be appreciated if you read tracker emails and can reply to questions if needed. If everything goes well during the next two stages your issue will be included in the next weekly release and your count on https://moodle.org/dev/contributions.php page will increase.&amp;lt;br/&amp;gt;&lt;br /&gt;
Thanks again for your contribution.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
Thanks for providing a patch.&amp;lt;br/&amp;gt;&lt;br /&gt;
Unfortunately this patch does not fully address the reported issue. ... DETAILS...&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
Even though the code does solve the issue in the short-term it is very likely to create regressions ..... &amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
I have spent some time reviewing the patch and I would recommend that you ..... &amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
Please let me know If you are willing to continue working on this issue and complete the solution.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Checklist=&lt;br /&gt;
&lt;br /&gt;
These are points to consider while peer-reviewing issues. Further explanation below.&lt;br /&gt;
&lt;br /&gt;
 [] Syntax&lt;br /&gt;
 [] Output&lt;br /&gt;
 [] Language&lt;br /&gt;
 [] Databases&lt;br /&gt;
 [] Testing (instructions and automated tests)&lt;br /&gt;
 [] Security&lt;br /&gt;
 [] Privacy (see [[Privacy API]])&lt;br /&gt;
 [] Performance and Clustering&lt;br /&gt;
 [] Documentation&lt;br /&gt;
 [] Git&lt;br /&gt;
 [] Third party code&lt;br /&gt;
 [] Sanity check&lt;br /&gt;
 [] Icons&lt;br /&gt;
&lt;br /&gt;
Acceptable check-marks are Y (for yes), N (for no) or - (for not applicable). All N check-marks should be accompanied by an explanation of the problem that still needs to be addressed.&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
To allow the community of Moodle developers to work together, conventions should be followed.&lt;br /&gt;
&lt;br /&gt;
* The code is easy to understand and, where it isn&#039;t, comments have been provided.&lt;br /&gt;
* Variables are named correctly (all lower case, no camel-case, no underscores).&lt;br /&gt;
* Functions are named correctly (all lower case, no camel-case, underscores allowed).&lt;br /&gt;
* PHP DocBlocks have been updated and adhere to [[Coding_style#Documentation_and_comments|coding style guide]].&lt;br /&gt;
* Where functions are being removed, the [[Deprecation]] process is followed.&lt;br /&gt;
* The code doesn&#039;t use [[Deprecated_functions_in_2.0|deprecated functions]].&lt;br /&gt;
* $_GET, $_POST, $_REQUEST, $_COOKIE, and $_SESSION are never used.&lt;br /&gt;
&lt;br /&gt;
See the [[Coding style]] guide for details.&lt;br /&gt;
Most of the previous items list are checked automatically by the CiBot ([[Automated code review]]). So in this case it&#039;s recommended to review the execution results to validate that there aren&#039;t errors. However, take into account that for now, CiBot is not checking all file types (it happens, for instance, with the Javascript files).&lt;br /&gt;
&lt;br /&gt;
==Output==&lt;br /&gt;
Output needs to be controlled by renderers to achieve consistency and correct application of themes.&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* output renders are used to generate output strings, including HTML tags;&lt;br /&gt;
* HTML output is valid html5;&lt;br /&gt;
* no inline styles have been used in HTML output (everything has to be in CSS);&lt;br /&gt;
* CSS has been added to the appropriate CSS files (base, specific area, sometimes canvas); and&lt;br /&gt;
* the code doesn&#039;t use buffered output unless absolutely necessary.&lt;br /&gt;
* all visual output has a RTL alternative included&lt;br /&gt;
&lt;br /&gt;
feedback any notices (E_STRICT, etc) seen into the MDL.&lt;br /&gt;
&lt;br /&gt;
==Language==&lt;br /&gt;
To achieve appropriate internationalisation of Moodle, language strings must be managed correctly.&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* new language strings are named correctly (all lower case, no camel-case, underscores are permissible in some cases);&lt;br /&gt;
* help strings are named and formatted as described in [[Help strings]];&lt;br /&gt;
* language strings are used instead of hard-coded strings for text output;&lt;br /&gt;
* language strings have not been removed or renamed, had their meaning changed or had their parameters changed in stable branches (permitted only in master); and&lt;br /&gt;
* [https://docs.moodle.org/dev/Commit_cheat_sheet#Include_AMOS_script_in_the_commit_if_needed AMOS commands]  have been specified when moving or copying language strings.&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
DB calls are the greatest performance bottleneck in Moodle.&lt;br /&gt;
&lt;br /&gt;
If there is SQL code you can test quickly then do so. &lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* there are minimal DB calls (no excessive use of the DB); and&lt;br /&gt;
* the code uses SQL compatible with all the supported DB engines (check all selected fields appear in an &#039;order by&#039; clause).&lt;br /&gt;
&lt;br /&gt;
==Testing instructions and automated tests==&lt;br /&gt;
It is the developer&#039;s responsibility to test code before integration. Issues should not be sent for peer review without tests so that the peer reviewer can assess their quality and use them to consider the scope of the issue.&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* there are specific testing instructions that state how, as well as what, to test. Please ensure that the testing instructions:&lt;br /&gt;
** [[Testing instructions guide|are in the correct format]];&lt;br /&gt;
** are clear; and&lt;br /&gt;
** are concise;&lt;br /&gt;
* the assignee has tested according to the instructions and verified that they are passing (This is the responsibility of the assignee, not the peer reviewer)&lt;br /&gt;
* new unit tests have been added when there is a change in functionality; and&lt;br /&gt;
* &#039;&#039;&#039;unit tests pass&#039;&#039;&#039; for related areas where changes have been made.&lt;br /&gt;
* &#039;&#039;&#039;Behat tests pass&#039;&#039;&#039; for related areas where changes have been made, especially when it involved UI changes.&lt;br /&gt;
&lt;br /&gt;
==Security==&lt;br /&gt;
The user community relies on Moodle being responsibly secure.&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* user login is checked where an identity is needed;&lt;br /&gt;
* sesskey values are checked before all write actions where appropriate (some read actions as well);&lt;br /&gt;
* capabilities are checked where roles differ; and&lt;br /&gt;
* if the issue itself is a [[Security|security]] issue, the [[Process#Security_issues|security process]] is being followed.&lt;br /&gt;
** Ensure that the fix is &#039;&#039;&#039;not&#039;&#039;&#039; available in a public repository (ie. a personal Github account); stand-alone patches should be provided instead.&lt;br /&gt;
** The issue will not be integrated until just before the next minor version release.&lt;br /&gt;
&lt;br /&gt;
==Privacy==&lt;br /&gt;
The user community relies on Moodle keeping user&#039;s privacy.&lt;br /&gt;
&lt;br /&gt;
See more info [[Privacy_API]]&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* No unnecessary personal user data is saved;&lt;br /&gt;
* All personal user data is saved in compliance with [https://en.wikipedia.org/wiki/General_Data_Protection_Regulation General Data Protection Regulation] (GDPR) which is an EU directive;&lt;br /&gt;
* For all stored data you will need to:&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;
==Performance and clustering==&lt;br /&gt;
It is easy to write code that works sufficiently well when you are working on either small sets of data or with a small number of active users.  Picking performance issues can be quite difficult and can required a complex level of understanding of both the section of code being reviewed, but also other parts that interact with it.&lt;br /&gt;
&lt;br /&gt;
Clustering is when the same code is run on different computers and an end user could send each request to a different computer.  This can produce a number of concurrency issues if not thought through.  One example is;  If you complete an opcache_reset(), no other server except the one you ran it on knows that it happened.  So data can get out of sync.&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* Any filesystem, database or cache accesses are done in the most efficient way.&lt;br /&gt;
* That any code or function that appear expensive are not in critical paths.  eg; They don&#039;t load on every page.&lt;br /&gt;
* The least possible code is running to complete the task, especially looking for hidden loops.  They can appear from calling functions.&lt;br /&gt;
* Any code that runs is not specific to a single node. (eg opcache_reset())  This ensures clusters will run correctly.&lt;br /&gt;
* If the code could affect performance at all, make sure there is a comment noting what was considered.&lt;br /&gt;
** What they did to mitigate performance impact, or why they thought it wasn&#039;t an issue.&lt;br /&gt;
** Why they made certain trade-offs.&lt;br /&gt;
&lt;br /&gt;
==Documentation==&lt;br /&gt;
Work does not stop when code is integrated.&lt;br /&gt;
&lt;br /&gt;
Ensure that:&lt;br /&gt;
* The PHPdoc comments on all classes, methods and fields are useful. (Comments that just repeat the function name are not helpful! Add value.)&lt;br /&gt;
* Where an API has been changed significantly, the relevant upgrade.txt file has been updated with information.&lt;br /&gt;
* Where something has been deprecated, that the comments don&#039;t just say &amp;quot;do NOT use this any more!!!&amp;quot; but acutally say what should be done instead.&lt;br /&gt;
* Appropriate [[Tracker_issue_labels|labels]] have been added when there has been a function change, particularly&lt;br /&gt;
** docs_required (any functional change, usually paired with ui_change),&lt;br /&gt;
** dev_docs_required (any change to APIs, usually paired with api_change),&lt;br /&gt;
** ui_change (any functional change, usually paired with docs_required, except ui_change remains permanetly),&lt;br /&gt;
** api_change (any change to APIs that devs will need to know about, usually paired with dev_docs_required, except api_change remains permanetly),&lt;br /&gt;
** unit_test_required and acceptance_test_required, when there are api or ui changes needing improved coverage, and&lt;br /&gt;
** qa_test_required (significant functional change, not covered by unit/acceptance ones).&lt;br /&gt;
* Also, verify that the components for the issue are correctly set, so maintainers (subscribed by default) will be mailed about issues early in the process.&lt;br /&gt;
&lt;br /&gt;
==Git==&lt;br /&gt;
Ensure that:&lt;br /&gt;
* the commit matches the [[Coding style#Git_commits|Coding style]]&lt;br /&gt;
* the Git history is clean and the work has been rebased to logical commits; and&lt;br /&gt;
* the original author of the work provided as a patch has been given credit within the commit (as author of in the commit message if changes were made).&lt;br /&gt;
&lt;br /&gt;
==Third party code==&lt;br /&gt;
Does the change contain [[Plugin with third party libraries|third party code]]? If so, ensure that:&lt;br /&gt;
&lt;br /&gt;
* The code is licensed under a [http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses| GPL compatible license].&lt;br /&gt;
* The instructions for upgrading/importing the library and contained within a readme_moodle.txt file.&lt;br /&gt;
* The library is recorded in a thirdparty.xml file, including licensing information.&lt;br /&gt;
* Third party code has been scanned to check for url accessible entry points that could be exploited. These should either be disabled, or if required functionality they should be checked for security weaknesses.&lt;br /&gt;
* Does not duplicate the functionality of any existing api or third party library in core.&lt;br /&gt;
* Any modifications to third party code are recorded in readme_moodle.txt&lt;br /&gt;
&lt;br /&gt;
==Sanity check==&lt;br /&gt;
Ensure that:&lt;br /&gt;
* the code seems to solve the described problem completely within its reported scope (and further issues have been created to resolve remaining parts or further refactoring);&lt;br /&gt;
* the code makes sense in relation to the broader codebase (look at the whole function, not just the altered code); and&lt;br /&gt;
* the developer has searched for and fixed other areas that may also have been affected by the same problem.&lt;br /&gt;
* verify that the related component maintainers, if known, have participated and are aware of the issue (as assignee, or existing comments...). If they have not, please perform a friendly &amp;lt;tt&amp;gt;@mention&amp;lt;/tt&amp;gt; to make them aware about the issue. A list of component leads is available here: https://docs.moodle.org/dev/Component_Leads&lt;br /&gt;
* if any version numbers have been changed in [[version.php]] files, then the changes follow [[Moodle_versions#How_to_increment_version_numbers_in_core|the rule for updating version numbers in core]].&lt;br /&gt;
* there are comments on tracker explaining why current approach was taken and why other options (especially large issues). If not comment asking them to explain&lt;br /&gt;
&lt;br /&gt;
==Icons==&lt;br /&gt;
Are new icons being introduced? If so, ensure that:&lt;br /&gt;
* the icons abide by our [https://docs.moodle.org/dev/Moodle_icons icon guidelines] with regards to size, design and format&lt;br /&gt;
* the icons are do not unnecessarily add new ways of expressing existing concepts&lt;br /&gt;
* the icons are in a pix folder that makes sense&lt;br /&gt;
&lt;br /&gt;
==See Also==&lt;br /&gt;
* [http://moodle.org/plugins/view.php?plugin=local_codechecker Code checker plugin]&lt;br /&gt;
&lt;br /&gt;
[[Category: Processes]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Commit_cheat_sheet&amp;diff=57553</id>
		<title>Commit cheat sheet</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Commit_cheat_sheet&amp;diff=57553"/>
		<updated>2020-05-29T14:03:10Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Split your work into a logical set of patches */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;You can consider this page as a list to check before you submit a patch for inclusion into Moodle.&lt;br /&gt;
&lt;br /&gt;
== Split your work into a logical set of patches ==&lt;br /&gt;
&lt;br /&gt;
Keep in mind that your commits will be reviewed before they are accepted. If the patch does one clear thing and does it well, the review process is fun. Git allows you to prepare patches on your branch into a sequence of logical steps. For example, when changing some API, divide the change into two steps. In the first commit, change the API. In the following commit, change all places that use the API. &amp;lt;tt&amp;gt;git rebase -i&amp;lt;/tt&amp;gt; is one of the commands you can use to do this.&lt;br /&gt;
&lt;br /&gt;
== Provide clear commit messages ==&lt;br /&gt;
&lt;br /&gt;
Consider the commit message as an email for the developer who would explore the change in the future. We recommend to follow common Git guidelines for formatting. The first line of commit message is generally treated as commit subject and should not exceed 72 characters. Include the MDL issue number and the area/component at the beginning of the subject line. Please note that &#039;code area&#039; refers to the code areas being affected by this commit - correct the MDL&#039;s components reported against if needed. If you are writing more than one line, the second line must be empty. All the lines should though wrap at the 72nd mark. We can only hardly follow the [https://www.google.com/?q=git%2050/72%20rule 50/72 rule] in Moodle given the extra information expected at first (subject) line.&lt;br /&gt;
&lt;br /&gt;
    MDL-xxxxx code area: short description of the patch&lt;br /&gt;
    &lt;br /&gt;
    The subject line is followed by an empty line and then a paragraph or two of&lt;br /&gt;
    a longer description follows if necessary. This longer description is useful&lt;br /&gt;
    for issues with longer history of comments in the linked MDL so that it&lt;br /&gt;
    summarizes the patch without the need to go through the whole discussion.&lt;br /&gt;
    &lt;br /&gt;
    Obviously, avoid messages like &amp;quot;as agreed in the chat&amp;quot; as they will become&lt;br /&gt;
    useless after a relatively short time.&lt;br /&gt;
&lt;br /&gt;
Most of Git tools are optimised for this format and they can display the log of commits best then.&lt;br /&gt;
&lt;br /&gt;
::Tip: the command &amp;lt;tt&amp;gt;git log --no-merges&amp;lt;/tt&amp;gt; will show you recent commit messages. Hopefully those are all good examples to copy.&lt;br /&gt;
&lt;br /&gt;
== Names in the commit message ==&lt;br /&gt;
&lt;br /&gt;
Retain the authorship of the patch. If the patch was submitted by someone else - for example a community member who published it in the tracker or in a forum - use the --author parameter. Make sure that your &#039;&#039;real&#039;&#039; name and contact email are recorded in patch. We use real names written in capital letters like &amp;quot;John Smith&amp;quot;. Please do not use names like &amp;lt;strike&amp;gt;&amp;quot;john smith&amp;quot;, &amp;quot;John S&amp;quot;, &amp;quot;johnny7887&amp;quot;&amp;lt;/strike&amp;gt;. If you use --author parameter, apply the same rules for the name of the author. See almost any Git tutorial on how to set your name and email in the global Git configuration.&lt;br /&gt;
&lt;br /&gt;
== Provide clear and operational instructions to test your patch ==&lt;br /&gt;
&lt;br /&gt;
In the tracker issue, please describe how the change can be tested. Please avoid vague phrases like &amp;quot;Make sure there is no regression in the core&amp;quot; or &amp;quot;Test all places where XXX is used&amp;quot;. Also, try to avoid requiring resources that are really difficult to gather, if possible - as in &amp;quot;Use production data from a server with 100.000+ students&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
If you have permission, edit the tracker issue and put the testing instructions into the &amp;quot;Testing instructions&amp;quot; field. If you do not have permission to edit that field, then write them in a comment on the tracker issue.&lt;br /&gt;
&lt;br /&gt;
It helps if you state your estimation of the testing difficulty so that testers can pick issues for them:&lt;br /&gt;
&lt;br /&gt;
;Easy : (average community member should be able to test it) - can be tested pretty easily via the web interface only at a public test site&lt;br /&gt;
;Moderate : (knowledgeable administrator should be able to test it) - requires local installation, for example to test some 1.9 -&amp;gt; 2.0 upgrade steps or some non-standard environment (for example MNet features, specific platform etc)&lt;br /&gt;
;Hard : (development skills are required to test it) - for example may require data hacking at SQL level to simulate data corruption or modifying the code to reproduce the problem&lt;br /&gt;
&lt;br /&gt;
Example testing instructions:&lt;br /&gt;
&lt;br /&gt;
    (difficulty: easy, requires teacher access to a course)&lt;br /&gt;
    1. Log in as a teacher and go to a course&lt;br /&gt;
    2. Turn editing mode on&lt;br /&gt;
    3. TEST: Make sure that the control icons appear next to the activity titles&lt;br /&gt;
    4. Turn editing mode off&lt;br /&gt;
    5. TEST: Make sure that the control icons are not displayed now&lt;br /&gt;
&lt;br /&gt;
== Introducing new strings ==&lt;br /&gt;
&lt;br /&gt;
Firstly, think twice and try to think in a non-English language. Any string you introduce is supposed to be translated by translators who usually do it for free in their own time. Do not waste their time by using get_string() for debugging messages that are likely to almost never appear. It is warmly recommended to let Helen review your strings before you submit them. This way we can keep the terminology and the style consistent. When introducing new strings, keep them alphabetically sorted. Using a self-descriptive names of the string identifier and the placeholder properties helps the translators to guess the context. Compare the following&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;grade&#039;] = &#039;Grade {$a}&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
with&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;maxgradevalue&#039;] = &#039;Grade {$a-&amp;gt;value}&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the first case, it is pretty difficult to guess whether the &amp;quot;Grade&amp;quot; in the string is a noun (as in &amp;quot;Grade 12/30&amp;quot;) or a verb (as in &amp;quot;Grade submission&amp;quot;). In many languages, the translation depends on it. The second case is more self-descriptive as it indicates that the placeholder will contain a value.&lt;br /&gt;
&lt;br /&gt;
Another good example of how _not_ to name string identifiers would be something like&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;post&#039;] = &#039;Post&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that there is no clue if the &amp;quot;post&amp;quot; here is used as a noun (e.g. describing one particular forum post) or a verb (such as an action of posting into a forum). In many languages, the translation is different for either case.&lt;br /&gt;
&lt;br /&gt;
See [[Help strings]] if you are introducing a new help string.&lt;br /&gt;
&lt;br /&gt;
== Include AMOS script in the commit if needed ==&lt;br /&gt;
&lt;br /&gt;
If you change the identifier of a string or split a string into two forks, provide a script for AMOS in the commit message. Since Moodle 2.0, the translations are kept on separated branches again. The AMOS plugin at http://lang.moodle.org tracks the changes in string files and automatically records modifications, additions and removals of strings. Therefore strings can be re-worded freely on stable branches and should be removed from the master branch if they are not needed any more (do not remove strings from stable branches).&lt;br /&gt;
&lt;br /&gt;
If you change the identifier of the string (that is the key in the $string array), move the string from one file to another or you are introducing a new string as a copy of some current one, you should provide instructions for AMOS so that the action can be applied in all language packs. That will save valuable translators&#039; time. Instructions for AMOS are being put into the commit message of a commit that modifies the original English string files. The commit message containing such a script may look like this:&lt;br /&gt;
&lt;br /&gt;
    MDL-xxxxx code area: short description of the patch&amp;lt;br /&amp;gt;&lt;br /&gt;
    It is recommended to leave a blank line between the commit message and the&lt;br /&gt;
    script block.&amp;lt;br /&amp;gt;&lt;br /&gt;
    AMOS BEGIN&lt;br /&gt;
     MOV [configfoobar,core_admin],[foobar_desc,core_admin]&lt;br /&gt;
     CPY [submission,mod_assignment],[submission,mod_workshop]&lt;br /&gt;
    AMOS END&lt;br /&gt;
&lt;br /&gt;
See [[Languages/AMOS#AMOS_script]] for more details of the syntax. See [http://git.moodle.org/gw?p=moodle.git&amp;amp;a=search&amp;amp;h=HEAD&amp;amp;st=commit&amp;amp;s=AMOS+BEGIN the log history] real examples of usage.&lt;br /&gt;
&lt;br /&gt;
==Removing strings==&lt;br /&gt;
&lt;br /&gt;
When a new feature completely replaces an existing feature, any strings which are no longer used should be removed from the code in the master branch. See [[String deprecation]] for more information.&lt;br /&gt;
&lt;br /&gt;
== Main version changes ==&lt;br /&gt;
&lt;br /&gt;
If your commit requires a change to the main version number in version.php (and corresponding upgrade in lib/db/upgrade.php), you should increment that version number by .01, and let integrators deal with merge conflicts (for example if multiple people that week submit several .01 updates).&lt;br /&gt;
&lt;br /&gt;
(Note there may be policies about avoiding the type of changes which require version.php updates, especially in stable branches.)&lt;br /&gt;
&lt;br /&gt;
[[Category:Git]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Quiz_database_structure&amp;diff=57362</id>
		<title>Quiz database structure</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Quiz_database_structure&amp;diff=57362"/>
		<updated>2020-04-25T10:54:03Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Quiz settings and runtime overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Quiz developer docs}}&lt;br /&gt;
&lt;br /&gt;
This page documents the database tables used by the quiz module.&lt;br /&gt;
&lt;br /&gt;
==Quiz settings and runtime overview==&lt;br /&gt;
&lt;br /&gt;
It is helpful to distinguish between quiz settings, which is where we store information about how the teacher has set up the quiz, and &#039;runtime&#039; (not a great name) where we store information about people&#039;s attempts at the quiz.&lt;br /&gt;
&lt;br /&gt;
Note that information about the attempter&#039;s interaction with each question is [[Overview_of_the_Moodle_question_engine#Database_tables|stored by the question engine]].&lt;br /&gt;
&lt;br /&gt;
Note: the diagram below is a bit out of date. The significant change is that quiz_question_instances has been renamed quiz_slots.&lt;br /&gt;
[[Image:Quiz_database.png|560px]]&lt;br /&gt;
&lt;br /&gt;
[[Image:Quiz_database.dia]] [http://projects.gnome.org/dia/ Dia ] file, should you wish to have a copy of this diagram in an editable format.&lt;br /&gt;
&lt;br /&gt;
==Common field types==&lt;br /&gt;
&lt;br /&gt;
* Fields that hold an overall score, like quiz.grade, should be NUMBER(10,5).&lt;br /&gt;
* Fields that hold an individual question score, like quiz_question_instances.grade, should be NUMBER(12,7).&lt;br /&gt;
&lt;br /&gt;
==Detailed table descriptions==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.x dev, you can get these by going to Administration -&amp;gt; Development -&amp;gt; XMLDB and clicking on the [Doc] link next in any of the relevant rows (mod/quiz/db, mod/quiz/report/&#039;&#039;xxx&#039;&#039;/db). Looking directly there is much more likely to be up-to-date than relying on information that has been copied here.&lt;br /&gt;
&lt;br /&gt;
(Wouldn&#039;t it be nice if that documentation was automatically built and available online.)&lt;br /&gt;
&lt;br /&gt;
==Rough change-log==&lt;br /&gt;
&lt;br /&gt;
===Moodle 2.3===&lt;br /&gt;
&lt;br /&gt;
* New column quiz_attempts.currentpage for MDL-3054.&lt;br /&gt;
* New column quiz.navmethod for MDL-11047.&lt;br /&gt;
* New columns quiz.gradeperiod, quiz.overduehandling and quiz_attempts.state for MDL-3030.&lt;br /&gt;
* Old columns quiz_reports.cron and quiz_reports.lastcron dropped (MDL-30635). (config_plugins is now used, as for other plugin types.) &lt;br /&gt;
&lt;br /&gt;
===Moodle 2.2===&lt;br /&gt;
&lt;br /&gt;
* Old quiz.popup column replaced by quiz.browsersecurity&lt;br /&gt;
&lt;br /&gt;
===Moodle 2.1 (new question engine)===&lt;br /&gt;
&lt;br /&gt;
* Old quiz.optionflags and quiz.penaltyscheme columns replaced by quiz.preferredbehaviour.&lt;br /&gt;
* New quiz.showblocks column.&lt;br /&gt;
* Old quiz.review column split into seven new quiz.review* columns.&lt;br /&gt;
&lt;br /&gt;
===Moodle 2.0===&lt;br /&gt;
&lt;br /&gt;
* New field quiz.introformat.&lt;br /&gt;
* New field quiz.questiondecimalpoints.&lt;br /&gt;
* New field quiz.showuserpicture. See MDL-3156.&lt;br /&gt;
* All the quiz report tables are new. See [[Quiz_report_enhancements]]&lt;br /&gt;
* All fields that store grades were reviewed and set to the recommended types mentioned above.&lt;br /&gt;
* Never used quiz_question_versions table was removed.&lt;br /&gt;
* New table quiz_overrides for MDL-16478.&lt;br /&gt;
&lt;br /&gt;
===Moodle 1.9===&lt;br /&gt;
&lt;br /&gt;
* Time limit field changed to int(10).&lt;br /&gt;
&lt;br /&gt;
===Moodle 1.7===&lt;br /&gt;
&lt;br /&gt;
* New table quiz_feedback.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Question_database_structure| Question bank/engine database structure]]&lt;br /&gt;
* [[Database_schema_introduction|Database schema introduction]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Quiz]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Quiz_database_structure&amp;diff=57361</id>
		<title>Quiz database structure</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Quiz_database_structure&amp;diff=57361"/>
		<updated>2020-04-25T10:53:52Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Quiz settings and runtime overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Quiz developer docs}}&lt;br /&gt;
&lt;br /&gt;
This page documents the database tables used by the quiz module.&lt;br /&gt;
&lt;br /&gt;
==Quiz settings and runtime overview==&lt;br /&gt;
&lt;br /&gt;
It is helpful to distinguish between quiz settings, which is where we store information about how the teacher has set up the quiz, and &#039;runtime&#039; (not a great name) where we store information about people&#039;s attempts at the quiz.&lt;br /&gt;
&lt;br /&gt;
Note that information about the attempter&#039;s interaction with each question is [[Overview_of_the_Moodle_question_engine#Database_tables|stored by the question engine]].&lt;br /&gt;
&lt;br /&gt;
Note: the diagram below is a bit out of date. The significant change is that quiz_question_instances has been renamed quiz_slot.&lt;br /&gt;
[[Image:Quiz_database.png|560px]]&lt;br /&gt;
&lt;br /&gt;
[[Image:Quiz_database.dia]] [http://projects.gnome.org/dia/ Dia ] file, should you wish to have a copy of this diagram in an editable format.&lt;br /&gt;
&lt;br /&gt;
==Common field types==&lt;br /&gt;
&lt;br /&gt;
* Fields that hold an overall score, like quiz.grade, should be NUMBER(10,5).&lt;br /&gt;
* Fields that hold an individual question score, like quiz_question_instances.grade, should be NUMBER(12,7).&lt;br /&gt;
&lt;br /&gt;
==Detailed table descriptions==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.x dev, you can get these by going to Administration -&amp;gt; Development -&amp;gt; XMLDB and clicking on the [Doc] link next in any of the relevant rows (mod/quiz/db, mod/quiz/report/&#039;&#039;xxx&#039;&#039;/db). Looking directly there is much more likely to be up-to-date than relying on information that has been copied here.&lt;br /&gt;
&lt;br /&gt;
(Wouldn&#039;t it be nice if that documentation was automatically built and available online.)&lt;br /&gt;
&lt;br /&gt;
==Rough change-log==&lt;br /&gt;
&lt;br /&gt;
===Moodle 2.3===&lt;br /&gt;
&lt;br /&gt;
* New column quiz_attempts.currentpage for MDL-3054.&lt;br /&gt;
* New column quiz.navmethod for MDL-11047.&lt;br /&gt;
* New columns quiz.gradeperiod, quiz.overduehandling and quiz_attempts.state for MDL-3030.&lt;br /&gt;
* Old columns quiz_reports.cron and quiz_reports.lastcron dropped (MDL-30635). (config_plugins is now used, as for other plugin types.) &lt;br /&gt;
&lt;br /&gt;
===Moodle 2.2===&lt;br /&gt;
&lt;br /&gt;
* Old quiz.popup column replaced by quiz.browsersecurity&lt;br /&gt;
&lt;br /&gt;
===Moodle 2.1 (new question engine)===&lt;br /&gt;
&lt;br /&gt;
* Old quiz.optionflags and quiz.penaltyscheme columns replaced by quiz.preferredbehaviour.&lt;br /&gt;
* New quiz.showblocks column.&lt;br /&gt;
* Old quiz.review column split into seven new quiz.review* columns.&lt;br /&gt;
&lt;br /&gt;
===Moodle 2.0===&lt;br /&gt;
&lt;br /&gt;
* New field quiz.introformat.&lt;br /&gt;
* New field quiz.questiondecimalpoints.&lt;br /&gt;
* New field quiz.showuserpicture. See MDL-3156.&lt;br /&gt;
* All the quiz report tables are new. See [[Quiz_report_enhancements]]&lt;br /&gt;
* All fields that store grades were reviewed and set to the recommended types mentioned above.&lt;br /&gt;
* Never used quiz_question_versions table was removed.&lt;br /&gt;
* New table quiz_overrides for MDL-16478.&lt;br /&gt;
&lt;br /&gt;
===Moodle 1.9===&lt;br /&gt;
&lt;br /&gt;
* Time limit field changed to int(10).&lt;br /&gt;
&lt;br /&gt;
===Moodle 1.7===&lt;br /&gt;
&lt;br /&gt;
* New table quiz_feedback.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Question_database_structure| Question bank/engine database structure]]&lt;br /&gt;
* [[Database_schema_introduction|Database schema introduction]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Quiz]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=External_services_description&amp;diff=57330</id>
		<title>External services description</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=External_services_description&amp;diff=57330"/>
		<updated>2020-04-21T11:55:32Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* services.php */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
This document explains how we describe and where we store the external services and functions in Moodle 2.0.&lt;br /&gt;
&lt;br /&gt;
===Service discovery===&lt;br /&gt;
We store the service descriptions and a part of the function descriptions in the component/db/services.php file. Function implementations are located in externallib.php files - this file name is not mandatory, but it is strongly recommended. externallib.php files also contain the other part of the function descriptions (the function parameters descriptions).&lt;br /&gt;
&lt;br /&gt;
During the upgrades the descriptions are parsed by a service discovery function that fills the database tables. Administrative UI may be used to change some configuration details. Services can also be defined via the admin UI, but it is recommended to use the new local plugin (look at the readme.txt into the Moodle local folder) when adding custom new services.&lt;br /&gt;
&lt;br /&gt;
===Administration pages===&lt;br /&gt;
Moodle administrators will be able to manage services. By default all services will be disabled.&lt;br /&gt;
&lt;br /&gt;
List of administration functionalities:&lt;br /&gt;
* enable a service (all the functions into this service will be available)&lt;br /&gt;
* associate a web service user (the user has web service capability) to some services =&amp;gt; a user can only call a service that he is associated with&lt;br /&gt;
* create a custom service (name the service + add and remove existing external functions from this service)&lt;br /&gt;
* search easily function by component in order to create a custom service&lt;br /&gt;
&lt;br /&gt;
===external_functions table===&lt;br /&gt;
This table lists the web service functions. It makes sense to call it external_functions as 1 web service function &amp;lt;=&amp;gt; 1 external function. This table maps the web service function name to the actual implementation of that function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
| varchar(200)&lt;br /&gt;
| &lt;br /&gt;
| the web service name (e.g. the unique name of the external function in the web service API) - a unique identifier for each function; ex.: core_get_users, mod_assignment_submit&lt;br /&gt;
|-&lt;br /&gt;
| classname&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| &lt;br /&gt;
| name of the class that contains method implementation; ex.: core_user_external, mod_assignment_external&lt;br /&gt;
|-&lt;br /&gt;
| methodname&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| &lt;br /&gt;
| static method; ex.: get_users, submit&lt;br /&gt;
|-&lt;br /&gt;
| classpath&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| NULL&lt;br /&gt;
| optional path to file with class definition - recommended for core classes only, null means use component/externallib.php; this path is relative to dirroot; ex.: user/externallib.php&lt;br /&gt;
|-&lt;br /&gt;
| component&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| &lt;br /&gt;
| Component where function defined, needed for automatic updates. Service description file is found in the db folder of this component.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;description&amp;lt;/font&amp;gt;&lt;br /&gt;
| &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;text&amp;lt;/font&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
| &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;A short human readable description of the function. It will be used to generate documentation and help the administrator to search a web service function. (NB: decide later if it&#039;s really improving the performance compare to add an access to the phpfile)&amp;lt;/font&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Detailed function description is stored as a complex PHP structure in the implementation class using suffix _parameters and _returns.&lt;br /&gt;
&lt;br /&gt;
===external_services table===&lt;br /&gt;
A &#039;&#039;service&#039;&#039; is defined as a group of external functions. The main purpose of these &#039;&#039;services&#039;&#039; is to allow defining of granular access control for multiple external systems.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;name&#039;&#039;&#039; &lt;br /&gt;
| varchar(150)&lt;br /&gt;
| &lt;br /&gt;
| Name of service (gradeexport_xml_export, mod_chat_export) - appears on the admin page. This name is not human readable when displayed into administration. We will use lang file to translate them automatically if a lang string exist.&amp;lt;/font&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| enabled&lt;br /&gt;
| int(1)&lt;br /&gt;
| 0&lt;br /&gt;
| service enabled, for security reasons some services may be disabled- administrators may disable any service via the admin UI&lt;br /&gt;
|-&lt;br /&gt;
| requiredcapability&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| NULL&lt;br /&gt;
| if capability name specified, user needs to have this permission in security context or in system context if context restriction not specified&lt;br /&gt;
|-&lt;br /&gt;
| restrictedusers&lt;br /&gt;
| int(1)&lt;br /&gt;
| 1&lt;br /&gt;
| 1 means on users explicitly listed in external_services_users may access this service, 0 means any user may use this service&lt;br /&gt;
|-&lt;br /&gt;
| component&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| NULL&lt;br /&gt;
| Component where service defined - null means custom service defined in admin UI.&lt;br /&gt;
|-&lt;br /&gt;
| timecreated&lt;br /&gt;
| bigint(10)&lt;br /&gt;
| &lt;br /&gt;
| The time the service was enabled or defined?&lt;br /&gt;
|-&lt;br /&gt;
| timemodified&lt;br /&gt;
| bigint(10)&lt;br /&gt;
| &lt;br /&gt;
| The last time the service was updated&lt;br /&gt;
|- &lt;br /&gt;
| shortname&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| &lt;br /&gt;
| The short name of the service&lt;br /&gt;
|- &lt;br /&gt;
| downloadfiles&lt;br /&gt;
| tinyint(1)&lt;br /&gt;
| &lt;br /&gt;
| A flag for something to do with download and files?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===external_services_functions table===&lt;br /&gt;
Lists all functions that are available in each service.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;externalserviceid&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| foreign key, reference external_services.id&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;functionname&#039;&#039;&#039; &lt;br /&gt;
| varchar(200)&lt;br /&gt;
| &lt;br /&gt;
| unique name of external function: references external_functions.name; the string value here simplifies service discovery and maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Function description ===&lt;br /&gt;
&lt;br /&gt;
We need to describe each function in detail, including input and output, so that we can:&lt;br /&gt;
&lt;br /&gt;
* help programmers quickly understand what they have to send and what to expect&lt;br /&gt;
* validate all incoming data as automatically as possible for security/correctness&lt;br /&gt;
* generate WSDL files for SOAP&lt;br /&gt;
* generate documentation for other protocols&lt;br /&gt;
&lt;br /&gt;
There are three main parts working together to do this:&lt;br /&gt;
&lt;br /&gt;
====services.php====&lt;br /&gt;
&lt;br /&gt;
In the db/services.php of each component, is a structure something like this to describe the web service functions, and optionally, any larger services built up of several functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$functions = array(&lt;br /&gt;
    &#039;moodle_user_create_users&#039; =&amp;gt; array(           //web service name (unique in all Moodle)&lt;br /&gt;
        &#039;classname&#039;   =&amp;gt; &#039;moodle_user_external&#039;, //class containing the function implementation&lt;br /&gt;
        &#039;methodname&#039;  =&amp;gt; &#039;create_users&#039;,              //name of the function into the class&lt;br /&gt;
        &#039;classpath&#039;   =&amp;gt; &#039;user/externallib.php&#039;,     //file containing the class (only used for core external function, not needed if your file is &#039;component/externallib.php&#039;),&lt;br /&gt;
        &#039;description&#039; =&amp;gt; &#039;create some users&#039;,&lt;br /&gt;
        &#039;type&#039; =&amp;gt; &#039;write&#039;,&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;
&lt;br /&gt;
$services = array( &lt;br /&gt;
    &#039;servicename&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;functions&#039; =&amp;gt; array (&#039;functionname&#039;, &#039;secondfunctionname&#039;), //web service function name&lt;br /&gt;
        &#039;requiredcapability&#039; =&amp;gt; &#039;some/capability:specified&#039;,                  &lt;br /&gt;
        &#039;restrictedusers&#039; =&amp;gt; 1,&lt;br /&gt;
        &#039;enabled&#039;=&amp;gt;0, //used only when installing the services&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function name is arbitrary, but must follow [[Web_service_API_functions#Naming_convention|the naming convention]]. This helps ensure that it is globally unique.&lt;br /&gt;
&lt;br /&gt;
The actual param description and description of returned values can be obtained from the same class by static method calls by adding &#039;_parameters&#039; and &#039;_returns&#039; to the methodname value.&lt;br /&gt;
&lt;br /&gt;
====externallib.php====&lt;br /&gt;
&lt;br /&gt;
It&#039;s the file referenced as the location for the web service function implementation. It also contains complete descriptions of the required parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Base description classes:&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
abstract class external_description {&lt;br /&gt;
    public $desc; //human readable description&lt;br /&gt;
    public $required;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($desc, $required) {&lt;br /&gt;
        $this-&amp;gt;desc = $desc;&lt;br /&gt;
        $this-&amp;gt;required = $required;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_value extends external_description {&lt;br /&gt;
    public $type;&lt;br /&gt;
    public $default;&lt;br /&gt;
    public $allownull;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($type, $desc=&#039;&#039;, $required=VALUE_REQUIRED, $default=null, $allownull=true) {&lt;br /&gt;
        parent::_construct($desc, $required);&lt;br /&gt;
        $this-&amp;gt;type      = $type;&lt;br /&gt;
        $this-&amp;gt;default   = $default;&lt;br /&gt;
        $this-&amp;gt;allownull = $allownull;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_single_structure extends external_description {&lt;br /&gt;
    public $keys; //an associative array of external_description&lt;br /&gt;
&lt;br /&gt;
    public function __construct(array $keys, $desc=&#039;&#039;, $required=VALUE_REQUIRED) {&lt;br /&gt;
        parent::_construct($desc, $required);&lt;br /&gt;
        $this-&amp;gt;keys = $keys; //key=&amp;gt;external_description&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_multiple_structure extends external_description {&lt;br /&gt;
    public $content; //the content type of the list =&amp;gt; external_description&lt;br /&gt;
&lt;br /&gt;
    public function __construct(external_description $content, $desc=&#039;&#039;, $required=VALUE_REQUIRED) {&lt;br /&gt;
        parent::_construct($desc, $required);&lt;br /&gt;
        $this-&amp;gt;content = $content;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_function_parameters extends external_single_structure {&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Externallib.php examples:&#039;&#039;&amp;lt;br/&amp;gt;&lt;br /&gt;
These examples include param/return value descriptions and function implementations. See the create_users function for difference between Mandatory/Optional/Default. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class moodle_group_external extends external_api { //see following chapter &#039;function implementation&#039; to have a look to the external_api class&lt;br /&gt;
    public static function add_member_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;groupid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;some group id&#039;),&lt;br /&gt;
                &#039;userid&#039;  =&amp;gt; new external_value(PARAM_INT, &#039;some user id&#039;)&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_member($groupid, $userid) {&lt;br /&gt;
        $params = self::validate_parameters(self::add_member_parameters(), array(&#039;groupid&#039;=&amp;gt;$groupid, &#039;userid&#039;=&amp;gt;$userid));&lt;br /&gt;
&lt;br /&gt;
        // all the parameter/behavioural checks and security constrainsts go here,&lt;br /&gt;
        // throwing exceptions if neeeded and and calling low level (grouplib)&lt;br /&gt;
        // add_member() function that will be one in charge of the functionality without&lt;br /&gt;
        // further checks.&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_member_returns() {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    public static function add_members_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;membership&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    self::add_member_parameters()&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_members(array $membership) {&lt;br /&gt;
        $params = self::validate_parameters(self::add_members_parameters(), array(&#039;membership&#039;=&amp;gt;$membership));&lt;br /&gt;
        foreach($params[&#039;membership&#039;] as $one) { // simply one iterator over the &amp;quot;single&amp;quot; function if possible&lt;br /&gt;
            self::add_member($one-&amp;gt;groupid, $one-&amp;gt;userid);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_members_returns() {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    public static function get_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;groupid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;some group id&#039;)&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function get_groups(array $groups) {&lt;br /&gt;
        $params = self::validate_parameters(self::get_groups_parameters(), array(&#039;groups&#039;=&amp;gt;$groups));&lt;br /&gt;
&lt;br /&gt;
        // all the parameter/behavioural checks and security constrainsts go here,&lt;br /&gt;
        // throwing exceptions if needed and and calling low level (grouplib)&lt;br /&gt;
        // get_groups() function that will be one in charge of the functionality without&lt;br /&gt;
        // further checks.&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
    public static function get_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;some group id&#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;just some 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;
&lt;br /&gt;
class moodle_user_external extends external_api {&lt;br /&gt;
    public static function create_users_parameters() {&lt;br /&gt;
        new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;users&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    new external_single_structure(&lt;br /&gt;
                        array(&lt;br /&gt;
                            &#039;username&#039;    =&amp;gt; new external_value(PARAM_RAW, &#039;Username policy is defined in Moodle security config&#039;),&lt;br /&gt;
                            &#039;password&#039;    =&amp;gt; new external_value(PARAM_RAW, &#039;Plain text password consisting of any characters&#039;),&lt;br /&gt;
                            &#039;firstname&#039;   =&amp;gt; new external_value(PARAM_NOTAGS, &#039;The first name(s) of the user&#039;),&lt;br /&gt;
                            &#039;lastname&#039;    =&amp;gt; new external_value(PARAM_NOTAGS, &#039;The family name of the user&#039;),&lt;br /&gt;
                            &#039;email&#039;       =&amp;gt; new external_value(PARAM_EMAIL, &#039;A valid and unique email address&#039;),&lt;br /&gt;
                            &#039;auth&#039;        =&amp;gt; new external_value(PARAM_SAFEDIR, &#039;Auth plugins include manual, ldap,&lt;br /&gt;
                                                                imap, etc&#039;, VALUE_DEFAULT,&#039;manual&#039;, false),&lt;br /&gt;
                            &#039;idnumber&#039;    =&amp;gt; new external_value(PARAM_RAW, &#039;An arbitrary ID code number perhaps from the institution&#039;,&lt;br /&gt;
                                                                VALUE_DEFAULT, null),&lt;br /&gt;
                            &#039;emailstop&#039;   =&amp;gt; new external_value(PARAM_NUMBER, &#039;Email is blocked: 1 is blocked and 0 otherwise&#039;,&lt;br /&gt;
                                                                VALUE_DEFAULT, 0),&lt;br /&gt;
                            &#039;lang&#039;        =&amp;gt; new external_value(PARAM_SAFEDIR, &#039;Language code such as &amp;quot;en_utf8&amp;quot; must exist on server&#039;,&lt;br /&gt;
                                                                VALUE_DEFAULT, $CFG-&amp;gt;lang, false),&lt;br /&gt;
                            &#039;theme&#039;       =&amp;gt; new external_value(PARAM_SAFEDIR, &#039;Theme name such as &amp;quot;standard&amp;quot; must exist on server&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;timezone&#039;    =&amp;gt; new external_value(PARAM_ALPHANUMEXT, &#039;Timezone code such as Australia/Perth,or 99 for default&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;mailformat&#039;  =&amp;gt; new external_value(PARAM_INTEGER, &#039;Mail format code is 0 for plain text, 1 for HTML etc&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;description&#039; =&amp;gt; new external_value(PARAM_TEXT, &#039;User profile description, as HTML&#039;, VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;city&#039;        =&amp;gt; new external_value(PARAM_NOTAGS, &#039;Home city of the user&#039;, VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;country&#039;     =&amp;gt; new external_value(PARAM_ALPHA, &#039;Home country code of the user, such as AU or CZ&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;preferences&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                                new external_single_structure(&lt;br /&gt;
                                    array(&lt;br /&gt;
                                        &#039;type&#039;  =&amp;gt; new external_value(PARAM_ALPHANUMEXT, &#039;The name of the preference&#039;),&lt;br /&gt;
                                        &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;The value of the preference&#039;)&lt;br /&gt;
                                    )&lt;br /&gt;
                                ), &#039;User preferences&#039;, VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;customfields&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                                new external_single_structure(&lt;br /&gt;
                                    array(&lt;br /&gt;
                                        &#039;type&#039;  =&amp;gt; new external_value(PARAM_ALPHANUMEXT, &#039;The name of the custom field&#039;),&lt;br /&gt;
                                        &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;The value of the custom field&#039;)&lt;br /&gt;
                                    )&lt;br /&gt;
                                ), &#039;User custom fields&#039;, VALUE_OPTIONAL)&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function create_users(array $users) {&lt;br /&gt;
        $params = self::validate_parameters(self::create_users_parameters(), array(&#039;users&#039;=&amp;gt;$users));&lt;br /&gt;
&lt;br /&gt;
        foreach ($params[&#039;users&#039;] as $user) {&lt;br /&gt;
            // all the parameter/behavioural checks and security constrainsts go here,&lt;br /&gt;
            // throwing exceptions if neeeded and and calling low level (userlib)&lt;br /&gt;
            // add_user() function that will be one in charge of the functionality without&lt;br /&gt;
            // further checks.&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    public static function create_users_returns() {&lt;br /&gt;
        return new external_multiple_structure(&lt;br /&gt;
            new external_value(&#039;userid&#039;, PARAM_INT, &#039;id of the created user&#039;)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the use of Moodle-oriented PARAM_XXXX constants to define the format of each field.  These are used by the validation function &#039;&#039;validate_parameters()&#039;&#039; to verify the data and throw an exception and abort the operation completely if the data changed during cleaning (ie it was malformed).&lt;br /&gt;
&lt;br /&gt;
====Params====&lt;br /&gt;
&lt;br /&gt;
We use the clean_param function to check data.  We have added in &#039;&#039;moodlelib.php&#039;&#039; some new checks for common Moodle data so that we get better cleaning.  eg &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
        case PARAM_AUTH:&lt;br /&gt;
            $param = clean_param($param, PARAM_SAFEDIR);&lt;br /&gt;
            if (exists_auth_plugin($param)) {&lt;br /&gt;
                return $param;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        case PARAM_LANG:&lt;br /&gt;
            $param = clean_param($param, PARAM_SAFEDIR);&lt;br /&gt;
            $langs = get_list_of_languages(false, true);&lt;br /&gt;
            if (in_array($param, $langs)) {&lt;br /&gt;
                return $param;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &#039;&#039;;  // Specified language is not installed&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        case PARAM_THEME:&lt;br /&gt;
            $param = clean_param($param, PARAM_SAFEDIR);&lt;br /&gt;
            if (file_exists($CFG-&amp;gt;dirroot.&#039;/theme/&#039;.$param)) {&lt;br /&gt;
                return $param;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &#039;&#039;;  // Specified theme is not installed&lt;br /&gt;
            }&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Parameters types=====&lt;br /&gt;
&lt;br /&gt;
Note, this list will almost certainly be out of date by the time you read it. Do yourself a favour, and look at the list in lib/moodlelib.php. That is guaranteed to be up-to-date.&lt;br /&gt;
&lt;br /&gt;
* &#039;PARAM_ALPHA&#039;,    &#039;alpha&#039;&lt;br /&gt;
* &#039;PARAM_ALPHAEXT&#039;, &#039;alphaext&#039;&lt;br /&gt;
* &#039;PARAM_ALPHANUM&#039;, &#039;alphanum&#039;&lt;br /&gt;
* &#039;PARAM_ALPHANUMEXT&#039;, &#039;alphanumext&#039;&lt;br /&gt;
* &#039;PARAM_AUTH&#039;,  &#039;auth&#039;&lt;br /&gt;
* &#039;PARAM_BASE64&#039;,   &#039;base64&#039;&lt;br /&gt;
* &#039;PARAM_BOOL&#039;,     &#039;bool&#039;&lt;br /&gt;
* &#039;PARAM_CAPABILITY&#039;,   &#039;capability&#039;&lt;br /&gt;
* &#039;PARAM_CLEANHTML&#039;, &#039;cleanhtml&#039;&lt;br /&gt;
* &#039;PARAM_EMAIL&#039;,   &#039;email&#039;&lt;br /&gt;
* &#039;PARAM_FILE&#039;,   &#039;file&#039;&lt;br /&gt;
* &#039;PARAM_FLOAT&#039;,  &#039;float&#039;&lt;br /&gt;
* &#039;PARAM_HOST&#039;,     &#039;host&#039;&lt;br /&gt;
* &#039;PARAM_INT&#039;,      &#039;int&#039;&lt;br /&gt;
* &#039;PARAM_LANG&#039;,  &#039;lang&#039;&lt;br /&gt;
* &#039;PARAM_LOCALURL&#039;, &#039;localurl&#039;&lt;br /&gt;
* &#039;PARAM_NOTAGS&#039;,   &#039;notags&#039;&lt;br /&gt;
* &#039;PARAM_PATH&#039;,     &#039;path&#039;&lt;br /&gt;
* &#039;PARAM_PEM&#039;,      &#039;pem&#039;&lt;br /&gt;
* &#039;PARAM_PERMISSION&#039;,   &#039;permission&#039;&lt;br /&gt;
* &#039;PARAM_RAW&#039;, &#039;raw&#039;&lt;br /&gt;
* &#039;PARAM_RAW_TRIMMED&#039;, &#039;raw_trimmed&#039;&lt;br /&gt;
* &#039;PARAM_SAFEDIR&#039;,  &#039;safedir&#039;&lt;br /&gt;
* &#039;PARAM_SAFEPATH&#039;,  &#039;safepath&#039;&lt;br /&gt;
* &#039;PARAM_SEQUENCE&#039;,  &#039;sequence&#039;&lt;br /&gt;
* &#039;PARAM_TAG&#039;,   &#039;tag&#039;&lt;br /&gt;
* &#039;PARAM_TAGLIST&#039;,   &#039;taglist&#039;&lt;br /&gt;
* &#039;PARAM_TEXT&#039;,  &#039;text&#039;&lt;br /&gt;
* &#039;PARAM_THEME&#039;,  &#039;theme&#039;&lt;br /&gt;
* &#039;PARAM_URL&#039;,      &#039;url&#039;&lt;br /&gt;
* &#039;PARAM_USERNAME&#039;,    &#039;username&#039;&lt;br /&gt;
* &#039;PARAM_STRINGID&#039;,    &#039;stringid&#039;&lt;br /&gt;
* &#039;PARAM_CLEAN&#039;,    &#039;clean&#039;&lt;br /&gt;
* &#039;PARAM_INTEGER&#039;,  &#039;int&#039;&lt;br /&gt;
* &#039;PARAM_NUMBER&#039;,  &#039;float&#039;&lt;br /&gt;
* &#039;PARAM_ACTION&#039;,   &#039;alphanumext&#039;&lt;br /&gt;
* &#039;PARAM_FORMAT&#039;,   &#039;alphanumext&#039;&lt;br /&gt;
* &#039;PARAM_MULTILANG&#039;,  &#039;text&#039;&lt;br /&gt;
* &#039;PARAM_TIMEZONE&#039;, &#039;timezone&#039;&lt;br /&gt;
* &#039;PARAM_CLEANFILE&#039;, &#039;file&#039;&lt;br /&gt;
* &#039;PARAM_COMPONENT&#039;, &#039;component&#039;&lt;br /&gt;
* &#039;PARAM_AREA&#039;, &#039;area&#039;&lt;br /&gt;
* &#039;PARAM_PLUGIN&#039;, &#039;plugin&#039;&lt;br /&gt;
&lt;br /&gt;
=== Function implementation ===&lt;br /&gt;
&lt;br /&gt;
As you probably understood when you read the previous chapter examples, the functions are generally stored in externallib.php files, as methods in a class that extends &#039;&#039;&#039;&#039;external_api&#039;&#039;&#039;&#039;.  The actual file location is referenced by &#039;&#039;&#039;&#039;classpath&#039;&#039;&#039;&#039; in the services.php description.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class moodle_user_external extends external_api {&lt;br /&gt;
    public static function create_users($users)&lt;br /&gt;
    $params = self::validate_parameters(self::create_users_parameters(), array(&#039;users&#039;=&amp;gt;$users)); //ws object are associative array, ws list are non associative array&lt;br /&gt;
 &lt;br /&gt;
        foreach ($params[&#039;users&#039;] as $user) {&lt;br /&gt;
            // all the parameter/behavioural checks and security constraints go here,&lt;br /&gt;
            // throwing exceptions if needed and and calling low level (userlib)&lt;br /&gt;
            // add_user() function that will be one in charge of the functionality without&lt;br /&gt;
            // further checks.&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: there is more examples in the previous chapter&lt;br /&gt;
&lt;br /&gt;
external_api file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Base class for external api methods.&lt;br /&gt;
 */&lt;br /&gt;
class external_api {&lt;br /&gt;
&lt;br /&gt;
     ...&lt;br /&gt;
&lt;br /&gt;
     /**&lt;br /&gt;
     * Validates submitted function parameters, if anything is incorrect&lt;br /&gt;
     * invalid_parameter_exception is thrown.&lt;br /&gt;
     * This is a simple recursive method which is intended to be called from&lt;br /&gt;
     * each implementation method of external API.&lt;br /&gt;
     * @param external_description $description description of parameters&lt;br /&gt;
     * @param mixed $params the actual parameters&lt;br /&gt;
     * @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found&lt;br /&gt;
     */&lt;br /&gt;
    public static function validate_parameters(external_description $description, $params) {&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;
=== Way to fill the database tables ===&lt;br /&gt;
The Moodle upgrade takes care of the updates. We added two functions &#039;&#039;lib/upgradelib.php/external_update_description()&#039;&#039; and &#039;&#039;lib/upgradelib.php/external_delete_description()&#039;&#039; that update all web service descriptions into the database. &#039;&#039;lib/upgradelib.php/external_update_description()&#039;&#039; is also called during Moodle installation process.&lt;br /&gt;
&lt;br /&gt;
=== Return values are filtered by the servers ===&lt;br /&gt;
Web service functions should be able to return whatever they want. Each server should be looking at the web services description and returns the expected values.&lt;br /&gt;
&lt;br /&gt;
Some bulk requests may terminate unexpectedly in the middle of processing elements, these partial failures should be indicated by special exceptions classes which include list of completed elements and original exception. &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;TODO: special exceptions and the way to manage them need to be detailed&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=See also=&lt;br /&gt;
* [[Web services]]&lt;br /&gt;
* [[External services security]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web Services]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=External_services_description&amp;diff=57329</id>
		<title>External services description</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=External_services_description&amp;diff=57329"/>
		<updated>2020-04-21T11:53:53Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* services.php */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
This document explains how we describe and where we store the external services and functions in Moodle 2.0.&lt;br /&gt;
&lt;br /&gt;
===Service discovery===&lt;br /&gt;
We store the service descriptions and a part of the function descriptions in the component/db/services.php file. Function implementations are located in externallib.php files - this file name is not mandatory, but it is strongly recommended. externallib.php files also contain the other part of the function descriptions (the function parameters descriptions).&lt;br /&gt;
&lt;br /&gt;
During the upgrades the descriptions are parsed by a service discovery function that fills the database tables. Administrative UI may be used to change some configuration details. Services can also be defined via the admin UI, but it is recommended to use the new local plugin (look at the readme.txt into the Moodle local folder) when adding custom new services.&lt;br /&gt;
&lt;br /&gt;
===Administration pages===&lt;br /&gt;
Moodle administrators will be able to manage services. By default all services will be disabled.&lt;br /&gt;
&lt;br /&gt;
List of administration functionalities:&lt;br /&gt;
* enable a service (all the functions into this service will be available)&lt;br /&gt;
* associate a web service user (the user has web service capability) to some services =&amp;gt; a user can only call a service that he is associated with&lt;br /&gt;
* create a custom service (name the service + add and remove existing external functions from this service)&lt;br /&gt;
* search easily function by component in order to create a custom service&lt;br /&gt;
&lt;br /&gt;
===external_functions table===&lt;br /&gt;
This table lists the web service functions. It makes sense to call it external_functions as 1 web service function &amp;lt;=&amp;gt; 1 external function. This table maps the web service function name to the actual implementation of that function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
| varchar(200)&lt;br /&gt;
| &lt;br /&gt;
| the web service name (e.g. the unique name of the external function in the web service API) - a unique identifier for each function; ex.: core_get_users, mod_assignment_submit&lt;br /&gt;
|-&lt;br /&gt;
| classname&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| &lt;br /&gt;
| name of the class that contains method implementation; ex.: core_user_external, mod_assignment_external&lt;br /&gt;
|-&lt;br /&gt;
| methodname&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| &lt;br /&gt;
| static method; ex.: get_users, submit&lt;br /&gt;
|-&lt;br /&gt;
| classpath&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| NULL&lt;br /&gt;
| optional path to file with class definition - recommended for core classes only, null means use component/externallib.php; this path is relative to dirroot; ex.: user/externallib.php&lt;br /&gt;
|-&lt;br /&gt;
| component&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| &lt;br /&gt;
| Component where function defined, needed for automatic updates. Service description file is found in the db folder of this component.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;description&amp;lt;/font&amp;gt;&lt;br /&gt;
| &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;text&amp;lt;/font&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
| &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;A short human readable description of the function. It will be used to generate documentation and help the administrator to search a web service function. (NB: decide later if it&#039;s really improving the performance compare to add an access to the phpfile)&amp;lt;/font&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Detailed function description is stored as a complex PHP structure in the implementation class using suffix _parameters and _returns.&lt;br /&gt;
&lt;br /&gt;
===external_services table===&lt;br /&gt;
A &#039;&#039;service&#039;&#039; is defined as a group of external functions. The main purpose of these &#039;&#039;services&#039;&#039; is to allow defining of granular access control for multiple external systems.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;name&#039;&#039;&#039; &lt;br /&gt;
| varchar(150)&lt;br /&gt;
| &lt;br /&gt;
| Name of service (gradeexport_xml_export, mod_chat_export) - appears on the admin page. This name is not human readable when displayed into administration. We will use lang file to translate them automatically if a lang string exist.&amp;lt;/font&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| enabled&lt;br /&gt;
| int(1)&lt;br /&gt;
| 0&lt;br /&gt;
| service enabled, for security reasons some services may be disabled- administrators may disable any service via the admin UI&lt;br /&gt;
|-&lt;br /&gt;
| requiredcapability&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| NULL&lt;br /&gt;
| if capability name specified, user needs to have this permission in security context or in system context if context restriction not specified&lt;br /&gt;
|-&lt;br /&gt;
| restrictedusers&lt;br /&gt;
| int(1)&lt;br /&gt;
| 1&lt;br /&gt;
| 1 means on users explicitly listed in external_services_users may access this service, 0 means any user may use this service&lt;br /&gt;
|-&lt;br /&gt;
| component&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| NULL&lt;br /&gt;
| Component where service defined - null means custom service defined in admin UI.&lt;br /&gt;
|-&lt;br /&gt;
| timecreated&lt;br /&gt;
| bigint(10)&lt;br /&gt;
| &lt;br /&gt;
| The time the service was enabled or defined?&lt;br /&gt;
|-&lt;br /&gt;
| timemodified&lt;br /&gt;
| bigint(10)&lt;br /&gt;
| &lt;br /&gt;
| The last time the service was updated&lt;br /&gt;
|- &lt;br /&gt;
| shortname&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| &lt;br /&gt;
| The short name of the service&lt;br /&gt;
|- &lt;br /&gt;
| downloadfiles&lt;br /&gt;
| tinyint(1)&lt;br /&gt;
| &lt;br /&gt;
| A flag for something to do with download and files?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===external_services_functions table===&lt;br /&gt;
Lists all functions that are available in each service.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;externalserviceid&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| foreign key, reference external_services.id&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;functionname&#039;&#039;&#039; &lt;br /&gt;
| varchar(200)&lt;br /&gt;
| &lt;br /&gt;
| unique name of external function: references external_functions.name; the string value here simplifies service discovery and maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Function description ===&lt;br /&gt;
&lt;br /&gt;
We need to describe each function in detail, including input and output, so that we can:&lt;br /&gt;
&lt;br /&gt;
* help programmers quickly understand what they have to send and what to expect&lt;br /&gt;
* validate all incoming data as automatically as possible for security/correctness&lt;br /&gt;
* generate WSDL files for SOAP&lt;br /&gt;
* generate documentation for other protocols&lt;br /&gt;
&lt;br /&gt;
There are three main parts working together to do this:&lt;br /&gt;
&lt;br /&gt;
====services.php====&lt;br /&gt;
&lt;br /&gt;
In the db/services.php of each component, is a structure something like this to describe the web service functions, and optionally, any larger services built up of several functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$functions = array(&lt;br /&gt;
    &#039;moodle_user_create_users&#039; =&amp;gt; array(           //web service name (unique in all Moodle)&lt;br /&gt;
        &#039;classname&#039;   =&amp;gt; &#039;moodle_user_external&#039;, //class containing the function implementation&lt;br /&gt;
        &#039;methodname&#039;  =&amp;gt; &#039;create_users&#039;,              //name of the function into the class&lt;br /&gt;
        &#039;classpath&#039;   =&amp;gt; &#039;user/externallib.php&#039;,     //file containing the class (only used for core external function, not needed if your file is &#039;component/externallib.php&#039;),&lt;br /&gt;
        &#039;description&#039; =&amp;gt; &#039;create some users&#039;,&lt;br /&gt;
        &#039;type&#039; =&amp;gt; &#039;write&#039;,&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;
&lt;br /&gt;
$services = array( &lt;br /&gt;
    &#039;servicename&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;functions&#039; =&amp;gt; array (&#039;functionname&#039;, &#039;secondfunctionname&#039;), //web service function name&lt;br /&gt;
        &#039;requiredcapability&#039; =&amp;gt; &#039;some/capability:specified&#039;,                  &lt;br /&gt;
        &#039;restrictedusers&#039; =&amp;gt; 1,&lt;br /&gt;
        &#039;enabled&#039;=&amp;gt;0, //used only when installing the services&lt;br /&gt;
    )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function name is arbitrary, but it must be globally unique so we highly recommend using the component name as prefix (and &amp;quot;moodle&amp;quot; for core functions).&lt;br /&gt;
&lt;br /&gt;
The actual param description and description of returned values can be obtained from the same class by static method calls by adding &#039;_parameters&#039; and &#039;_returns&#039; to the methodname value.&lt;br /&gt;
&lt;br /&gt;
====externallib.php====&lt;br /&gt;
&lt;br /&gt;
It&#039;s the file referenced as the location for the web service function implementation. It also contains complete descriptions of the required parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Base description classes:&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
abstract class external_description {&lt;br /&gt;
    public $desc; //human readable description&lt;br /&gt;
    public $required;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($desc, $required) {&lt;br /&gt;
        $this-&amp;gt;desc = $desc;&lt;br /&gt;
        $this-&amp;gt;required = $required;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_value extends external_description {&lt;br /&gt;
    public $type;&lt;br /&gt;
    public $default;&lt;br /&gt;
    public $allownull;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($type, $desc=&#039;&#039;, $required=VALUE_REQUIRED, $default=null, $allownull=true) {&lt;br /&gt;
        parent::_construct($desc, $required);&lt;br /&gt;
        $this-&amp;gt;type      = $type;&lt;br /&gt;
        $this-&amp;gt;default   = $default;&lt;br /&gt;
        $this-&amp;gt;allownull = $allownull;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_single_structure extends external_description {&lt;br /&gt;
    public $keys; //an associative array of external_description&lt;br /&gt;
&lt;br /&gt;
    public function __construct(array $keys, $desc=&#039;&#039;, $required=VALUE_REQUIRED) {&lt;br /&gt;
        parent::_construct($desc, $required);&lt;br /&gt;
        $this-&amp;gt;keys = $keys; //key=&amp;gt;external_description&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_multiple_structure extends external_description {&lt;br /&gt;
    public $content; //the content type of the list =&amp;gt; external_description&lt;br /&gt;
&lt;br /&gt;
    public function __construct(external_description $content, $desc=&#039;&#039;, $required=VALUE_REQUIRED) {&lt;br /&gt;
        parent::_construct($desc, $required);&lt;br /&gt;
        $this-&amp;gt;content = $content;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class external_function_parameters extends external_single_structure {&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Externallib.php examples:&#039;&#039;&amp;lt;br/&amp;gt;&lt;br /&gt;
These examples include param/return value descriptions and function implementations. See the create_users function for difference between Mandatory/Optional/Default. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class moodle_group_external extends external_api { //see following chapter &#039;function implementation&#039; to have a look to the external_api class&lt;br /&gt;
    public static function add_member_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;groupid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;some group id&#039;),&lt;br /&gt;
                &#039;userid&#039;  =&amp;gt; new external_value(PARAM_INT, &#039;some user id&#039;)&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_member($groupid, $userid) {&lt;br /&gt;
        $params = self::validate_parameters(self::add_member_parameters(), array(&#039;groupid&#039;=&amp;gt;$groupid, &#039;userid&#039;=&amp;gt;$userid));&lt;br /&gt;
&lt;br /&gt;
        // all the parameter/behavioural checks and security constrainsts go here,&lt;br /&gt;
        // throwing exceptions if neeeded and and calling low level (grouplib)&lt;br /&gt;
        // add_member() function that will be one in charge of the functionality without&lt;br /&gt;
        // further checks.&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_member_returns() {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    public static function add_members_parameters() {&lt;br /&gt;
        return new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;membership&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    self::add_member_parameters()&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_members(array $membership) {&lt;br /&gt;
        $params = self::validate_parameters(self::add_members_parameters(), array(&#039;membership&#039;=&amp;gt;$membership));&lt;br /&gt;
        foreach($params[&#039;membership&#039;] as $one) { // simply one iterator over the &amp;quot;single&amp;quot; function if possible&lt;br /&gt;
            self::add_member($one-&amp;gt;groupid, $one-&amp;gt;userid);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    public static function add_members_returns() {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    public static function get_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;groupid&#039; =&amp;gt; new external_value(PARAM_INT, &#039;some group id&#039;)&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function get_groups(array $groups) {&lt;br /&gt;
        $params = self::validate_parameters(self::get_groups_parameters(), array(&#039;groups&#039;=&amp;gt;$groups));&lt;br /&gt;
&lt;br /&gt;
        // all the parameter/behavioural checks and security constrainsts go here,&lt;br /&gt;
        // throwing exceptions if needed and and calling low level (grouplib)&lt;br /&gt;
        // get_groups() function that will be one in charge of the functionality without&lt;br /&gt;
        // further checks.&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
    public static function get_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;some group id&#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;just some 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;
&lt;br /&gt;
class moodle_user_external extends external_api {&lt;br /&gt;
    public static function create_users_parameters() {&lt;br /&gt;
        new external_function_parameters(&lt;br /&gt;
            array(&lt;br /&gt;
                &#039;users&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                    new external_single_structure(&lt;br /&gt;
                        array(&lt;br /&gt;
                            &#039;username&#039;    =&amp;gt; new external_value(PARAM_RAW, &#039;Username policy is defined in Moodle security config&#039;),&lt;br /&gt;
                            &#039;password&#039;    =&amp;gt; new external_value(PARAM_RAW, &#039;Plain text password consisting of any characters&#039;),&lt;br /&gt;
                            &#039;firstname&#039;   =&amp;gt; new external_value(PARAM_NOTAGS, &#039;The first name(s) of the user&#039;),&lt;br /&gt;
                            &#039;lastname&#039;    =&amp;gt; new external_value(PARAM_NOTAGS, &#039;The family name of the user&#039;),&lt;br /&gt;
                            &#039;email&#039;       =&amp;gt; new external_value(PARAM_EMAIL, &#039;A valid and unique email address&#039;),&lt;br /&gt;
                            &#039;auth&#039;        =&amp;gt; new external_value(PARAM_SAFEDIR, &#039;Auth plugins include manual, ldap,&lt;br /&gt;
                                                                imap, etc&#039;, VALUE_DEFAULT,&#039;manual&#039;, false),&lt;br /&gt;
                            &#039;idnumber&#039;    =&amp;gt; new external_value(PARAM_RAW, &#039;An arbitrary ID code number perhaps from the institution&#039;,&lt;br /&gt;
                                                                VALUE_DEFAULT, null),&lt;br /&gt;
                            &#039;emailstop&#039;   =&amp;gt; new external_value(PARAM_NUMBER, &#039;Email is blocked: 1 is blocked and 0 otherwise&#039;,&lt;br /&gt;
                                                                VALUE_DEFAULT, 0),&lt;br /&gt;
                            &#039;lang&#039;        =&amp;gt; new external_value(PARAM_SAFEDIR, &#039;Language code such as &amp;quot;en_utf8&amp;quot; must exist on server&#039;,&lt;br /&gt;
                                                                VALUE_DEFAULT, $CFG-&amp;gt;lang, false),&lt;br /&gt;
                            &#039;theme&#039;       =&amp;gt; new external_value(PARAM_SAFEDIR, &#039;Theme name such as &amp;quot;standard&amp;quot; must exist on server&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;timezone&#039;    =&amp;gt; new external_value(PARAM_ALPHANUMEXT, &#039;Timezone code such as Australia/Perth,or 99 for default&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;mailformat&#039;  =&amp;gt; new external_value(PARAM_INTEGER, &#039;Mail format code is 0 for plain text, 1 for HTML etc&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;description&#039; =&amp;gt; new external_value(PARAM_TEXT, &#039;User profile description, as HTML&#039;, VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;city&#039;        =&amp;gt; new external_value(PARAM_NOTAGS, &#039;Home city of the user&#039;, VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;country&#039;     =&amp;gt; new external_value(PARAM_ALPHA, &#039;Home country code of the user, such as AU or CZ&#039;,&lt;br /&gt;
                                                                VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;preferences&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                                new external_single_structure(&lt;br /&gt;
                                    array(&lt;br /&gt;
                                        &#039;type&#039;  =&amp;gt; new external_value(PARAM_ALPHANUMEXT, &#039;The name of the preference&#039;),&lt;br /&gt;
                                        &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;The value of the preference&#039;)&lt;br /&gt;
                                    )&lt;br /&gt;
                                ), &#039;User preferences&#039;, VALUE_OPTIONAL),&lt;br /&gt;
                            &#039;customfields&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
                                new external_single_structure(&lt;br /&gt;
                                    array(&lt;br /&gt;
                                        &#039;type&#039;  =&amp;gt; new external_value(PARAM_ALPHANUMEXT, &#039;The name of the custom field&#039;),&lt;br /&gt;
                                        &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;The value of the custom field&#039;)&lt;br /&gt;
                                    )&lt;br /&gt;
                                ), &#039;User custom fields&#039;, VALUE_OPTIONAL)&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
    public static function create_users(array $users) {&lt;br /&gt;
        $params = self::validate_parameters(self::create_users_parameters(), array(&#039;users&#039;=&amp;gt;$users));&lt;br /&gt;
&lt;br /&gt;
        foreach ($params[&#039;users&#039;] as $user) {&lt;br /&gt;
            // all the parameter/behavioural checks and security constrainsts go here,&lt;br /&gt;
            // throwing exceptions if neeeded and and calling low level (userlib)&lt;br /&gt;
            // add_user() function that will be one in charge of the functionality without&lt;br /&gt;
            // further checks.&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    public static function create_users_returns() {&lt;br /&gt;
        return new external_multiple_structure(&lt;br /&gt;
            new external_value(&#039;userid&#039;, PARAM_INT, &#039;id of the created user&#039;)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the use of Moodle-oriented PARAM_XXXX constants to define the format of each field.  These are used by the validation function &#039;&#039;validate_parameters()&#039;&#039; to verify the data and throw an exception and abort the operation completely if the data changed during cleaning (ie it was malformed).&lt;br /&gt;
&lt;br /&gt;
====Params====&lt;br /&gt;
&lt;br /&gt;
We use the clean_param function to check data.  We have added in &#039;&#039;moodlelib.php&#039;&#039; some new checks for common Moodle data so that we get better cleaning.  eg &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
        case PARAM_AUTH:&lt;br /&gt;
            $param = clean_param($param, PARAM_SAFEDIR);&lt;br /&gt;
            if (exists_auth_plugin($param)) {&lt;br /&gt;
                return $param;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        case PARAM_LANG:&lt;br /&gt;
            $param = clean_param($param, PARAM_SAFEDIR);&lt;br /&gt;
            $langs = get_list_of_languages(false, true);&lt;br /&gt;
            if (in_array($param, $langs)) {&lt;br /&gt;
                return $param;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &#039;&#039;;  // Specified language is not installed&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        case PARAM_THEME:&lt;br /&gt;
            $param = clean_param($param, PARAM_SAFEDIR);&lt;br /&gt;
            if (file_exists($CFG-&amp;gt;dirroot.&#039;/theme/&#039;.$param)) {&lt;br /&gt;
                return $param;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &#039;&#039;;  // Specified theme is not installed&lt;br /&gt;
            }&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Parameters types=====&lt;br /&gt;
&lt;br /&gt;
Note, this list will almost certainly be out of date by the time you read it. Do yourself a favour, and look at the list in lib/moodlelib.php. That is guaranteed to be up-to-date.&lt;br /&gt;
&lt;br /&gt;
* &#039;PARAM_ALPHA&#039;,    &#039;alpha&#039;&lt;br /&gt;
* &#039;PARAM_ALPHAEXT&#039;, &#039;alphaext&#039;&lt;br /&gt;
* &#039;PARAM_ALPHANUM&#039;, &#039;alphanum&#039;&lt;br /&gt;
* &#039;PARAM_ALPHANUMEXT&#039;, &#039;alphanumext&#039;&lt;br /&gt;
* &#039;PARAM_AUTH&#039;,  &#039;auth&#039;&lt;br /&gt;
* &#039;PARAM_BASE64&#039;,   &#039;base64&#039;&lt;br /&gt;
* &#039;PARAM_BOOL&#039;,     &#039;bool&#039;&lt;br /&gt;
* &#039;PARAM_CAPABILITY&#039;,   &#039;capability&#039;&lt;br /&gt;
* &#039;PARAM_CLEANHTML&#039;, &#039;cleanhtml&#039;&lt;br /&gt;
* &#039;PARAM_EMAIL&#039;,   &#039;email&#039;&lt;br /&gt;
* &#039;PARAM_FILE&#039;,   &#039;file&#039;&lt;br /&gt;
* &#039;PARAM_FLOAT&#039;,  &#039;float&#039;&lt;br /&gt;
* &#039;PARAM_HOST&#039;,     &#039;host&#039;&lt;br /&gt;
* &#039;PARAM_INT&#039;,      &#039;int&#039;&lt;br /&gt;
* &#039;PARAM_LANG&#039;,  &#039;lang&#039;&lt;br /&gt;
* &#039;PARAM_LOCALURL&#039;, &#039;localurl&#039;&lt;br /&gt;
* &#039;PARAM_NOTAGS&#039;,   &#039;notags&#039;&lt;br /&gt;
* &#039;PARAM_PATH&#039;,     &#039;path&#039;&lt;br /&gt;
* &#039;PARAM_PEM&#039;,      &#039;pem&#039;&lt;br /&gt;
* &#039;PARAM_PERMISSION&#039;,   &#039;permission&#039;&lt;br /&gt;
* &#039;PARAM_RAW&#039;, &#039;raw&#039;&lt;br /&gt;
* &#039;PARAM_RAW_TRIMMED&#039;, &#039;raw_trimmed&#039;&lt;br /&gt;
* &#039;PARAM_SAFEDIR&#039;,  &#039;safedir&#039;&lt;br /&gt;
* &#039;PARAM_SAFEPATH&#039;,  &#039;safepath&#039;&lt;br /&gt;
* &#039;PARAM_SEQUENCE&#039;,  &#039;sequence&#039;&lt;br /&gt;
* &#039;PARAM_TAG&#039;,   &#039;tag&#039;&lt;br /&gt;
* &#039;PARAM_TAGLIST&#039;,   &#039;taglist&#039;&lt;br /&gt;
* &#039;PARAM_TEXT&#039;,  &#039;text&#039;&lt;br /&gt;
* &#039;PARAM_THEME&#039;,  &#039;theme&#039;&lt;br /&gt;
* &#039;PARAM_URL&#039;,      &#039;url&#039;&lt;br /&gt;
* &#039;PARAM_USERNAME&#039;,    &#039;username&#039;&lt;br /&gt;
* &#039;PARAM_STRINGID&#039;,    &#039;stringid&#039;&lt;br /&gt;
* &#039;PARAM_CLEAN&#039;,    &#039;clean&#039;&lt;br /&gt;
* &#039;PARAM_INTEGER&#039;,  &#039;int&#039;&lt;br /&gt;
* &#039;PARAM_NUMBER&#039;,  &#039;float&#039;&lt;br /&gt;
* &#039;PARAM_ACTION&#039;,   &#039;alphanumext&#039;&lt;br /&gt;
* &#039;PARAM_FORMAT&#039;,   &#039;alphanumext&#039;&lt;br /&gt;
* &#039;PARAM_MULTILANG&#039;,  &#039;text&#039;&lt;br /&gt;
* &#039;PARAM_TIMEZONE&#039;, &#039;timezone&#039;&lt;br /&gt;
* &#039;PARAM_CLEANFILE&#039;, &#039;file&#039;&lt;br /&gt;
* &#039;PARAM_COMPONENT&#039;, &#039;component&#039;&lt;br /&gt;
* &#039;PARAM_AREA&#039;, &#039;area&#039;&lt;br /&gt;
* &#039;PARAM_PLUGIN&#039;, &#039;plugin&#039;&lt;br /&gt;
&lt;br /&gt;
=== Function implementation ===&lt;br /&gt;
&lt;br /&gt;
As you probably understood when you read the previous chapter examples, the functions are generally stored in externallib.php files, as methods in a class that extends &#039;&#039;&#039;&#039;external_api&#039;&#039;&#039;&#039;.  The actual file location is referenced by &#039;&#039;&#039;&#039;classpath&#039;&#039;&#039;&#039; in the services.php description.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class moodle_user_external extends external_api {&lt;br /&gt;
    public static function create_users($users)&lt;br /&gt;
    $params = self::validate_parameters(self::create_users_parameters(), array(&#039;users&#039;=&amp;gt;$users)); //ws object are associative array, ws list are non associative array&lt;br /&gt;
 &lt;br /&gt;
        foreach ($params[&#039;users&#039;] as $user) {&lt;br /&gt;
            // all the parameter/behavioural checks and security constraints go here,&lt;br /&gt;
            // throwing exceptions if needed and and calling low level (userlib)&lt;br /&gt;
            // add_user() function that will be one in charge of the functionality without&lt;br /&gt;
            // further checks.&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: there is more examples in the previous chapter&lt;br /&gt;
&lt;br /&gt;
external_api file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Base class for external api methods.&lt;br /&gt;
 */&lt;br /&gt;
class external_api {&lt;br /&gt;
&lt;br /&gt;
     ...&lt;br /&gt;
&lt;br /&gt;
     /**&lt;br /&gt;
     * Validates submitted function parameters, if anything is incorrect&lt;br /&gt;
     * invalid_parameter_exception is thrown.&lt;br /&gt;
     * This is a simple recursive method which is intended to be called from&lt;br /&gt;
     * each implementation method of external API.&lt;br /&gt;
     * @param external_description $description description of parameters&lt;br /&gt;
     * @param mixed $params the actual parameters&lt;br /&gt;
     * @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found&lt;br /&gt;
     */&lt;br /&gt;
    public static function validate_parameters(external_description $description, $params) {&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;
=== Way to fill the database tables ===&lt;br /&gt;
The Moodle upgrade takes care of the updates. We added two functions &#039;&#039;lib/upgradelib.php/external_update_description()&#039;&#039; and &#039;&#039;lib/upgradelib.php/external_delete_description()&#039;&#039; that update all web service descriptions into the database. &#039;&#039;lib/upgradelib.php/external_update_description()&#039;&#039; is also called during Moodle installation process.&lt;br /&gt;
&lt;br /&gt;
=== Return values are filtered by the servers ===&lt;br /&gt;
Web service functions should be able to return whatever they want. Each server should be looking at the web services description and returns the expected values.&lt;br /&gt;
&lt;br /&gt;
Some bulk requests may terminate unexpectedly in the middle of processing elements, these partial failures should be indicated by special exceptions classes which include list of completed elements and original exception. &amp;lt;font color=&amp;quot;green&amp;quot;&amp;gt;TODO: special exceptions and the way to manage them need to be detailed&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=See also=&lt;br /&gt;
* [[Web services]]&lt;br /&gt;
* [[External services security]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web Services]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=57321</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=57321"/>
		<updated>2020-04-20T16:18:34Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Trouble shooting */&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;
First, you should have a look at [[Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium#Working_combinations_of_OS.2BBrowser.2Bselenium|working combinations of OS+Browser+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/]. Ensure you have the right version of the Chrome driver - see [[#Trouble_shooting| Trouble shooting]]. (Firefox is currently problematic. See [[Actual_Selenium_with_old_Firefox_47.0.1]] if you need to try to make it work.)&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;vendor/bin/behat&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. Use comma-separated list like &#039;&amp;lt;tt&amp;gt;@some_thing,@some_thing_else&amp;lt;/tt&amp;gt;&#039; to run tests from multiple areas. See upstream documentation on Gherkin filters for advanced syntax and more complex examples.&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=&amp;lt;number&amp;gt; or --parallel=&amp;lt;number&amp;gt;&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m=&amp;lt;number&amp;gt; or --maxruns=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;name&amp;gt; or --add-core-features-to-theme=&amp;lt;name&amp;gt;&#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;
&lt;br /&gt;
You almost always want to limit the number of tests that are run. To run all the tests in one plugin:&lt;br /&gt;
&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 --tags mod_myplugin&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run all the tests in one .feature file:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run a single scenario:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature:40&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;40&#039; is the line-number of the feature file where the Scenario starts.&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/run.php&lt;br /&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;
# 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;
Example: Initialise and run Behat tests for a custom plugin under a custom theme:&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme=&amp;quot;mytheme&amp;quot;&lt;br /&gt;
 php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;mytheme&amp;quot;&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; iframe&#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 --add-core-features-to-theme={THEME_NAME}, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php --add-core-features-to-theme=clean&lt;br /&gt;
vendor/bin/behat --suite=clean --tags=&amp;quot;@enrol_foobar&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is a core theme but it will work with custom theme. No = or quotes needed around the theme name.&lt;br /&gt;
&lt;br /&gt;
Make sure that &amp;lt;tt&amp;gt;$CFG-&amp;gt;theme&amp;lt;/tt&amp;gt; is &#039;&#039;&#039;not set&#039;&#039;&#039; in your config.php.&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;
=== Write new tests and behat methods ===&lt;br /&gt;
&lt;br /&gt;
If you want to write tests for your own integration, you can do so by creating new tests with format .feature. Follow instructions in [[Writing_acceptance_tests|this page]] to write new tests. &lt;br /&gt;
&lt;br /&gt;
It is also possible to add new steps the moodle behat integration. In order to do so, you will have to create a new .php class with the prefix &#039;&#039;&#039;behat_&#039;&#039;&#039;. Copy the format from &#039;&#039;&#039;lib\behat\behat_base.php&#039;&#039;&#039;, but set your class to extend the behat_base class instead of the MinkExtension. You can define new behat steps by declaring functions with the appropriate heading. &lt;br /&gt;
&lt;br /&gt;
You will not be  able to use these steps and features right away. Check [[Running_acceptance_test#New_step_definitions_or_features_are_not_executed|this section]] for instructions on how to update the behat integration. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For further information on how to create new steps definitions, check [[Acceptance testing/Custom acceptance steps]].&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
&lt;br /&gt;
If you follow the steps above, Behat will run with Chrome.&lt;br /&gt;
&lt;br /&gt;
You can get it to run with other browsers. The basic idea is to expand the $CFG-&amp;gt;behat_profiles array in config.php to list more browsers.&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;
&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;
Alternative way of running three Selenium servers in parallel:&lt;br /&gt;
&lt;br /&gt;
 $ printf %d\\n {4444..4446} | xargs -n 1 -P 3 java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port&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;
=== Increasing timeouts ===&lt;br /&gt;
&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
Javascript code and/or AJAX requests are not ready after 10 seconds. &lt;br /&gt;
There is a Javascript error or the code is extremely slow.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it could just mean that the browser was not yet ready. You may find that the test works if you run it again. If you get this error frequently, it might be useful to increase the timeout.&lt;br /&gt;
&lt;br /&gt;
It is possible to increase this timeout by adding a line in your config.php. (Requires Moodle versions 3.5 (from 3.5.6), 3.6 (from 3.6.4), or 3.7+.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_increasetimeout = 2;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will increase all the timeouts by a factor of 2; if that isn&#039;t sufficient, you could use 3. &lt;br /&gt;
&lt;br /&gt;
Increasing timeouts will make tests run a bit slower (because there are points where Behat waits up to a timeout to make sure something doesn&#039;t happen) so don&#039;t do this unless you need to.&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;
=== 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;
== Trouble shooting ==&lt;br /&gt;
&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;
&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your browser 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;
=== The tests are failing, and the error message is completely useless ===&lt;br /&gt;
&lt;br /&gt;
For example, it just says &amp;quot;Error writing to database&amp;quot; with no stack trace.&lt;br /&gt;
&lt;br /&gt;
Add -vv command-line option to get very verbose output.&lt;br /&gt;
&lt;br /&gt;
=== Errors during setup (before test are launched) ===&lt;br /&gt;
Typical errors are:&lt;br /&gt;
* Behat requirement not satisfied: http://127.0.0.1/m/stable_master is not available, ensure you specified correct url and that the server is set up and started.&lt;br /&gt;
* Behat is configured but not enabled on this test site.&lt;br /&gt;
&lt;br /&gt;
In order to fix those errors please check that: the behat_dataroot has correct write permissions and that the $CFG-&amp;gt;behat* variables are placed before the lib/setup.php include:&lt;br /&gt;
 require_once(__DIR__ . &#039;/lib/setup.php&#039;);&lt;br /&gt;
&lt;br /&gt;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&lt;br /&gt;
&lt;br /&gt;
If you are using chrome, you need to ensure that the driver matches the version of the installed chrome browser – which may change on OS updates/upgrades.  Moodle or Selenium will not give the appropriate message – see [https://tracker.moodle.org/browse/MDL-67659/ MDL-67659].  One solution is the one suggested in the issue and use Andrew Nicols’ [https://github.com/andrewnicols/chromedriver-wrapper/  Chromedriver Wrapper] which will ensure you have the appropriate driver before running the tests.&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;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=57312</id>
		<title>Cache API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=57312"/>
		<updated>2020-04-16T12:01:19Z</updated>

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

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

		<summary type="html">&lt;p&gt;Tim Hunt: /* 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;
First, you should have a look at [[Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium#Working_combinations_of_OS.2BBrowser.2Bselenium|working combinations of OS+Browser+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/]. Ensure you have the right version of the Chrome driver - see [[#Trouble_shooting| Trouble shooting]]. (Firefox is currently problematic. See [[Actual_Selenium_with_old_Firefox_47.0.1]] if you need to try to make it work.)&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;vendor/bin/behat&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. Use comma-separated list like &#039;&amp;lt;tt&amp;gt;@some_thing,@some_thing_else&amp;lt;/tt&amp;gt;&#039; to run tests from multiple areas. See upstream documentation on Gherkin filters for advanced syntax and more complex examples.&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=&amp;lt;number&amp;gt; or --parallel=&amp;lt;number&amp;gt;&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m=&amp;lt;number&amp;gt; or --maxruns=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;name&amp;gt; or --add-core-features-to-theme=&amp;lt;name&amp;gt;&#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;
&lt;br /&gt;
You almost always want to limit the number of tests that are run. To run all the tests in one plugin:&lt;br /&gt;
&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 --tags mod_myplugin&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run all the tests in one .feature file:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run a single scenario:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature:40&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;40&#039; is the line-number of the feature file where the Scenario starts.&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/run.php&lt;br /&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;
# 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;
Example: Initialise and run Behat tests for a custom plugin under a custom theme:&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme=&amp;quot;mytheme&amp;quot;&lt;br /&gt;
 php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;mytheme&amp;quot;&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; iframe&#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 --add-core-features-to-theme={THEME_NAME}, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php --add-core-features-to-theme=clean&lt;br /&gt;
vendor/bin/behat --suite=clean --tags=&amp;quot;@enrol_foobar&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is a core theme but it will work with custom theme. No = or quotes needed around the theme name.&lt;br /&gt;
&lt;br /&gt;
Make sure that &amp;lt;tt&amp;gt;$CFG-&amp;gt;theme&amp;lt;/tt&amp;gt; is &#039;&#039;&#039;not set&#039;&#039;&#039; in your config.php.&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;
=== Write new tests and behat methods ===&lt;br /&gt;
&lt;br /&gt;
If you want to write tests for your own integration, you can do so by creating new tests with format .feature. Follow instructions in [[Writing_acceptance_tests|this page]] to write new tests. &lt;br /&gt;
&lt;br /&gt;
It is also possible to add new steps the moodle behat integration. In order to do so, you will have to create a new .php class with the prefix &#039;&#039;&#039;behat_&#039;&#039;&#039;. Copy the format from &#039;&#039;&#039;lib\behat\behat_base.php&#039;&#039;&#039;, but set your class to extend the behat_base class instead of the MinkExtension. You can define new behat steps by declaring functions with the appropriate heading. &lt;br /&gt;
&lt;br /&gt;
You will not be  able to use these steps and features right away. Check [[Running_acceptance_test#New_step_definitions_or_features_are_not_executed|this section]] for instructions on how to update the behat integration. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For further information on how to create new steps definitions, check [[Acceptance testing/Custom acceptance steps]].&lt;br /&gt;
&lt;br /&gt;
=== Running acceptance tests with different browser ===&lt;br /&gt;
&lt;br /&gt;
If you follow the steps above, Behat will run with Chrome.&lt;br /&gt;
&lt;br /&gt;
You can get it to run with other browsers. The basic idea is to expand the $CFG-&amp;gt;behat_profiles array in config.php to list more browsers.&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;
&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;
Alternative way of running three Selenium servers in parallel:&lt;br /&gt;
&lt;br /&gt;
 $ printf %d\\n {4444..4446} | xargs -n 1 -P 3 java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port&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;
=== Increasing timeouts ===&lt;br /&gt;
&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
Javascript code and/or AJAX requests are not ready after 10 seconds. &lt;br /&gt;
There is a Javascript error or the code is extremely slow.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it could just mean that the browser was not yet ready. You may find that the test works if you run it again. If you get this error frequently, it might be useful to increase the timeout.&lt;br /&gt;
&lt;br /&gt;
It is possible to increase this timeout by adding a line in your config.php. (Requires Moodle versions 3.5 (from 3.5.6), 3.6 (from 3.6.4), or 3.7+.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_increasetimeout = 2;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will increase all the timeouts by a factor of 2; if that isn&#039;t sufficient, you could use 3. &lt;br /&gt;
&lt;br /&gt;
Increasing timeouts will make tests run a bit slower (because there are points where Behat waits up to a timeout to make sure something doesn&#039;t happen) so don&#039;t do this unless you need to.&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;
=== 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;
== Trouble shooting ==&lt;br /&gt;
&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;
&lt;br /&gt;
If you followed all the steps and you receive an unknown weird error probably your browser 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;
=== Errors during setup (before test are launched) ===&lt;br /&gt;
Typical errors are:&lt;br /&gt;
* Behat requirement not satisfied: http://127.0.0.1/m/stable_master is not available, ensure you specified correct url and that the server is set up and started.&lt;br /&gt;
* Behat is configured but not enabled on this test site.&lt;br /&gt;
&lt;br /&gt;
In order to fix those errors please check that: the behat_dataroot has correct write permissions and that the $CFG-&amp;gt;behat* variables are placed before the lib/setup.php include:&lt;br /&gt;
 require_once(__DIR__ . &#039;/lib/setup.php&#039;);&lt;br /&gt;
&lt;br /&gt;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&lt;br /&gt;
&lt;br /&gt;
If you are using chrome, you need to ensure that the driver matches the version of the installed chrome browser – which may change on OS updates/upgrades.  Moodle or Selenium will not give the appropriate message – see [https://tracker.moodle.org/browse/MDL-67659/ MDL-67659].  One solution is the one suggested in the issue and use Andrew Nicols’ [https://github.com/andrewnicols/chromedriver-wrapper/  Chromedriver Wrapper] which will ensure you have the appropriate driver before running the tests.&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;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=57258</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=57258"/>
		<updated>2020-04-07T12:19:30Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* 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;
First, you should have a look at [[Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium#Working_combinations_of_OS.2BBrowser.2Bselenium|working combinations of OS+Browser+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/]. Ensure you have the right version of the Chrome driver - see [[#Trouble_shooting| Trouble shooting]]. (Firefox is currently problematic. See [[Actual_Selenium_with_old_Firefox_47.0.1]] if you need to try to make it work.)&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;vendor/bin/behat&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. Use comma-separated list like &#039;&amp;lt;tt&amp;gt;@some_thing,@some_thing_else&amp;lt;/tt&amp;gt;&#039; to run tests from multiple areas. See upstream documentation on Gherkin filters for advanced syntax and more complex examples.&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=&amp;lt;number&amp;gt; or --parallel=&amp;lt;number&amp;gt;&#039;&#039;&#039; (required) Number of parallel behat run to initialise&lt;br /&gt;
# &#039;&#039;&#039;-m=&amp;lt;number&amp;gt; or --maxruns=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;number&amp;gt;&#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=&amp;lt;name&amp;gt; or --add-core-features-to-theme=&amp;lt;name&amp;gt;&#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;
&lt;br /&gt;
You almost always want to limit the number of tests that are run. To run all the tests in one plugin:&lt;br /&gt;
&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 --tags mod_myplugin&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run all the tests in one .feature file:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run a single scenario:&lt;br /&gt;
&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 /path/to/moodle/mod/myplugin/tests/behat/testsomething.feature:40&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here, &#039;40&#039; is the line-number of the feature file where the Scenario starts.&lt;br /&gt;
&lt;br /&gt;
=== Parallel runs ===&lt;br /&gt;
For running parallel runs, use following command&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/run.php&lt;br /&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;
# 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;
Example: Initialise and run Behat tests for a custom plugin under a custom theme:&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme=&amp;quot;mytheme&amp;quot;&lt;br /&gt;
 php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;mytheme&amp;quot;&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; iframe&#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 --add-core-features-to-theme={THEME_NAME}, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php --add-core-features-to-theme=clean&lt;br /&gt;
vendor/bin/behat --suite=clean --tags=&amp;quot;@enrol_foobar&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is a core theme but it will work with custom theme. No = or quotes needed around the theme name.&lt;br /&gt;
&lt;br /&gt;
Make sure that &amp;lt;tt&amp;gt;$CFG-&amp;gt;theme&amp;lt;/tt&amp;gt; is &#039;&#039;&#039;not set&#039;&#039;&#039; in your config.php.&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;
=== Write new tests and behat methods ===&lt;br /&gt;
&lt;br /&gt;
If you want to write tests for your own integration, you can do so by creating new tests with format .feature. Follow instructions in [[Writing_acceptance_tests|this page]] to write new tests. &lt;br /&gt;
&lt;br /&gt;
It is also possible to add new steps the moodle behat integration. In order to do so, you will have to create a new .php class with the prefix &#039;&#039;&#039;behat_&#039;&#039;&#039;. Copy the format from &#039;&#039;&#039;lib\behat\behat_base.php&#039;&#039;&#039;, but set your class to extend the behat_base class instead of the MinkExtension. You can define new behat steps by declaring functions with the appropriate heading. &lt;br /&gt;
&lt;br /&gt;
You will not be  able to use these steps and features right away. Check [[Running_acceptance_test#New_step_definitions_or_features_are_not_executed|this section]] for instructions on how to update the behat integration. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For further information on how to create new steps definitions, check [[Acceptance testing/Custom acceptance steps]].&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;
Alternative way of running three Selenium servers in parallel:&lt;br /&gt;
&lt;br /&gt;
 $ printf %d\\n {4444..4446} | xargs -n 1 -P 3 java -jar /path/to/your/selenium/server/selenium-server-standalone-2.NN.N.jar -port&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;
=== Increasing timeouts ===&lt;br /&gt;
&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
Javascript code and/or AJAX requests are not ready after 10 seconds. &lt;br /&gt;
There is a Javascript error or the code is extremely slow.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it could just mean that the browser was not yet ready. You may find that the test works if you run it again. If you get this error frequently, it might be useful to increase the timeout.&lt;br /&gt;
&lt;br /&gt;
It is possible to increase this timeout by adding a line in your config.php. (Requires Moodle versions 3.5 (from 3.5.6), 3.6 (from 3.6.4), or 3.7+.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_increasetimeout = 2;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will increase all the timeouts by a factor of 2; if that isn&#039;t sufficient, you could use 3. &lt;br /&gt;
&lt;br /&gt;
Increasing timeouts will make tests run a bit slower (because there are points where Behat waits up to a timeout to make sure something doesn&#039;t happen) so don&#039;t do this unless you need to.&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;
=== 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;
== Trouble shooting ==&lt;br /&gt;
&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;
=== Errors during setup (before test are launched) ===&lt;br /&gt;
Typical errors are:&lt;br /&gt;
* Behat requirement not satisfied: http://127.0.0.1/m/stable_master is not available, ensure you specified correct url and that the server is set up and started.&lt;br /&gt;
* Behat is configured but not enabled on this test site.&lt;br /&gt;
&lt;br /&gt;
In order to fix those errors please check that: the behat_dataroot has correct write permissions and that the $CFG-&amp;gt;behat* variables are placed before the lib/setup.php include:&lt;br /&gt;
 require_once(__DIR__ . &#039;/lib/setup.php&#039;);&lt;br /&gt;
&lt;br /&gt;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&lt;br /&gt;
&lt;br /&gt;
If you are using chrome, you need to ensure that the driver matches the version of the installed chrome browser – which may change on OS updates/upgrades.  Moodle or Selenium will not give the appropriate message – see [https://tracker.moodle.org/browse/MDL-67659/ MDL-67659].  One solution is the one suggested in the issue and use Andrew Nicols’ [https://github.com/andrewnicols/chromedriver-wrapper/  Chromedriver Wrapper] which will ensure you have the appropriate driver before running the tests.&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;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Coding&amp;diff=57230</id>
		<title>Coding</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Coding&amp;diff=57230"/>
		<updated>2020-04-06T11:10:49Z</updated>

		<summary type="html">&lt;p&gt;Tim Hunt: /* Events */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is the top-level page describing Moodle&#039;s coding guidelines.  It&#039;s the place to start if you want to know how to write code for Moodle.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Moodle architecture==&lt;br /&gt;
&lt;br /&gt;
Moodle tries to run on the widest possible range of platforms, for the widest possible number of people, while remaining easy to install, use, upgrade and integrate with other systems.&lt;br /&gt;
&lt;br /&gt;
For more about this, see [[Moodle architecture]].&lt;br /&gt;
&lt;br /&gt;
==Plugins== &lt;br /&gt;
&lt;br /&gt;
Moodle has a general philosophy of modularity.  There are nearly 30 different standard types of plugins and even more sub-plugin types, however all of these plugin types work the same way. Blocks and activities are the only small exceptions.&lt;br /&gt;
&lt;br /&gt;
See [[Plugins|Moodle plugins]] and [[Subplugins|Moodle sub-plugins]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Coding style==&lt;br /&gt;
&lt;br /&gt;
Consistent [[Coding style|coding style]] is important in any development project, and particularly so when many developers are involved. A standard style helps to ensure that the code is easier to read and understand, which helps overall quality. &lt;br /&gt;
&lt;br /&gt;
Writing your code in this way is an important step to having your code accepted by the Moodle community.&lt;br /&gt;
&lt;br /&gt;
Our [[Coding_style|Moodle coding style]] document explains this standard.&lt;br /&gt;
&lt;br /&gt;
==Security==&lt;br /&gt;
&lt;br /&gt;
Security is about protecting the interests and data of all our users.  Moodle may not be banking software, but it is still protecting a lot of sensitive and important data such as private discussions and grades from outside eyes (or student hackers!) as well as protecting our users from spammers and other internet predators.&lt;br /&gt;
&lt;br /&gt;
It&#039;s also a script running on people&#039;s servers, so Moodle needs to be a responsible Internet citizen and not introduce vulnerabilities that could allow crackers to gain unlawful access to the server it runs on. &lt;br /&gt;
&lt;br /&gt;
Any single script (in Moodle core or a third party module) can introduce a vulnerability to thousands of sites, so it&#039;s important that all developers strictly follow our [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
==XHTML and CSS==&lt;br /&gt;
&lt;br /&gt;
It&#039;s important that Moodle produces strict, well-formed [http://en.wikipedia.org/wiki/HTML5 HTML 5] code (preferably backwards compatible with [[XHTML|XHTML 1.1]] if possible), compliant with all common accessibility guidelines (such as [http://www.w3.org/TR/WCAG20/ W3C WAG 2.0], [http://www.w3.org/TR/wai-aria-practices/ ARIA]).&lt;br /&gt;
&lt;br /&gt;
CSS should be used for layout. Moodle comes with several themes installed. Beginning with version 2.7, only the &#039;Clean&#039; theme comes in the base Moodle code. The &#039;standard&#039; theme, which should be a plain theme suitable to act as a building block for other themes. That should contain the minimal styling to make Moodle look OK and be functional. Then Moodle comes with several other default themes that look good and demonstrate various techniques for building themes.&lt;br /&gt;
&lt;br /&gt;
This helps consistency across browsers in a nicely-degrading way (especially those using non-visual or mobile browsers), as well as improving life for theme designers.&lt;br /&gt;
&lt;br /&gt;
==JavaScript==&lt;br /&gt;
&lt;br /&gt;
In general, everything in Moodle should work with JavaScript turned off in your browser. This is important for accessibility, and in line with the principles of unobtrusive JavaScript and progressive enhancement. &lt;br /&gt;
&lt;br /&gt;
Features that are not available without JavaScript must be compliant with all accessibility standards.&lt;br /&gt;
&lt;br /&gt;
See the [[JavaScript guidelines|Moodle JavaScript guidelines]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Internationalisation==&lt;br /&gt;
&lt;br /&gt;
Moodle works in over 84 languages because we pay great attention to keeping the language strings and locale information separate from the code, in language packs.&lt;br /&gt;
&lt;br /&gt;
The default language for all code, comments and documentation, however, is English (AU).&lt;br /&gt;
&lt;br /&gt;
Full details:  [[String API]]&lt;br /&gt;
&lt;br /&gt;
==Accessibility==&lt;br /&gt;
&lt;br /&gt;
Moodle should work well for the widest possible range of people.&lt;br /&gt;
&lt;br /&gt;
See [[Accessibility|Moodle Accessibility]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Usability==&lt;br /&gt;
&lt;br /&gt;
See [[Interface_guidelines| Interface Guidelines]] (under construction)&lt;br /&gt;
&lt;br /&gt;
==Performance==&lt;br /&gt;
&lt;br /&gt;
The load any Moodle site can cope with will, of course, depend on the server and network hardware that it is running on. However there are some features (intended especially for developers) that are discouraged on production sites for performance reasons.&lt;br /&gt;
&lt;br /&gt;
The most important property is scalability, so a small increase in the number of users, courses, activities in a course, and so on, only causes a correspondingly small increase in server load.&lt;br /&gt;
&lt;br /&gt;
For more information and advice, see [[Performance and scalability|Performance and scalability]].&lt;br /&gt;
&lt;br /&gt;
==Database==&lt;br /&gt;
&lt;br /&gt;
Moodle has a powerful database abstraction layer that we wrote ourselves, called [[XMLDB_Documentation|XMLDB]].  This lets the same Moodle code work on MySQL/MariaDB, PostgreSQL, MS SQL Server and Oracle. There are know issues when using Oracle, it is not fully supported and is not recommended for production sites.&lt;br /&gt;
&lt;br /&gt;
We have tools and API for [[DDL functions|defining and modifying tables]] as well as [[Data manipulation API|methods for getting data in and out]] of the database.&lt;br /&gt;
&lt;br /&gt;
Overview: [[Database|Moodle Database]] guidelines&lt;br /&gt;
&lt;br /&gt;
==Events==&lt;br /&gt;
&lt;br /&gt;
Moodle allows inter-module communication via &#039;&#039;&#039;events&#039;&#039;&#039;.  Modules can &#039;&#039;&#039;trigger&#039;&#039;&#039; specific events and other modules can choose to &#039;&#039;&#039;handle/observe&#039;&#039;&#039; those events.&lt;br /&gt;
&lt;br /&gt;
See:&lt;br /&gt;
* [[Events API]] - Since Moodle 2.6 - in particular, note the [[Events_API#Events_naming_convention|Events naming convention]].&lt;br /&gt;
&lt;br /&gt;
==Web services==&lt;br /&gt;
&lt;br /&gt;
Should be implemented according to [[Web service API functions]] and [[How to contribute a web service function to core]], including the [[Web_service_API_functions#Naming_convention|Naming convention]].&lt;br /&gt;
&lt;br /&gt;
==Manual testing==&lt;br /&gt;
&lt;br /&gt;
All issues integrated into the core codebase are tested both during Integration, and subsequently by our testing team. While much of this testing is automated, there are many parts which cannot be automated, and manual testing is required.&lt;br /&gt;
&lt;br /&gt;
Moodle has guidelines on [[Testing_instructions_guide|how to write clear testing instructions]] which we recommend you read and follow.&lt;br /&gt;
&lt;br /&gt;
==Unit testing==&lt;br /&gt;
&lt;br /&gt;
[http://en.wikipedia.org/wiki/Unit_testing Unit testing] is not simply a technique but a philosophy of software development.&lt;br /&gt;
&lt;br /&gt;
The idea is to create automatable tests for each bit of functionality that you are developing (at the same time you are developing it).  This not only helps everyone later test that the software works, but helps the development itself, because it forces you to work in a modular way with very clearly defined structures and goals.&lt;br /&gt;
&lt;br /&gt;
Moodle uses a framework called [https://github.com/sebastianbergmann/phpunit/ PHPUnit] that makes writing unit tests fairly simple.&lt;br /&gt;
&lt;br /&gt;
See [[PHPUnit]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Acceptance testing==&lt;br /&gt;
&lt;br /&gt;
PHPUnit covers mostly the internal implementation of functions and classes, the user interaction testing can be automated using the [http://behat.org Behat framework].&lt;br /&gt;
&lt;br /&gt;
See [[Acceptance testing]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Third Party Libraries==&lt;br /&gt;
Moodle has a standard way to include third party libraries in your code. See https://docs.moodle.org/dev/Third_Party_Libraries&lt;br /&gt;
&lt;br /&gt;
== Other standards ==&lt;br /&gt;
&lt;br /&gt;
Please note that Moodle coding style and design is pretty unique, it is not compatible with [http://pear.php.net/manual/en/standards.php PEAR coding standards] or any other common PHP standards.&lt;/div&gt;</summary>
		<author><name>Tim Hunt</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Events_API&amp;diff=57216</id>
		<title>Events API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Events_API&amp;diff=57216"/>
		<updated>2020-04-06T10:56:17Z</updated>

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