<?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=Bencellis</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=Bencellis"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Bencellis"/>
	<updated>2026-06-06T03:50:24Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=PHPUnit_integration&amp;diff=62413</id>
		<title>PHPUnit integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=PHPUnit_integration&amp;diff=62413"/>
		<updated>2022-05-24T10:58:30Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Extra methods */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle PHPUnit integration was created to simplify using of PHPUnit framework in Moodle. It consists of specialised bootstrap script, utility scripts that initialise testing environment and highly optimised custom test case classes that handle automatic global state resetting after test that includes global variables, database rollback and purging of dataroot. We also try to bridge the gap between the design and coding style of Moodle and PHPUnit.&lt;br /&gt;
&lt;br /&gt;
Most of the documentation at http://www.phpunit.de/manual/3.6/en/index.html is relevant to Moodle PHPUnit integration, exceptions and additions are described below.&lt;br /&gt;
=Definitions=&lt;br /&gt;
These definitions may differ in each testing framework or programming language. The definitions here should be valid for PHPUnit framework with our Moodle tweaks.&lt;br /&gt;
;Test suite: is a collection of test cases or other suites usually related to one area of the product. It is defined in PHPUnit configuration files (phpunit.xml by default).&lt;br /&gt;
&lt;br /&gt;
;Test file: is a file with *_test.php name which contains a test case. (*_Test.php in PHPUnit)&lt;br /&gt;
&lt;br /&gt;
;Test case: is a class with test methods. Their name should match the name of the test file (*_test). Moodle tests extend basic_testcase or advanced_testcase. (PHPUnit\Framework\TestCase in PHPUnit)&lt;br /&gt;
&lt;br /&gt;
;Test: is a public method starting with &amp;quot;test_&amp;quot; prefix defined in test case class. It is the implementation of the test procedure.&lt;br /&gt;
&lt;br /&gt;
;Assertion: is the actual comparison of expected and actual behaviour of the tested code.&lt;br /&gt;
=Organisation of test files=&lt;br /&gt;
All testing related files are stored in &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;/tests/&amp;lt;/syntaxhighlight&amp;gt; subdirectories:&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/*_test.php&amp;lt;/syntaxhighlight&amp;gt; are PHPUnit test files&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/fixtures/&amp;lt;/syntaxhighlight&amp;gt; contains auxiliary files used in tests&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/performance/&amp;lt;/syntaxhighlight&amp;gt; is reserved for performance testing scripts&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/generator/&amp;lt;/syntaxhighlight&amp;gt; for generators&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/other/&amp;lt;/syntaxhighlight&amp;gt; is used for everything else that does not fit&lt;br /&gt;
==Class and file naming rules==&lt;br /&gt;
=== Actual (Moodle 3.11 and up) ===&lt;br /&gt;
The class names of all testcases need to be unique, with namespaces being used to guarantee this.&lt;br /&gt;
* All test case class names should match the file name, for example: the class name for &amp;lt;tt&amp;gt;mod/forum/test/this_test.php&amp;lt;/tt&amp;gt; should be &amp;lt;tt&amp;gt;this_test&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* All test case classes end with &#039;&#039;&#039;_test&#039;&#039;&#039; suffix.&lt;br /&gt;
* All test case classes should use the namespace they belong to, for example: the namespace for &amp;lt;tt&amp;gt;mod/forum/test/this_test.php&amp;lt;/tt&amp;gt; should be &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;. With sub-namespaces (and corresponding sub-directories) allowed to better match what is being tested.&lt;br /&gt;
Read about the rules for namespaces @ [[Coding style#Namespaces_within_.2A.2A.2Ftests_directories|coding style]] (grand summary = 100% the same rules that are applied to **/classes directories).&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
* class &#039;&#039;&#039;lib_test&#039;&#039;&#039; (with namespace &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;) is expected in file &#039;&#039;&#039;/mod/forum/tests/lib_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit mod/forum/tests/lib_test.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* class &#039;&#039;&#039;text_test&#039;&#039;&#039; (with namespace &amp;lt;tt&amp;gt;core&amp;lt;/tt&amp;gt;) is expected in file &#039;&#039;&#039;/lib/texts/text_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit lib/texts/text_test.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* class &#039;&#039;&#039;provider_test&#039;&#039;&#039; (with namespace &amp;lt;tt&amp;gt;block_comments\privacy&amp;lt;/tt&amp;gt;) is expected in file &#039;&#039;&#039;/blocks/comments/tests/privacy/provider_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit vendor/bin/phpunit blocks/comments/tests/privacy/provider_test.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Previous (until Moodle 3.10) ===&lt;br /&gt;
Frankenstyle prefix is used to guarantee this. Since 2.6 it is possible to use automatic class loader when executing individual unit tests.&lt;br /&gt;
* All test case classes start with Frankenstyle prefix, for example: &#039;&#039;&#039;mod_forum_&#039;&#039;&#039;, &#039;&#039;&#039;block_html_&#039;&#039;&#039;, &#039;&#039;&#039;core_&#039;&#039;&#039;.&lt;br /&gt;
* All test case classes end with &#039;&#039;&#039;_testcase&#039;&#039;&#039; suffix.&lt;br /&gt;
* File names are constructed from the class names - the Frankenstyle prefix is removed, suffix &#039;&#039;&#039;_testcase&#039;&#039;&#039; is replaced with &#039;&#039;&#039;_test&#039;&#039;&#039;&lt;br /&gt;
Finally, but not less important, test case classes can be (recommended) namespaces. Read about the rules to do it properly @ [[Coding style#Namespaces_within_.2A.2A.2Ftests_directories|coding style]] (grand summary = 100% the same rules that are applied to **/classes directories).&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
* class &#039;&#039;&#039;mod_forum_lib_testcase&#039;&#039;&#039; is expected in file &#039;&#039;&#039;/mod/forum/tests/lib_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit mod_forum_lib_testcase&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* class &#039;&#039;&#039;core_text_testcase&#039;&#039;&#039; is expected in file &#039;&#039;&#039;/lib/texts/text_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit core_text_testcase&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=basic_testcase=&lt;br /&gt;
&#039;&#039;basic_testcase&#039;&#039; is useful for simple tests that do not modify database or global variables. If something accidentally changes global state the test fails. This test case class is nearly identical to PHPUnit_Framework_TestCase used in PHPUnit documentation.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php // File: mod/myplugin/tests/sample_test.php&lt;br /&gt;
&lt;br /&gt;
namespace mod_myplugin;&lt;br /&gt;
&lt;br /&gt;
class sample_test extends \basic_testcase {&lt;br /&gt;
    public function test_equals() {&lt;br /&gt;
        $a = 1 + 2;&lt;br /&gt;
        $this-&amp;gt;assertEquals(3, $a);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=advanced_testcase=&lt;br /&gt;
By default each test starts with fresh new moodle installation. Test may modify database content, files or global variables. It is possible to use data generators to create new course, categories, module instances and other objects, alternatively table data can be preloaded from XML or CSV files.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php // File: mod/myplugin/tests/complex_test.php&lt;br /&gt;
&lt;br /&gt;
namespace mod_myplugin;&lt;br /&gt;
&lt;br /&gt;
class complex_test extends \advanced_testcase {&lt;br /&gt;
    public function test_isadmin() {&lt;br /&gt;
        global $DB;&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;resetAfterTest(true);          // reset all changes automatically after this test&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;assertFalse(is_siteadmin());   // by default no user is logged-in&lt;br /&gt;
        $this-&amp;gt;setUser(2);                    // switch $USER&lt;br /&gt;
        $this-&amp;gt;assertTrue(is_siteadmin());    // admin is logged-in now&lt;br /&gt;
&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;user&#039;, array()); // lets do something crazy&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;resetAllData();                // that was not a good idea, let&#039;s go back&lt;br /&gt;
        $this-&amp;gt;assertTrue($admin = $DB-&amp;gt;record_exists(&#039;user&#039;, array(&#039;id&#039;=&amp;gt;2)));&lt;br /&gt;
        $this-&amp;gt;assertFalse(is_siteadmin());&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Extra methods==&lt;br /&gt;
; resetAfterTest(bool) :true means reset automatically after test, false means keep changes to next test method, null means detect changes - the default is true.&lt;br /&gt;
; resetAllData() : reset global state in the middle of a test&lt;br /&gt;
; setAdminUser() : set current $USER as admin&lt;br /&gt;
; setGuestUser() : set current $USER as guest&lt;br /&gt;
; setUser() : set current $USER to a specific user - use getDataGenerator() to create one&lt;br /&gt;
; getDataGenerator() : returns data generator instance - use if you need to add new courses, users, etc.&lt;br /&gt;
; preventResetByRollback() : terminates active transactions, useful only when test contains own database transaction handling&lt;br /&gt;
; createXXXDataSet() : creates in memory structure of database table contents, used in loadDataSet() (eg: createXMLDataSet(), createCsvDataSet(), createFlatXMLDataSet())&lt;br /&gt;
; loadDataSet() : bulk loading of table contents&lt;br /&gt;
; getDebuggingMessages() : Return debugging messages from the current test. (Moodle 2.4 and upwards)&lt;br /&gt;
; resetDebugging() : Clear all previous debugging messages in current test. (Moodle 2.4 and upwards)&lt;br /&gt;
; assertDebuggingCalled() : Assert that exactly debugging was just called once. (Moodle 2.4 and upwards)&lt;br /&gt;
; assertDebuggingNotCalled() : Assert no debugging happened. (Moodle 2.4 and upwards)&lt;br /&gt;
; assertDebuggingCalledCount() : Asserts how many times debugging has been called. (Moodle 3.1 and upwards)&lt;br /&gt;
; [[Writing PHPUnit tests#Testing sending of messages|redirectMessages()]]: Captures ongoing messages for later testing (Moodle 2.4 and upwards)&lt;br /&gt;
; [[Writing PHPUnit tests#Testing_sending_of_emails|redirectEmails()]]: Captures ongoing emails for later testing (Moodle 2.6 and upwards)&lt;br /&gt;
==Restrictions==&lt;br /&gt;
* it is not possible to modify database structure such as create new table or drop columns from advanced_testcase.&lt;br /&gt;
=Moodle specific features=&lt;br /&gt;
* detection of global state changes - helps with detection of unintended changes in database&lt;br /&gt;
* highly optimised global state reset&lt;br /&gt;
* dataset loading - this feature is copied from PHPUnit database testcases&lt;br /&gt;
* automatic generation of phpunit.xml - init script builds list of plugin testcases&lt;br /&gt;
* database driver testing class - used for functional DB tests&lt;br /&gt;
* SimpleTest emulation class - helps with migration of old tests&lt;br /&gt;
* debugging() interception - enables to control and test any expected debug output. (Moodle 2.4 and upwards)&lt;br /&gt;
=Limitations=&lt;br /&gt;
* no Selenium support&lt;br /&gt;
* no support for PHPUnit_Extensions_Database_TestCase - it is possible to use data set loader only&lt;br /&gt;
=See also=&lt;br /&gt;
* [[Writing PHPUnit tests]]&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Behat_integration&amp;diff=62080</id>
		<title>Behat integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Behat_integration&amp;diff=62080"/>
		<updated>2022-04-26T14:29:37Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* change link to tools page */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
This page describes the internals of Behat and the integration with Moodle. For the functional description, how to install it, run the suite and add more features, see [[Acceptance_testing]].&lt;br /&gt;
&lt;br /&gt;
Behat is a framework for behavior driven development (BDD) which allows us to specify Moodle functionalities (aka features) as a human-readable list of steps. It parses these steps to executable actions to simulate user interaction against headless browsers (without java script support, only curl-kind requests) or user simulation tools like Selenium, which interacts with browsers and allows JavaScript events simulation.&lt;br /&gt;
== Objective ==&lt;br /&gt;
The aim of this integration is to allow Moodle components to have its own set of features and step definitions. This allows us to periodically execute sets of tests to detect regressions and Moodle features in different environments (browsers, DBs engines, web servers...). The Moodle QA tests will be progressively rewritten according to this format to run automatically.&lt;br /&gt;
== How Behat works ==&lt;br /&gt;
This section aims to explain the basics about BDD and Behat and a quick view of how Behat internally works from the CLI command execution to the results output.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note that all those examples are not necessarily real nor part of the suite, they are meant to help understand the concepts, see [[Acceptance_testing]] for real examples&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Some terms used:&lt;br /&gt;
* &#039;&#039;&#039;Features&#039;&#039;&#039;: Human-readable list of scenarios that describes a feature&lt;br /&gt;
  @auth&lt;br /&gt;
  &#039;&#039;&#039;Feature&#039;&#039;&#039;: Login&lt;br /&gt;
    In order to login&lt;br /&gt;
    As a moodle user&lt;br /&gt;
    I need to be able to validate the username and password against moodle&lt;br /&gt;
    &lt;br /&gt;
    &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Login as an existing user&lt;br /&gt;
      Given I am on &amp;quot;login/index.php&amp;quot;&lt;br /&gt;
      When I fill in &amp;quot;username&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
      And I fill in &amp;quot;password&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
      And I press &amp;quot;loginbtn&amp;quot;&lt;br /&gt;
      Then I should see &amp;quot;Moodle 101: Course Name&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Login as an non-existing user&lt;br /&gt;
      Given I am on &amp;quot;login/index.php&amp;quot;&lt;br /&gt;
      When I fill in &amp;quot;username&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
      And I fill in &amp;quot;password&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
      And I press &amp;quot;loginbtn&amp;quot;&lt;br /&gt;
      Then I should see &amp;quot;Invalid login&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Human-readable list of steps to describe an expected behaviour&lt;br /&gt;
  &#039;&#039;&#039;Scenario&#039;&#039;&#039;: Login as an existing user&lt;br /&gt;
    Given I am on &amp;quot;login/index.php&amp;quot;&lt;br /&gt;
    When I fill in &amp;quot;username&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I fill in &amp;quot;password&amp;quot; with &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I press &amp;quot;loginbtn&amp;quot;&lt;br /&gt;
    Then I should see &amp;quot;Moodle 101: Course Name&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;Steps&#039;&#039;&#039;: Human-readable sentences that describes an action. There are 3 types of steps, &amp;quot;Given&amp;quot; describing the initial context, &amp;quot;When&amp;quot; the event that provokes a change and &amp;quot;Then&amp;quot; where the outcomes should be asserted.&lt;br /&gt;
  I click on the &amp;quot;Add user&amp;quot; button&lt;br /&gt;
* &#039;&#039;&#039;Steps definitions&#039;&#039;&#039;: PHP methods referenced by steps when matching it&#039;s regular expression. The @Given, @When and @Then tags are descriptive and they are not taken into account when matching steps with steps definitions. The regular expressions placeholders are returned to the PHP method as arguments so methods can use them to tell the browser which button (for example) they want to click.&lt;br /&gt;
  /**&lt;br /&gt;
   * @When /^I click on the &amp;quot;(.*)&amp;quot; button$/&lt;br /&gt;
   */&lt;br /&gt;
  public function i_click_on_the_button($button) {&lt;br /&gt;
    // Simulates the user interaction (see Mink description below for more info)&lt;br /&gt;
    $this-&amp;gt;getSession()-&amp;gt;getPage()-&amp;gt;pressButton($button);&lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
* &#039;&#039;&#039;Behat&#039;&#039;&#039;: PHP framework and CLI application that wraps the whole process of features files loading + features files parsing + execution of actions in the browser + results output (http://behat.org/)&lt;br /&gt;
* &#039;&#039;&#039;Gherkin&#039;&#039;&#039;: Human-readable language used to define features that can be parsed and translated into PHP methods. For more info, it&#039;s the same language used by Cucumber, the BDD Ruby framework (https://github.com/cucumber/cucumber/wiki/Gherkin)&lt;br /&gt;
* &#039;&#039;&#039;Context&#039;&#039;&#039;: In Behat scope a context is a PHP class that groups steps definitions (as methods)&lt;br /&gt;
* &#039;&#039;&#039;Mink&#039;&#039;&#039;: Is the component which interacts with browsers, simulating a real user interaction. It allows us to write PHP code (or use the available PHP methods) to send requests to the different browsers APIs through a common interface or extend it to allow browser-specific actions. The supported browsers includes Selenium, Selenium2, Sahi... http://mink.behat.org/&lt;br /&gt;
* &#039;&#039;&#039;Selenium 2&#039;&#039;&#039;: Web browser automation tool, applications like Mink can communicate with it through a RESTful API (http://code.google.com/p/selenium/wiki/JsonWireProtocol) to execute actions simulating user interaction.&lt;br /&gt;
* &#039;&#039;&#039;Selector type&#039;&#039;&#039;: Related with &#039;&#039;&#039;locator&#039;&#039;&#039;, is a way to select a node inside the page DOM, more info in https://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps&lt;br /&gt;
* &#039;&#039;&#039;Locator&#039;&#039;&#039;: Is what we are looking for inside the page DOM, it completely depends on the associated selector type, a few examples of it:&lt;br /&gt;
** Selector type = &amp;quot;link&amp;quot;, Locator = &amp;quot;Link text&amp;quot;&lt;br /&gt;
** Selector type = &amp;quot;field&amp;quot;, Locator = &amp;quot;Field legend text&amp;quot;&lt;br /&gt;
** Selector type = &amp;quot;css&amp;quot;, Locator = &amp;quot;.css-class #id&amp;quot;&lt;br /&gt;
** Selector type = &amp;quot;xpath&amp;quot;, Locator = &amp;quot;//input[@id=&#039;id-value&#039;]&amp;quot;&lt;br /&gt;
All this components are written in PHP, open sourced and packaged in a single and extensible framework.&lt;br /&gt;
=== Quick view of the whole process ===&lt;br /&gt;
# Behat CLI execution&lt;br /&gt;
#* Behat application initialization and loading of arguments (features files to execute, output format...)&lt;br /&gt;
#* Reads the Behat config file (browser servers are specified here)&lt;br /&gt;
#* Extensions overrides management&lt;br /&gt;
#* Gherkin initialization&lt;br /&gt;
# Features files selection&lt;br /&gt;
#* According to the arguments Gherkin looks for .features files&lt;br /&gt;
#** It can use different features loaders (single file, a directory, the default directory...)&lt;br /&gt;
#** The framework can be extended to allow multiple folders loading&lt;br /&gt;
# Features parsing (Gherkin)&lt;br /&gt;
#* Loops through the loaded features files looking for scenarios&lt;br /&gt;
#* Gets the list of steps of each scenario&lt;br /&gt;
#* There are hooks at different levels (https://docs.behat.org/en/latest/user_guide/context/hooks.html)&lt;br /&gt;
# Steps parsing (Gherkin)&lt;br /&gt;
#* Gherkin looks in the available steps definitions for a regular expression that matches the step text&lt;br /&gt;
# Step definition execution&lt;br /&gt;
#* The step definition code is executed&lt;br /&gt;
#* Steps definitions most of the time uses the Mink component to communicate with the browser API sending requests like &amp;quot;click on that button&amp;quot; or &amp;quot;go to XXX page&amp;quot;&lt;br /&gt;
# Scenario outcomes&lt;br /&gt;
#* The scenario counts as failed if an exception is thrown when executing a step definition (for example trying to click a non-existing button)&lt;br /&gt;
#* The scenario counts as passed if no exception is thrown during it&#039;s steps execution&lt;br /&gt;
# Finishing CLI execution&lt;br /&gt;
#* A summary with all the scenario results is displayed&lt;br /&gt;
#* It accepts different output formats (like JUnitXML) to it&#039;s execution in continuous integration systems (http://docs.behat.org/guides/6.cli.html#format-options)&lt;br /&gt;
== Moodle integration ==&lt;br /&gt;
It follows the approach chosen with PHPUnit:&lt;br /&gt;
* It comes disabled by default, Behat is not included within Moodle and it has to be installed separately with the composer installer&lt;br /&gt;
* Moodle components (subsystems and plugins) can have a tests/behat/ folder&lt;br /&gt;
* The scenarios are executed in a test environment, the production database and dataroot are not affected by the tests modifications&lt;br /&gt;
* The scenarios specifies their own fixtures and it&#039;s execution is isolated from other scenarios and features, resetting the test database and the test dataroot before each scenario&lt;br /&gt;
* Moodle lists the features files and steps definitions of it&#039;s components in a behat.yml file, similar to the phpunit.xml manifest&lt;br /&gt;
* A basic behat.yml.dist config file has been included&lt;br /&gt;
=== Alternative environment ===&lt;br /&gt;
Acceptance testing implies interaction with the browser like real users does, so it requires the site to be accessible via URL. The Moodle integration creates a new moodle site installation in parallel to the production one to run the tests in a sandbox without affecting the production environment, switching the regular $CFG-&amp;gt;wwwroot, $CFG-&amp;gt;dataroot and $CFG-&amp;gt;prefix to alternatives, which should be only accessible from localhost or internal networks. Info about how to run the tests in https://docs.moodle.org/dev/Acceptance_testing#Running_tests. &lt;br /&gt;
&lt;br /&gt;
This default configuration is useful when developing in a local host, but to run the tests automatically with Jenkins, travis, other CI systems or with saucelabs we provide a few extra settings. More info in https://docs.moodle.org/dev/Acceptance_testing#Advanced_usage&lt;br /&gt;
&lt;br /&gt;
All the behat CLI utilities we provide within the Moodle codebase (admin/tool/behat/cli/*) are using $CFG-&amp;gt;behat_wwwroot, $CFG-&amp;gt;behat_prefix and $CFG-&amp;gt;behat_dataroot instead of $CFG-&amp;gt;wwwroot, $CFG-&amp;gt;prefix and $CFG-&amp;gt;dataroot, this scripts are self-contained, but as we are accessing through a browser, we also need to switch the whole Moodle instance to test mode. For this there are two requirements:&lt;br /&gt;
* Test mode is enabled if&lt;br /&gt;
** &#039;&#039;&#039;php admin/tool/behat/cli/init.php&#039;&#039;&#039; or &#039;&#039;&#039;php admin/tool/behat/cli/util.php --enable&#039;&#039;&#039; has been executed&lt;br /&gt;
* Test mode is requested if&lt;br /&gt;
** The vendor/bin/behat command is running, we know it because we hook the Behat process before the tests begins to run and we require moodle config.php after it&lt;br /&gt;
** We set $CFG-&amp;gt;behat_wwwroot in config.php and we are accessing the moodle instance through it&lt;br /&gt;
The unique $CFG-&amp;gt;behat_wwwroot prevents unintended execution of acceptance tests on production sites.&lt;br /&gt;
=== Javascript ===&lt;br /&gt;
There are two types of tests depending on if their scenario needs a real browser capable of execute Javascript or if they can run in a headless browser.&lt;br /&gt;
* Tests with Javascript requires interaction with a browser through a user simulation tool like Selenium or ZombieJS to be executed; see http://mink.behat.org/#different-browsers-drivers for all available drivers&lt;br /&gt;
* Test that does not requires Javascript are faster to run but can not test rich applications like Moodle&lt;br /&gt;
In most of the cases a Javascript test would be more appropriate because most of the users uses Javascript-capable browsers, non-Javascript tests can be useful to ensure that Moodle maintains its functionality without Javascript enabled and to ensure there are no big issues, regressions or exceptions in general.&lt;br /&gt;
=== Admin tool &amp;quot;Acceptance testing&amp;quot; ===&lt;br /&gt;
There is an admin tool to run and ease the creation of acceptance tests. &lt;br /&gt;
* Web interface: The web interface allows you to list and filter the available steps definitions, a non-technical user can use this interface to write new features (admin/tool/behat/index.php)&lt;br /&gt;
* CLI: Command to enable and disable the test environment and to update the behat.yml file with the system tests and steps definitions (admin/tool/behat/cli/util.php and admin/tool/behat/cli/init.php for a quick start)&lt;br /&gt;
[[File:Acceptance_testing_UI_2.5.png]]&lt;br /&gt;
=== Available steps to create tests ===&lt;br /&gt;
There are behat libraries with tons of steps definitions to run all sort of processes and interactions with the browser, some of them overlaps Moodle-specific libraries and tests writers can be confused not only by this also by the amount of steps and vague or too technical steps descriptions. Moodle provides a set of steps definitions written in a common format to make tests writers life easier. New steps definitions must follow this guidelines: https://docs.moodle.org/dev/Acceptance_testing#Adding_steps_definitions&lt;br /&gt;
=== Behat extension ===&lt;br /&gt;
A new Behat extension (https://github.com/moodlehq/moodle-behat-extension) has been created to maintain Behat and its dependencies as they comes from upstream.&lt;br /&gt;
&lt;br /&gt;
The aim of this extension is:&lt;br /&gt;
* Load features from multiple folders (Moodle subsystems and plugins)&lt;br /&gt;
* Load steps definitions from multiple folders and add them as subcontexts (Moodle subsystems and plugins)&lt;br /&gt;
* Return the available steps definitions in a more human-readable format without regexps&lt;br /&gt;
* Look for exceptions, debugging() calls, PHP error messages and other backtraces in Moodle&#039;s output&lt;br /&gt;
* Extend the Selenium2 behat driver to allow extra Selenium capabilities&lt;br /&gt;
* Add a new formatter method based on progress (the moodle default one) to display info about the moodle site being tested&lt;br /&gt;
All the other particularities of this integration can managed playing with different Behat config parameters.&lt;br /&gt;
[[Category:Behat]]&lt;br /&gt;
[[es:Behat]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=61590</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=61590"/>
		<updated>2021-12-15T15:54:52Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Setting up Selenium */ Addition of later version and issue with version 4 jars.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle uses [https://behat.org Behat], a php framework for automated functional testing, as part of a suite of testing tools.&lt;br /&gt;
&lt;br /&gt;
Behat takes a set of Features, Scenarios, and Steps, and uses these to step through actions and test results using a real web browser. This is possible thanks to a protocol implemented in most modern web browsers called Webdriver.&lt;br /&gt;
&lt;br /&gt;
This documentation covers how to run Behat tests within Moodle, including requirements, setup, useful tips and tricks, and basic troubleshooting.&lt;br /&gt;
== Requirements ==&lt;br /&gt;
# Any recent OS with [[Releases|supported version of Moodle]] installed&lt;br /&gt;
# A recent browser (We support Firefox, Chrome as standard, but other browsers are possible)&lt;br /&gt;
# The WebDriver implementation for your browser&lt;br /&gt;
# A recent version of Selenium (Optional, but recommended)&lt;br /&gt;
# A recent Java Runtime Environment (Required if using Selenium)&lt;br /&gt;
=== Recommended extras ===&lt;br /&gt;
Some extra software is also recommended for testing with Behat.&lt;br /&gt;
==== Pre-configured browser profiles: moodle-browser-config ====&lt;br /&gt;
Available for [[Releases|all supported versions of Moodle]], the [https://github.com/andrewnicols/moodle-browser-config moodle-browser-config] is a recommended inclusion for Behat. This configuration tooling provides a range of standard browser profiles for testing.&lt;br /&gt;
&lt;br /&gt;
Note: In future, the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool may be included as composer dependency to Moodle but this is currently not the case.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
# Check out the moodle-browser-config repository:&lt;br /&gt;
    // Change directory to your git root, for example:&lt;br /&gt;
    $ cd ~/git&lt;br /&gt;
    // Clone the moodle-browser-config repository&lt;br /&gt;
    git clone https://github.com/andrewnicols/moodle-browser-config&lt;br /&gt;
# Open your Moodle installation&#039;s &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; in your preferred editor, and require the tool&#039;s init.php:&lt;br /&gt;
    require_once(&#039;/path/to/your/git/dir/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Provided profiles =====&lt;br /&gt;
The full list of profiles which are included with &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; are provided in its [https://github.com/andrewnicols/moodle-browser-config own documentation].&lt;br /&gt;
&lt;br /&gt;
The following is a summary of the profiles that most users may be interested in.&lt;br /&gt;
&lt;br /&gt;
You can also provide your own custom profiles, including for remote services such as Browserstack, and Saucelabs, as&lt;br /&gt;
well as for other browsers supporting the W3C Webdriver specification.&lt;br /&gt;
&lt;br /&gt;
Please note that &amp;lt;tt&amp;gt;Safari&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;Safaridriver&amp;lt;/tt&amp;gt; are not currently supported as they do not meet the W3C WebDriver specification.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Profile name&lt;br /&gt;
! Description&lt;br /&gt;
! Uses Selenium?&lt;br /&gt;
! Displays GUI?&lt;br /&gt;
|-&lt;br /&gt;
| firefox&lt;br /&gt;
| Use Firefox via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessfirefox&lt;br /&gt;
| Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| geckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessgeckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chrome&lt;br /&gt;
| Use Chrome via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschrome&lt;br /&gt;
| Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edge&lt;br /&gt;
| Use Edge via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedge&lt;br /&gt;
| Use Edge via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|}&lt;br /&gt;
==== chromedriver-wrapper ====&lt;br /&gt;
When using Google Chrome, you must use the correct version of the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; browser driver for the version of Chrome that you use.&lt;br /&gt;
&lt;br /&gt;
Since Google Chrome automatically updates on a regular basis, you will need to regularly upgrade your driver to match the version of Chrome that you are using.&lt;br /&gt;
&lt;br /&gt;
To make this easier, a &amp;lt;tt&amp;gt;chromedriver-wrapper&amp;lt;/tt&amp;gt; utility has been written. This inspects the version of Chrome that is in your path, and downloads the correct version of &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; for that version, before starting it.&lt;br /&gt;
&lt;br /&gt;
Installation instructinos can be found at [https://github.com/andrewnicols/chromedriver-wrapper https://github.com/andrewnicols/chromedriver-wrapper].&lt;br /&gt;
== Getting started (basic) ==&lt;br /&gt;
This is a quick walk through to get Behat running for the first time.&lt;br /&gt;
=== Setting up ===&lt;br /&gt;
There are a number of ways of configuring Behat on Moodle. This is one of the simplest.&lt;br /&gt;
&lt;br /&gt;
These notes assume that you have already installed a supported Java Runtime Environment, and the [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config]] tool.&lt;br /&gt;
==== Setting up Selenium ====&lt;br /&gt;
Generally we recommend use of Selenium, though this is not a fixed requirement. You can use the browser&#039;s driver implementation directly but this is harder to setup for the first time.&lt;br /&gt;
&lt;br /&gt;
Selenium is written in Java, and requires a recent version of the JRE to run. Please ensure that you have this installed prior to starting.&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.9.5 / 3.10.2 / 3.11.0 Moodle will work with any modern version of Selenium. At time of writing that is &amp;lt;tt&amp;gt;3.141.59&amp;lt;/tt&amp;gt;, and 4.1.0.  &#039;&#039;&#039;Note:&#039;&#039;&#039; There appears to be an issue with the version 4 jars - particually in 4.1.0 that generates a &#039;&#039;&#039;&amp;quot;Could not open connection: Capability value must be set”&#039;&#039;&#039; error which appears to be an issue between Selenium and the MInkExtension for JavaScript tests - see https://githubmemory.com/repo/Behat/MinkExtension/activity for more information.  The quick work around is to use the earlier version (3.141.59).&lt;br /&gt;
# Download the Selenium Server (Grid) from [https://www.selenium.dev/downloads/ https://www.selenium.dev/downloads/]. This is a single JAR file, put it anywhere handy.&lt;br /&gt;
# Start Selenium&lt;br /&gt;
    # Version 3.141.59:&lt;br /&gt;
    $ java -jar selenium-server-standalone-3.141.59.jar&lt;br /&gt;
    &lt;br /&gt;
    # Version 4.0.0 and later:&lt;br /&gt;
    $ java -jar selenium-server-4.0.0-beta-3.jar standalone&lt;br /&gt;
You can optionally specify a number of settings, depending on the version of Selenium that you are using, including the port to run on.&lt;br /&gt;
See the help for the version of Selenium that you are using.&lt;br /&gt;
==== Setting up your browsers ====&lt;br /&gt;
Selenium is just an intelligient wrapper to start, and manage your browser sessions. It doesn&#039;t actually include any web browsers itself.&lt;br /&gt;
&lt;br /&gt;
Moodle HQ run all behat tests against both Firefox and Chrome multiple times per day. Other combinations, including Microsoft Edge, are also supported.&lt;br /&gt;
&lt;br /&gt;
To use Behat, you will need a recent version of your preferred browser, as well as a &amp;lt;tt&amp;gt;driver&amp;lt;/tt&amp;gt; for that browser. The driver is responsible for communication between Selenium (or Moodle directly) and the browser.&lt;br /&gt;
&lt;br /&gt;
Both the browser, and its driver, must be placed inside your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt; - this may be somewhere like &amp;lt;tt&amp;gt;/usr/bin&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;/usr/local/bin&amp;lt;/tt&amp;gt;, or perhaps a user bin directory like &amp;lt;tt&amp;gt;~/bin&amp;lt;/tt&amp;gt; which is present in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Chrome =====&lt;br /&gt;
You can download Google Chrome from [https://www.google.com.au/chrome https://www.google.com.au/chrome].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://chromedriver.chromium.org/downloads correct version of the chromedriver] as per the&lt;br /&gt;
documentation. Alternatively you can make use of the [[Running_acceptance_test#chromedriver-wrapper|chromedriver-wrapper utility]] noted in the Recommended extras sections.&lt;br /&gt;
&lt;br /&gt;
Either the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;, or the &amp;lt;tt&amp;gt;chromedriver-wrapper/bin&amp;lt;/tt&amp;gt; folder must be in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
===== Firefox =====&lt;br /&gt;
You can download Mozilla Firefox from [https://www.mozilla.org/en-US/firefox/new/ https://www.mozilla.org/en-US/firefox/new/].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://github.com/mozilla/geckodriver/releases correct version of geckodriver as per the [https://firefox-source-docs.mozilla.org/testing/geckodriver/Support.html documentation].&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Set up Moodle ====&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat&lt;br /&gt;
# Set the following in your Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $CFG-&amp;gt;behat_dataroot = &#039;/path/to/the/dataroot/you/created&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://127.0.0.1/path/to/your/site&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_prefix = &#039;beh_&#039;;&lt;br /&gt;
# We recommend that you also include the &amp;lt;tt&amp;gt;behat-browser-config&amp;lt;/tt&amp;gt; if you have not done so already.&lt;br /&gt;
    require_once(&#039;/path/to/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Notes about the behat_wwwroot =====&lt;br /&gt;
You will need to set the &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to your Moodle site, but it &#039;&#039;must&#039;&#039; use a different value to your &amp;lt;tt&amp;gt;$CFG-&amp;gt;wwwroot&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
One common way to do this is to use &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; for behat, but &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; for standard use. Alternatively you can add an additional hostname in your &amp;lt;tt&amp;gt;/etc/hosts&amp;lt;/tt&amp;gt; file and use this instead.&lt;br /&gt;
&lt;br /&gt;
If you use Docker, then you may be able to use &amp;lt;tt&amp;gt;host.docker.internal&amp;lt;/tt&amp;gt; where your site is hosted on the docker host&lt;br /&gt;
==== Configure Behat for Moodle ====&lt;br /&gt;
After setting your configuration, you can simply initialise behat:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will install all required Composer dependencies, install a new Moodle site, and put configuration in place&lt;br /&gt;
&lt;br /&gt;
When it finishes it will give advice on how to run Behat.&lt;br /&gt;
==== Run Behat tests ====&lt;br /&gt;
Before running behat, ensure that your Selenium server is running.&lt;br /&gt;
&lt;br /&gt;
The easiest way to run behat, and test that everything works is by simply running it using the command provided when you&lt;br /&gt;
initialised Behat. If you didn&#039;t make a note of it, you can just run the initialisation again.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will give you a command which you can then run. This command will run every behat scenario, which will take a considerable amount of time. This command will look a bit like this:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
To make this more useful you an combine it with flags, for example to only run certain &amp;lt;tt&amp;gt;tags&amp;lt;/tt&amp;gt; or for a specific Behat Feature file, or Scenario.&lt;br /&gt;
&lt;br /&gt;
To run all features/scenarios which are tagged with &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --tags=@mod_forum&lt;br /&gt;
To run one specific feature file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature&lt;br /&gt;
To run one specific scenario within a feature file:&lt;br /&gt;
    # To run the Scenario on line 38 of the file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature:38&lt;br /&gt;
To run one specific scenario by name:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --name=&amp;quot;As a teacher I can see my own response&amp;quot;&lt;br /&gt;
See the upstream documentation on Behat, and Gherkin filters for more information.&lt;br /&gt;
===== Running using a different browser =====&lt;br /&gt;
The default browser in Behat is &amp;lt;tt&amp;gt;Firefox&amp;lt;/tt&amp;gt;. To specify a different browser profile, you can add the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument. For example, to use Chrome in Headless mode:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
If you are using the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; utility, then you can use any profile listed in [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config|moodle-browser-config]]. Otherwise you can write your own browser profile configuration.&lt;br /&gt;
=== Advanced testing ===&lt;br /&gt;
==== Run tests without Selenium (chromedriver, geckodriver) ====&lt;br /&gt;
Historically, Behat required Selenium server, however browsers now make use of their own automation layer. For example, Firefox uses &amp;lt;tt&amp;gt;Geckodriver&amp;lt;/tt&amp;gt; and Chrome uses &amp;lt;tt&amp;gt;Chromedriver&amp;lt;/tt&amp;gt;. As a result the use of Selenium itself is now optional.&lt;br /&gt;
&lt;br /&gt;
The moodle-browser-config tool includes standard profiles to use these drivers directly and without the use of Selenium.&lt;br /&gt;
&lt;br /&gt;
To use the drivers directly, you must run the driver itself, for example to run &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ chromedriver&lt;br /&gt;
To run &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ geckodriver&lt;br /&gt;
Note: geckodriver runs on port 4444 by default. You cannot geckodriver at the same time as selenium.&lt;br /&gt;
&lt;br /&gt;
After starting your preferred browser, you can then run behat and specify an alternative profile, for example:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=geckodriver&lt;br /&gt;
==== Headless browsers ====&lt;br /&gt;
There are a number of reasons that you may prefer to use a headless browser. It can be particularly helpful if you are running the tests on a remote system, for example over SSH, or if you do not want to be interrupted by browsers popping up on your machine.&lt;br /&gt;
&lt;br /&gt;
The following headless profiles are some of those provided in the moodle-browser-config tool as standard:&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessfirefox&amp;lt;/tt&amp;gt; Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessgeckodriver&amp;lt;/tt&amp;gt; Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschrome&amp;lt;/tt&amp;gt; Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschromedriver&amp;lt;/tt&amp;gt; Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
These can be provided to the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument to behat:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
==== Parallel runs ====&lt;br /&gt;
Out-of-the-box, Moodle will configure Behat to run a single Moodle installation with all tests run in series. This is great for developer use where you are running a single test. or a small suite of tests. However this can be quite slow. A lot of time is spent waiting in Behat for things to happen. This may be for a page to load, for additional content to load, or even explicit waits because some interactions must be deliberately slowed down. As a result, a system running behat will not have a particularly high load most of the time.&lt;br /&gt;
&lt;br /&gt;
If you want to run a large suite of tests then it is possible to take advantage of the relatively low resource consumption by running several behat runners in parallel. This is commonly referred to as a &#039;&#039;&#039;Parallel run&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A parallel run of behat takes the same codebase and creates several installations rather than just a single Moodle installation. The behat Feature files are then grouped and allocated between each of the separate installations.&lt;br /&gt;
&lt;br /&gt;
To support this, each of the parallels runs needs its own:&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_dataroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* database&lt;br /&gt;
Rather than using an entirely separate database, the same database is actually used, but a different &amp;lt;tt&amp;gt;behat_prefix&amp;lt;/tt&amp;gt; is used to prefix the table names in the database differently.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
The Behat initialisation command is responsible for preparing Moodle to run a standard run. You&#039;ll have used this before when installing for a standard run:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
The same command can be used to prepare Moodle for a parallel run by specifying the &amp;lt;tt&amp;gt;--parallel&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-j&amp;lt;/tt&amp;gt; parameter:&lt;br /&gt;
    // Below command will initialise moodle to run 3 parallel tests.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3&lt;br /&gt;
This can be combined with the &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; flag to prepare Behat to run with all installed themes.&lt;br /&gt;
&lt;br /&gt;
A number of advanced options are also available but you are unlikely to need these:&lt;br /&gt;
# &amp;lt;tt&amp;gt;-m=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--maxruns=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; 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;
# &amp;lt;tt&amp;gt;--fromrun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;-o&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--optimize-runs&amp;lt;/tt&amp;gt; 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;
You can view details of all of these using the &amp;lt;tt&amp;gt;--help&amp;lt;/tt&amp;gt; flag to &amp;lt;tt&amp;gt;admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Running Parallel tests =====&lt;br /&gt;
You can use the Moodle behat runner to run all tests, including Standard runs. It is an intelligient wrapper around the standard &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; command which specifies the configuration file, and other required features.&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php&lt;br /&gt;
Many of the standard options and parameters that can be passed to &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; can also be passed to the Moodle runner, for example:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; Run tests which match the specified tags&lt;br /&gt;
# &amp;lt;tt&amp;gt;--name=&amp;quot;Scenario name&amp;quot;&amp;lt;/tt&amp;gt; Run a test matching the supplied scenario name&lt;br /&gt;
# &amp;lt;tt&amp;gt;--feature=&amp;quot;/path/to/test.feature&amp;quot;&amp;lt;/tt&amp;gt; Run a specific feature file.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; Features for specified theme will be executed.&lt;br /&gt;
The runner also includes a number of custom parameters relating to parallel runs:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--fromrun&amp;lt;/tt&amp;gt; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun&amp;lt;/tt&amp;gt; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; feature is particularly useful and can be used to replace a string in the command with the run number. This is useful when specifying output formats, and rerun files as noted below.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The following example demonstrates how Behat might be initialised with three parallel runs, to run on all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme&lt;br /&gt;
And then to run all tests matching the &amp;lt;tt&amp;gt;@tool_myplugin&amp;lt;/tt&amp;gt; tag, against the &amp;lt;tt&amp;gt;classic&amp;lt;/tt&amp;gt; theme:&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;classic&amp;quot;&lt;br /&gt;
===== Custom parameters 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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&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;
       // ...&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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;],&lt;br /&gt;
    ];&lt;br /&gt;
==== Tests filters ====&lt;br /&gt;
With the &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; or the &amp;lt;tt&amp;gt;-name&amp;lt;/tt&amp;gt; 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;
* &amp;lt;tt&amp;gt;@javascript&amp;lt;/tt&amp;gt;: All the tests that runs in a browser using Javascript; they require Selenium or the browser&#039;s own automation layer, as per [[Running acceptance test#Run%20tests%20without%20Selenium%20.28chromedriver.2C%20geckodriver.29|Running acceptance test#Run tests without Selenium .28chromedriver.2C geckodriver.29,]] to be running, otherwise an exception will be thrown.&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_file_upload&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_alert&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_switch_window&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; window&amp;lt;/tt&amp;gt; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_switch_iframe&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; iframe&amp;lt;/tt&amp;gt; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_cross_browser&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@componentname&amp;lt;/tt&amp;gt;: 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;
==== Output formats ====&lt;br /&gt;
Behat is able to output in a number of different formats, and to different locations as required.&lt;br /&gt;
&lt;br /&gt;
This can be achieved by specifying the &amp;lt;tt&amp;gt;--format&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;--out&amp;lt;/tt&amp;gt; parameters when running behat, for example:&lt;br /&gt;
    // Run behat, using the &#039;pretty&#039; format and outputting the value to /tmp/pretty.txt&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt&lt;br /&gt;
It is also possible to output to multiple formats simultaneously by repeating the arguments, for example:&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
The following output formats are supported:&lt;br /&gt;
# &amp;lt;tt&amp;gt;progress&amp;lt;/tt&amp;gt;: Prints one character per step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;pretty&amp;lt;/tt&amp;gt;: Prints the feature as is.&lt;br /&gt;
# &amp;lt;tt&amp;gt;junit&amp;lt;/tt&amp;gt;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_progress&amp;lt;/tt&amp;gt;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_list&amp;lt;/tt&amp;gt;: List all scenarios.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_stepcount&amp;lt;/tt&amp;gt;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_screenshot&amp;lt;/tt&amp;gt;: Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump image only&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump html only.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump both.&lt;br /&gt;
## &amp;lt;tt&amp;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;&amp;lt;/tt&amp;gt;&lt;br /&gt;
Note: 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;
When working with parallel runs, you may wish to have an output for each run. If you were to specify a standard path for this then each of the parallel runs would overwrite the others file. The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; option allows this to be handled:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
In this example, the &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; argument is provided with a value of &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt;. Anywhere that &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt; appears in the command it will be replaced with the run number. The above command will generate a set of commands like:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun1/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_1.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun2/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_2.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun3/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_3.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
==== Rerun failed scenarios ====&lt;br /&gt;
With slow systems or parallel run you may experience see some random failures. These may happen when your system is too slow, when it is too fast, or where a page depends on external dependencies.&lt;br /&gt;
&lt;br /&gt;
To help with this it is possible to rerun any failed scenarios using the &amp;lt;tt&amp;gt;--rerun&amp;lt;/tt&amp;gt; option to Behat.&lt;br /&gt;
&lt;br /&gt;
The following example runs Behat with the rerun option:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
If any single test fails then the command will return a non-zero exit code. Running the same command again will mean that only failed scenarios are run.&lt;br /&gt;
&lt;br /&gt;
For a parallel run it can be called as follows:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
The Moodle behat runner also includes an &amp;lt;tt&amp;gt;--auto-rerun&amp;lt;/tt&amp;gt; option which will automatically rerun failed scenarios exactly once.&lt;br /&gt;
==== Running behat with specified theme ====&lt;br /&gt;
Behat can be run with any installed theme, but it must be initialised with the &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; option first.&lt;br /&gt;
&lt;br /&gt;
After configuring the theme can be specified using the &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; parameter.&lt;br /&gt;
&lt;br /&gt;
Note: The default theme in Moodle (boost) has the suite name &amp;lt;tt&amp;gt;default&amp;lt;/tt&amp;gt;.&lt;br /&gt;
    // Initialise Behat for all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --add-core-features-to-theme&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against all initalised themes:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the default theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=default&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the &#039;classic&#039; theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=classic&lt;br /&gt;
==== Using Docker to start selenium server ====&lt;br /&gt;
There are a wide range of docker images available which contain a browser with Selenium. You will probably be using the official SeleniumHQ images unless you have a specific reason not to.&lt;br /&gt;
&lt;br /&gt;
The complete list of available SeleniumHQ images is available at [https://hub.docker.com/u/selenium/ https://hub.docker.com/u/selenium/].&lt;br /&gt;
&lt;br /&gt;
Moodle uses the &#039;&#039;standalone&#039;&#039; version and any recent version with version 3.141.59 or higher is supported.&lt;br /&gt;
&lt;br /&gt;
For any test which uploads files, for example when interacting with the filepicker, you must also ensure that the Moodle directory is mounted as on your local filesystem.&lt;br /&gt;
&lt;br /&gt;
An example usage is:&lt;br /&gt;
    $ docker run -d -p 4444:4444 -v `pwd`:`pwd` selenium/standalone-firefox:latest&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In the Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; file you must change the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to an address that can be reached from within the docker image.&lt;br /&gt;
&lt;br /&gt;
You cannot use &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; as this will be the IP address of the docker container itself.&lt;br /&gt;
&lt;br /&gt;
On some more recent versions of Docker you can use &amp;lt;tt&amp;gt;http://host.docker.internal/&amp;lt;/tt&amp;gt;, for example if my site is located at &amp;lt;tt&amp;gt;/sm&amp;lt;/tt&amp;gt; on my development machine, I can set the following:&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://host.docker.internal/sm&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Manually configuring other browsers =====&lt;br /&gt;
If you would prefer not to use the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool but still wish to specify different browsers then you can do so using the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_profiles&amp;lt;/tt&amp;gt; array. Each key/value pair contains a profile name, and the configuration for that profile. For example:&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;chrome&#039; =&amp;gt; [&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;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
=== Increasing timeouts ===&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it may just mean that your browser did not finish generating parts of the UI before behat tried was finished.&lt;br /&gt;
&lt;br /&gt;
If you find that this happens regularly on different scenarios then you may want to increase the timeout:&lt;br /&gt;
    $CFG-&amp;gt;behat_increasetimeout = 2;&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: This is usually an indicator that your development machine is not well tuned. A better option would be to find out where the bottleneck is. This is usually the database configuration.&lt;br /&gt;
&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;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/syntaxhighlight&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;
=== Tests are failing ===&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;
=== The tests are failing, and the error message is completely useless ===&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;
=== 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;
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;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&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;
&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;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Quick setup and testing using moodle-docker ===&lt;br /&gt;
This is a quick guide to help locally pass tests for your developments, before submitting them:&lt;br /&gt;
# Set up a default Moodle install using [https://github.com/moodlehq/moodle-docker moodle-docker], with the database and Moodle version of your choice. See its README for more details. This will start some docker containers.&lt;br /&gt;
# Initialize behat to start testing with this command, from the webserver container: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# Run the behat test of your choice, from the webserver container. For instance: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/behat --config /var/www/behatdata/behatrun/behat/behat.yml --tags tool_task&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And you&#039;ll see something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
Moodle 4.0dev (Build: 20210507), 0b47ea0a44a092f9000729ca7b15fff23111538b&lt;br /&gt;
Php: 7.3.26, mysqli: 5.7.21, OS: Linux 5.4.0-66-generic x86_64&lt;br /&gt;
Run optional tests:&lt;br /&gt;
&lt;br /&gt;
Accessibility: No&lt;br /&gt;
Server OS &amp;quot;Linux&amp;quot;, Browser: &amp;quot;firefox&amp;quot;&lt;br /&gt;
Started at 09-05-2021, 06:00&lt;br /&gt;
...................................................................... 70&lt;br /&gt;
...............................&lt;br /&gt;
12 scenarios (12 passed)&lt;br /&gt;
101 steps (101 passed)&lt;br /&gt;
2m53.27s (47.61Mb)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Information to re-home ==&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Disable behat context or features to run in theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&lt;br /&gt;
To disable specific contexts and features from being executed by a specific theme/suite you can create a &amp;lt;tt&amp;gt;/theme/{MYTHEME}/tests/behat/blacklist.json&amp;lt;/tt&amp;gt; file with following format.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
The above will:&lt;br /&gt;
# disable the use of step_definitions from &amp;lt;tt&amp;gt;behat_grade&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;behat_navigation&amp;lt;/tt&amp;gt; while running theme suite; and&lt;br /&gt;
# disable running of scenarios in &amp;lt;tt&amp;gt;auth/tests/behat/login.feature&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;grade/tests/behat/grade_hidden_items.feature&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Write new tests and behat methods ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&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;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=61559</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=61559"/>
		<updated>2021-12-07T15:39:48Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Add in Browser&amp;#039;s automation layers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle uses [https://behat.org Behat], a php framework for automated functional testing, as part of a suite of testing tools.&lt;br /&gt;
&lt;br /&gt;
Behat takes a set of Features, Scenarios, and Steps, and uses these to step through actions and test results using a real web browser. This is possible thanks to a protocol implemented in most modern web browsers called Webdriver.&lt;br /&gt;
&lt;br /&gt;
This documentation covers how to run Behat tests within Moodle, including requirements, setup, useful tips and tricks, and basic troubleshooting.&lt;br /&gt;
== Requirements ==&lt;br /&gt;
# Any recent OS with [[Releases|supported version of Moodle]] installed&lt;br /&gt;
# A recent browser (We support Firefox, Chrome as standard, but other browsers are possible)&lt;br /&gt;
# The WebDriver implementation for your browser&lt;br /&gt;
# A recent version of Selenium (Optional, but recommended)&lt;br /&gt;
# A recent Java Runtime Environment (Required if using Selenium)&lt;br /&gt;
=== Recommended extras ===&lt;br /&gt;
Some extra software is also recommended for testing with Behat.&lt;br /&gt;
==== Pre-configured browser profiles: moodle-browser-config ====&lt;br /&gt;
Available for [[Releases|all supported versions of Moodle]], the [https://github.com/andrewnicols/moodle-browser-config moodle-browser-config] is a recommended inclusion for Behat. This configuration tooling provides a range of standard browser profiles for testing.&lt;br /&gt;
&lt;br /&gt;
Note: In future, the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool may be included as composer dependency to Moodle but this is currently not the case.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
# Check out the moodle-browser-config repository:&lt;br /&gt;
    // Change directory to your git root, for example:&lt;br /&gt;
    $ cd ~/git&lt;br /&gt;
    // Clone the moodle-browser-config repository&lt;br /&gt;
    git clone https://github.com/andrewnicols/moodle-browser-config&lt;br /&gt;
# Open your Moodle installation&#039;s &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; in your preferred editor, and require the tool&#039;s init.php:&lt;br /&gt;
    require_once(&#039;/path/to/your/git/dir/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Provided profiles =====&lt;br /&gt;
The full list of profiles which are included with &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; are provided in its [https://github.com/andrewnicols/moodle-browser-config own documentation].&lt;br /&gt;
&lt;br /&gt;
The following is a summary of the profiles that most users may be interested in.&lt;br /&gt;
&lt;br /&gt;
You can also provide your own custom profiles, including for remote services such as Browserstack, and Saucelabs, as&lt;br /&gt;
well as for other browsers supporting the W3C Webdriver specification.&lt;br /&gt;
&lt;br /&gt;
Please note that &amp;lt;tt&amp;gt;Safari&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;Safaridriver&amp;lt;/tt&amp;gt; are not currently supported as they do not meet the W3C WebDriver specification.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Profile name&lt;br /&gt;
! Description&lt;br /&gt;
! Uses Selenium?&lt;br /&gt;
! Displays GUI?&lt;br /&gt;
|-&lt;br /&gt;
| firefox&lt;br /&gt;
| Use Firefox via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessfirefox&lt;br /&gt;
| Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| geckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessgeckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chrome&lt;br /&gt;
| Use Chrome via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschrome&lt;br /&gt;
| Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edge&lt;br /&gt;
| Use Edge via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedge&lt;br /&gt;
| Use Edge via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|}&lt;br /&gt;
==== chromedriver-wrapper ====&lt;br /&gt;
When using Google Chrome, you must use the correct version of the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; browser driver for the version of Chrome that you use.&lt;br /&gt;
&lt;br /&gt;
Since Google Chrome automatically updates on a regular basis, you will need to regularly upgrade your driver to match the version of Chrome that you are using.&lt;br /&gt;
&lt;br /&gt;
To make this easier, a &amp;lt;tt&amp;gt;chromedriver-wrapper&amp;lt;/tt&amp;gt; utility has been written. This inspects the version of Chrome that is in your path, and downloads the correct version of &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; for that version, before starting it.&lt;br /&gt;
&lt;br /&gt;
Installation instructinos can be found at [https://github.com/andrewnicols/chromedriver-wrapper https://github.com/andrewnicols/chromedriver-wrapper].&lt;br /&gt;
== Getting started (basic) ==&lt;br /&gt;
This is a quick walk through to get Behat running for the first time.&lt;br /&gt;
=== Setting up ===&lt;br /&gt;
There are a number of ways of configuring Behat on Moodle. This is one of the simplest.&lt;br /&gt;
&lt;br /&gt;
These notes assume that you have already installed a supported Java Runtime Environment, and the [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config]] tool.&lt;br /&gt;
==== Setting up Selenium ====&lt;br /&gt;
Generally we recommend use of Selenium, though this is not a fixed requirement. You can use the browser&#039;s driver implementation directly but this is harder to setup for the first time.&lt;br /&gt;
&lt;br /&gt;
Selenium is written in Java, and requires a recent version of the JRE to run. Please ensure that you have this installed prior to starting.&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.9.5 / 3.10.2 / 3.11.0 Moodle will work with any modern version of Selenium. At time of writing that is &amp;lt;tt&amp;gt;3.141.59&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;4.0.0-beta-3&amp;lt;/tt&amp;gt;.&lt;br /&gt;
# Download the Selenium Server (Grid) from [https://www.selenium.dev/downloads/ https://www.selenium.dev/downloads/]. This is a single JAR file, put it anywhere handy.&lt;br /&gt;
# Start Selenium&lt;br /&gt;
    # Version 3.141.59:&lt;br /&gt;
    $ java -jar selenium-server-standalone-3.141.59.jar&lt;br /&gt;
    &lt;br /&gt;
    # Version 4.0.0 and later:&lt;br /&gt;
    $ java -jar selenium-server-4.0.0-beta-3.jar standalone&lt;br /&gt;
You can optionally specify a number of settings, depending on the version of Selenium that you are using, including the port to run on.&lt;br /&gt;
See the help for the version of Selenium that you are using.&lt;br /&gt;
==== Setting up your browsers ====&lt;br /&gt;
Selenium is just an intelligient wrapper to start, and manage your browser sessions. It doesn&#039;t actually include any web browsers itself.&lt;br /&gt;
&lt;br /&gt;
Moodle HQ run all behat tests against both Firefox and Chrome multiple times per day. Other combinations, including Microsoft Edge, are also supported.&lt;br /&gt;
&lt;br /&gt;
To use Behat, you will need a recent version of your preferred browser, as well as a &amp;lt;tt&amp;gt;driver&amp;lt;/tt&amp;gt; for that browser. The driver is responsible for communication between Selenium (or Moodle directly) and the browser.&lt;br /&gt;
&lt;br /&gt;
Both the browser, and its driver, must be placed inside your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt; - this may be somewhere like &amp;lt;tt&amp;gt;/usr/bin&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;/usr/local/bin&amp;lt;/tt&amp;gt;, or perhaps a user bin directory like &amp;lt;tt&amp;gt;~/bin&amp;lt;/tt&amp;gt; which is present in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Chrome =====&lt;br /&gt;
You can download Google Chrome from [https://www.google.com.au/chrome https://www.google.com.au/chrome].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://chromedriver.chromium.org/downloads correct version of the chromedriver] as per the&lt;br /&gt;
documentation. Alternatively you can make use of the [[Running_acceptance_test#chromedriver-wrapper|chromedriver-wrapper utility]] noted in the Recommended extras sections.&lt;br /&gt;
&lt;br /&gt;
Either the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;, or the &amp;lt;tt&amp;gt;chromedriver-wrapper/bin&amp;lt;/tt&amp;gt; folder must be in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
===== Firefox =====&lt;br /&gt;
You can download Mozilla Firefox from [https://www.mozilla.org/en-US/firefox/new/ https://www.mozilla.org/en-US/firefox/new/].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://github.com/mozilla/geckodriver/releases correct version of geckodriver as per the [https://firefox-source-docs.mozilla.org/testing/geckodriver/Support.html documentation].&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Set up Moodle ====&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat&lt;br /&gt;
# Set the following in your Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $CFG-&amp;gt;behat_dataroot = &#039;/path/to/the/dataroot/you/created&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://127.0.0.1/path/to/your/site&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_prefix = &#039;beh_&#039;;&lt;br /&gt;
# We recommend that you also include the &amp;lt;tt&amp;gt;behat-browser-config&amp;lt;/tt&amp;gt; if you have not done so already.&lt;br /&gt;
    require_once(&#039;/path/to/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Notes about the behat_wwwroot =====&lt;br /&gt;
You will need to set the &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to your Moodle site, but it &#039;&#039;must&#039;&#039; use a different value to your &amp;lt;tt&amp;gt;$CFG-&amp;gt;wwwroot&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
One common way to do this is to use &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; for behat, but &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; for standard use. Alternatively you can add an additional hostname in your &amp;lt;tt&amp;gt;/etc/hosts&amp;lt;/tt&amp;gt; file and use this instead.&lt;br /&gt;
&lt;br /&gt;
If you use Docker, then you may be able to use &amp;lt;tt&amp;gt;host.docker.internal&amp;lt;/tt&amp;gt; where your site is hosted on the docker host&lt;br /&gt;
==== Configure Behat for Moodle ====&lt;br /&gt;
After setting your configuration, you can simply initialise behat:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will install all required Composer dependencies, install a new Moodle site, and put configuration in place&lt;br /&gt;
&lt;br /&gt;
When it finishes it will give advice on how to run Behat.&lt;br /&gt;
==== Run Behat tests ====&lt;br /&gt;
Before running behat, ensure that your Selenium server is running.&lt;br /&gt;
&lt;br /&gt;
The easiest way to run behat, and test that everything works is by simply running it using the command provided when you&lt;br /&gt;
initialised Behat. If you didn&#039;t make a note of it, you can just run the initialisation again.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will give you a command which you can then run. This command will run every behat scenario, which will take a considerable amount of time. This command will look a bit like this:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
To make this more useful you an combine it with flags, for example to only run certain &amp;lt;tt&amp;gt;tags&amp;lt;/tt&amp;gt; or for a specific Behat Feature file, or Scenario.&lt;br /&gt;
&lt;br /&gt;
To run all features/scenarios which are tagged with &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --tags=@mod_forum&lt;br /&gt;
To run one specific feature file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature&lt;br /&gt;
To run one specific scenario within a feature file:&lt;br /&gt;
    # To run the Scenario on line 38 of the file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature:38&lt;br /&gt;
To run one specific scenario by name:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --name=&amp;quot;As a teacher I can see my own response&amp;quot;&lt;br /&gt;
See the upstream documentation on Behat, and Gherkin filters for more information.&lt;br /&gt;
===== Running using a different browser =====&lt;br /&gt;
The default browser in Behat is &amp;lt;tt&amp;gt;Firefox&amp;lt;/tt&amp;gt;. To specify a different browser profile, you can add the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument. For example, to use Chrome in Headless mode:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
If you are using the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; utility, then you can use any profile listed in [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config|moodle-browser-config]]. Otherwise you can write your own browser profile configuration.&lt;br /&gt;
=== Advanced testing ===&lt;br /&gt;
==== Run tests without Selenium (chromedriver, geckodriver) ====&lt;br /&gt;
Historically, Behat required Selenium server, however browsers now make use of their own automation layer. For example, Firefox uses &amp;lt;tt&amp;gt;Geckodriver&amp;lt;/tt&amp;gt; and Chrome uses &amp;lt;tt&amp;gt;Chromedriver&amp;lt;/tt&amp;gt;. As a result the use of Selenium itself is now optional.&lt;br /&gt;
&lt;br /&gt;
The moodle-browser-config tool includes standard profiles to use these drivers directly and without the use of Selenium.&lt;br /&gt;
&lt;br /&gt;
To use the drivers directly, you must run the driver itself, for example to run &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ chromedriver&lt;br /&gt;
To run &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ geckodriver&lt;br /&gt;
Note: geckodriver runs on port 4444 by default. You cannot geckodriver at the same time as selenium.&lt;br /&gt;
&lt;br /&gt;
After starting your preferred browser, you can then run behat and specify an alternative profile, for example:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=geckodriver&lt;br /&gt;
==== Headless browsers ====&lt;br /&gt;
There are a number of reasons that you may prefer to use a headless browser. It can be particularly helpful if you are running the tests on a remote system, for example over SSH, or if you do not want to be interrupted by browsers popping up on your machine.&lt;br /&gt;
&lt;br /&gt;
The following headless profiles are some of those provided in the moodle-browser-config tool as standard:&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessfirefox&amp;lt;/tt&amp;gt; Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessgeckodriver&amp;lt;/tt&amp;gt; Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschrome&amp;lt;/tt&amp;gt; Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschromedriver&amp;lt;/tt&amp;gt; Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
These can be provided to the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument to behat:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
==== Parallel runs ====&lt;br /&gt;
Out-of-the-box, Moodle will configure Behat to run a single Moodle installation with all tests run in series. This is great for developer use where you are running a single test. or a small suite of tests. However this can be quite slow. A lot of time is spent waiting in Behat for things to happen. This may be for a page to load, for additional content to load, or even explicit waits because some interactions must be deliberately slowed down. As a result, a system running behat will not have a particularly high load most of the time.&lt;br /&gt;
&lt;br /&gt;
If you want to run a large suite of tests then it is possible to take advantage of the relatively low resource consumption by running several behat runners in parallel. This is commonly referred to as a &#039;&#039;&#039;Parallel run&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A parallel run of behat takes the same codebase and creates several installations rather than just a single Moodle installation. The behat Feature files are then grouped and allocated between each of the separate installations.&lt;br /&gt;
&lt;br /&gt;
To support this, each of the parallels runs needs its own:&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_dataroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* database&lt;br /&gt;
Rather than using an entirely separate database, the same database is actually used, but a different &amp;lt;tt&amp;gt;behat_prefix&amp;lt;/tt&amp;gt; is used to prefix the table names in the database differently.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
The Behat initialisation command is responsible for preparing Moodle to run a standard run. You&#039;ll have used this before when installing for a standard run:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
The same command can be used to prepare Moodle for a parallel run by specifying the &amp;lt;tt&amp;gt;--parallel&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-j&amp;lt;/tt&amp;gt; parameter:&lt;br /&gt;
    // Below command will initialise moodle to run 3 parallel tests.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3&lt;br /&gt;
This can be combined with the &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; flag to prepare Behat to run with all installed themes.&lt;br /&gt;
&lt;br /&gt;
A number of advanced options are also available but you are unlikely to need these:&lt;br /&gt;
# &amp;lt;tt&amp;gt;-m=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--maxruns=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; 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;
# &amp;lt;tt&amp;gt;--fromrun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;-o&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--optimize-runs&amp;lt;/tt&amp;gt; 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;
You can view details of all of these using the &amp;lt;tt&amp;gt;--help&amp;lt;/tt&amp;gt; flag to &amp;lt;tt&amp;gt;admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Running Parallel tests =====&lt;br /&gt;
You can use the Moodle behat runner to run all tests, including Standard runs. It is an intelligient wrapper around the standard &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; command which specifies the configuration file, and other required features.&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php&lt;br /&gt;
Many of the standard options and parameters that can be passed to &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; can also be passed to the Moodle runner, for example:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; Run tests which match the specified tags&lt;br /&gt;
# &amp;lt;tt&amp;gt;--name=&amp;quot;Scenario name&amp;quot;&amp;lt;/tt&amp;gt; Run a test matching the supplied scenario name&lt;br /&gt;
# &amp;lt;tt&amp;gt;--feature=&amp;quot;/path/to/test.feature&amp;quot;&amp;lt;/tt&amp;gt; Run a specific feature file.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; Features for specified theme will be executed.&lt;br /&gt;
The runner also includes a number of custom parameters relating to parallel runs:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--fromrun&amp;lt;/tt&amp;gt; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun&amp;lt;/tt&amp;gt; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; feature is particularly useful and can be used to replace a string in the command with the run number. This is useful when specifying output formats, and rerun files as noted below.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The following example demonstrates how Behat might be initialised with three parallel runs, to run on all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme&lt;br /&gt;
And then to run all tests matching the &amp;lt;tt&amp;gt;@tool_myplugin&amp;lt;/tt&amp;gt; tag, against the &amp;lt;tt&amp;gt;classic&amp;lt;/tt&amp;gt; theme:&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;classic&amp;quot;&lt;br /&gt;
===== Custom parameters 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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&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;
       // ...&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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;],&lt;br /&gt;
    ];&lt;br /&gt;
==== Tests filters ====&lt;br /&gt;
With the &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; or the &amp;lt;tt&amp;gt;-name&amp;lt;/tt&amp;gt; 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;
* &amp;lt;tt&amp;gt;@javascript&amp;lt;/tt&amp;gt;: All the tests that runs in a browser using Javascript; they require Selenium or the browser&#039;s own automation layer, as per [[Running acceptance test#Run%20tests%20without%20Selenium%20.28chromedriver.2C%20geckodriver.29|Running acceptance test#Run tests without Selenium .28chromedriver.2C geckodriver.29,]] to be running, otherwise an exception will be thrown.&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_file_upload&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_alert&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_switch_window&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; window&amp;lt;/tt&amp;gt; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_switch_iframe&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; iframe&amp;lt;/tt&amp;gt; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_cross_browser&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@componentname&amp;lt;/tt&amp;gt;: 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;
==== Output formats ====&lt;br /&gt;
Behat is able to output in a number of different formats, and to different locations as required.&lt;br /&gt;
&lt;br /&gt;
This can be achieved by specifying the &amp;lt;tt&amp;gt;--format&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;--out&amp;lt;/tt&amp;gt; parameters when running behat, for example:&lt;br /&gt;
    // Run behat, using the &#039;pretty&#039; format and outputting the value to /tmp/pretty.txt&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt&lt;br /&gt;
It is also possible to output to multiple formats simultaneously by repeating the arguments, for example:&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
The following output formats are supported:&lt;br /&gt;
# &amp;lt;tt&amp;gt;progress&amp;lt;/tt&amp;gt;: Prints one character per step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;pretty&amp;lt;/tt&amp;gt;: Prints the feature as is.&lt;br /&gt;
# &amp;lt;tt&amp;gt;junit&amp;lt;/tt&amp;gt;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_progress&amp;lt;/tt&amp;gt;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_list&amp;lt;/tt&amp;gt;: List all scenarios.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_stepcount&amp;lt;/tt&amp;gt;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_screenshot&amp;lt;/tt&amp;gt;: Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump image only&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump html only.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump both.&lt;br /&gt;
## &amp;lt;tt&amp;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;&amp;lt;/tt&amp;gt;&lt;br /&gt;
Note: 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;
When working with parallel runs, you may wish to have an output for each run. If you were to specify a standard path for this then each of the parallel runs would overwrite the others file. The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; option allows this to be handled:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
In this example, the &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; argument is provided with a value of &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt;. Anywhere that &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt; appears in the command it will be replaced with the run number. The above command will generate a set of commands like:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun1/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_1.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun2/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_2.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun3/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_3.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
==== Rerun failed scenarios ====&lt;br /&gt;
With slow systems or parallel run you may experience see some random failures. These may happen when your system is too slow, when it is too fast, or where a page depends on external dependencies.&lt;br /&gt;
&lt;br /&gt;
To help with this it is possible to rerun any failed scenarios using the &amp;lt;tt&amp;gt;--rerun&amp;lt;/tt&amp;gt; option to Behat.&lt;br /&gt;
&lt;br /&gt;
The following example runs Behat with the rerun option:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
If any single test fails then the command will return a non-zero exit code. Running the same command again will mean that only failed scenarios are run.&lt;br /&gt;
&lt;br /&gt;
For a parallel run it can be called as follows:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
The Moodle behat runner also includes an &amp;lt;tt&amp;gt;--auto-rerun&amp;lt;/tt&amp;gt; option which will automatically rerun failed scenarios exactly once.&lt;br /&gt;
==== Running behat with specified theme ====&lt;br /&gt;
Behat can be run with any installed theme, but it must be initialised with the &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; option first.&lt;br /&gt;
&lt;br /&gt;
After configuring the theme can be specified using the &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; parameter.&lt;br /&gt;
&lt;br /&gt;
Note: The default theme in Moodle (boost) has the suite name &amp;lt;tt&amp;gt;default&amp;lt;/tt&amp;gt;.&lt;br /&gt;
    // Initialise Behat for all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --add-core-features-to-theme&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against all initalised themes:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the default theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=default&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the &#039;classic&#039; theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=classic&lt;br /&gt;
==== Using Docker to start selenium server ====&lt;br /&gt;
There are a wide range of docker images available which contain a browser with Selenium. You will probably be using the official SeleniumHQ images unless you have a specific reason not to.&lt;br /&gt;
&lt;br /&gt;
The complete list of available SeleniumHQ images is available at [https://hub.docker.com/u/selenium/ https://hub.docker.com/u/selenium/].&lt;br /&gt;
&lt;br /&gt;
Moodle uses the &#039;&#039;standalone&#039;&#039; version and any recent version with version 3.141.59 or higher is supported.&lt;br /&gt;
&lt;br /&gt;
For any test which uploads files, for example when interacting with the filepicker, you must also ensure that the Moodle directory is mounted as on your local filesystem.&lt;br /&gt;
&lt;br /&gt;
An example usage is:&lt;br /&gt;
    $ docker run -d -p 4444:4444 -v `pwd`:`pwd` selenium/standalone-firefox:latest&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In the Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; file you must change the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to an address that can be reached from within the docker image.&lt;br /&gt;
&lt;br /&gt;
You cannot use &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; as this will be the IP address of the docker container itself.&lt;br /&gt;
&lt;br /&gt;
On some more recent versions of Docker you can use &amp;lt;tt&amp;gt;http://host.docker.internal/&amp;lt;/tt&amp;gt;, for example if my site is located at &amp;lt;tt&amp;gt;/sm&amp;lt;/tt&amp;gt; on my development machine, I can set the following:&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://host.docker.internal/sm&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Manually configuring other browsers =====&lt;br /&gt;
If you would prefer not to use the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool but still wish to specify different browsers then you can do so using the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_profiles&amp;lt;/tt&amp;gt; array. Each key/value pair contains a profile name, and the configuration for that profile. For example:&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;chrome&#039; =&amp;gt; [&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;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
=== Increasing timeouts ===&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it may just mean that your browser did not finish generating parts of the UI before behat tried was finished.&lt;br /&gt;
&lt;br /&gt;
If you find that this happens regularly on different scenarios then you may want to increase the timeout:&lt;br /&gt;
    $CFG-&amp;gt;behat_increasetimeout = 2;&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: This is usually an indicator that your development machine is not well tuned. A better option would be to find out where the bottleneck is. This is usually the database configuration.&lt;br /&gt;
&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;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/syntaxhighlight&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;
=== Tests are failing ===&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;
=== The tests are failing, and the error message is completely useless ===&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;
=== 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;
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;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&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;
&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;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Quick setup and testing using moodle-docker ===&lt;br /&gt;
This is a quick guide to help locally pass tests for your developments, before submitting them:&lt;br /&gt;
# Set up a default Moodle install using [https://github.com/moodlehq/moodle-docker moodle-docker], with the database and Moodle version of your choice. See its README for more details. This will start some docker containers.&lt;br /&gt;
# Initialize behat to start testing with this command, from the webserver container: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# Run the behat test of your choice, from the webserver container. For instance: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/behat --config /var/www/behatdata/behatrun/behat/behat.yml --tags tool_task&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And you&#039;ll see something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
Moodle 4.0dev (Build: 20210507), 0b47ea0a44a092f9000729ca7b15fff23111538b&lt;br /&gt;
Php: 7.3.26, mysqli: 5.7.21, OS: Linux 5.4.0-66-generic x86_64&lt;br /&gt;
Run optional tests:&lt;br /&gt;
&lt;br /&gt;
Accessibility: No&lt;br /&gt;
Server OS &amp;quot;Linux&amp;quot;, Browser: &amp;quot;firefox&amp;quot;&lt;br /&gt;
Started at 09-05-2021, 06:00&lt;br /&gt;
...................................................................... 70&lt;br /&gt;
...............................&lt;br /&gt;
12 scenarios (12 passed)&lt;br /&gt;
101 steps (101 passed)&lt;br /&gt;
2m53.27s (47.61Mb)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Information to re-home ==&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Disable behat context or features to run in theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&lt;br /&gt;
To disable specific contexts and features from being executed by a specific theme/suite you can create a &amp;lt;tt&amp;gt;/theme/{MYTHEME}/tests/behat/blacklist.json&amp;lt;/tt&amp;gt; file with following format.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
The above will:&lt;br /&gt;
# disable the use of step_definitions from &amp;lt;tt&amp;gt;behat_grade&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;behat_navigation&amp;lt;/tt&amp;gt; while running theme suite; and&lt;br /&gt;
# disable running of scenarios in &amp;lt;tt&amp;gt;auth/tests/behat/login.feature&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;grade/tests/behat/grade_hidden_items.feature&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Write new tests and behat methods ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&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;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=61558</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=61558"/>
		<updated>2021-12-07T15:20:04Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Parallel runs */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle uses [https://behat.org Behat], a php framework for automated functional testing, as part of a suite of testing tools.&lt;br /&gt;
&lt;br /&gt;
Behat takes a set of Features, Scenarios, and Steps, and uses these to step through actions and test results using a real web browser. This is possible thanks to a protocol implemented in most modern web browsers called Webdriver.&lt;br /&gt;
&lt;br /&gt;
This documentation covers how to run Behat tests within Moodle, including requirements, setup, useful tips and tricks, and basic troubleshooting.&lt;br /&gt;
== Requirements ==&lt;br /&gt;
# Any recent OS with [[Releases|supported version of Moodle]] installed&lt;br /&gt;
# A recent browser (We support Firefox, Chrome as standard, but other browsers are possible)&lt;br /&gt;
# The WebDriver implementation for your browser&lt;br /&gt;
# A recent version of Selenium (Optional, but recommended)&lt;br /&gt;
# A recent Java Runtime Environment (Required if using Selenium)&lt;br /&gt;
=== Recommended extras ===&lt;br /&gt;
Some extra software is also recommended for testing with Behat.&lt;br /&gt;
==== Pre-configured browser profiles: moodle-browser-config ====&lt;br /&gt;
Available for [[Releases|all supported versions of Moodle]], the [https://github.com/andrewnicols/moodle-browser-config moodle-browser-config] is a recommended inclusion for Behat. This configuration tooling provides a range of standard browser profiles for testing.&lt;br /&gt;
&lt;br /&gt;
Note: In future, the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool may be included as composer dependency to Moodle but this is currently not the case.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
# Check out the moodle-browser-config repository:&lt;br /&gt;
    // Change directory to your git root, for example:&lt;br /&gt;
    $ cd ~/git&lt;br /&gt;
    // Clone the moodle-browser-config repository&lt;br /&gt;
    git clone https://github.com/andrewnicols/moodle-browser-config&lt;br /&gt;
# Open your Moodle installation&#039;s &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; in your preferred editor, and require the tool&#039;s init.php:&lt;br /&gt;
    require_once(&#039;/path/to/your/git/dir/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Provided profiles =====&lt;br /&gt;
The full list of profiles which are included with &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; are provided in its [https://github.com/andrewnicols/moodle-browser-config own documentation].&lt;br /&gt;
&lt;br /&gt;
The following is a summary of the profiles that most users may be interested in.&lt;br /&gt;
&lt;br /&gt;
You can also provide your own custom profiles, including for remote services such as Browserstack, and Saucelabs, as&lt;br /&gt;
well as for other browsers supporting the W3C Webdriver specification.&lt;br /&gt;
&lt;br /&gt;
Please note that &amp;lt;tt&amp;gt;Safari&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;Safaridriver&amp;lt;/tt&amp;gt; are not currently supported as they do not meet the W3C WebDriver specification.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Profile name&lt;br /&gt;
! Description&lt;br /&gt;
! Uses Selenium?&lt;br /&gt;
! Displays GUI?&lt;br /&gt;
|-&lt;br /&gt;
| firefox&lt;br /&gt;
| Use Firefox via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessfirefox&lt;br /&gt;
| Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| geckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessgeckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chrome&lt;br /&gt;
| Use Chrome via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschrome&lt;br /&gt;
| Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edge&lt;br /&gt;
| Use Edge via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedge&lt;br /&gt;
| Use Edge via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|}&lt;br /&gt;
==== chromedriver-wrapper ====&lt;br /&gt;
When using Google Chrome, you must use the correct version of the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; browser driver for the version of Chrome that you use.&lt;br /&gt;
&lt;br /&gt;
Since Google Chrome automatically updates on a regular basis, you will need to regularly upgrade your driver to match the version of Chrome that you are using.&lt;br /&gt;
&lt;br /&gt;
To make this easier, a &amp;lt;tt&amp;gt;chromedriver-wrapper&amp;lt;/tt&amp;gt; utility has been written. This inspects the version of Chrome that is in your path, and downloads the correct version of &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; for that version, before starting it.&lt;br /&gt;
&lt;br /&gt;
Installation instructinos can be found at [https://github.com/andrewnicols/chromedriver-wrapper https://github.com/andrewnicols/chromedriver-wrapper].&lt;br /&gt;
== Getting started (basic) ==&lt;br /&gt;
This is a quick walk through to get Behat running for the first time.&lt;br /&gt;
=== Setting up ===&lt;br /&gt;
There are a number of ways of configuring Behat on Moodle. This is one of the simplest.&lt;br /&gt;
&lt;br /&gt;
These notes assume that you have already installed a supported Java Runtime Environment, and the [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config]] tool.&lt;br /&gt;
==== Setting up Selenium ====&lt;br /&gt;
Generally we recommend use of Selenium, though this is not a fixed requirement. You can use the browser&#039;s driver implementation directly but this is harder to setup for the first time.&lt;br /&gt;
&lt;br /&gt;
Selenium is written in Java, and requires a recent version of the JRE to run. Please ensure that you have this installed prior to starting.&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.9.5 / 3.10.2 / 3.11.0 Moodle will work with any modern version of Selenium. At time of writing that is &amp;lt;tt&amp;gt;3.141.59&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;4.0.0-beta-3&amp;lt;/tt&amp;gt;.&lt;br /&gt;
# Download the Selenium Server (Grid) from [https://www.selenium.dev/downloads/ https://www.selenium.dev/downloads/]. This is a single JAR file, put it anywhere handy.&lt;br /&gt;
# Start Selenium&lt;br /&gt;
    # Version 3.141.59:&lt;br /&gt;
    $ java -jar selenium-server-standalone-3.141.59.jar&lt;br /&gt;
    &lt;br /&gt;
    # Version 4.0.0 and later:&lt;br /&gt;
    $ java -jar selenium-server-4.0.0-beta-3.jar standalone&lt;br /&gt;
You can optionally specify a number of settings, depending on the version of Selenium that you are using, including the port to run on.&lt;br /&gt;
See the help for the version of Selenium that you are using.&lt;br /&gt;
==== Setting up your browsers ====&lt;br /&gt;
Selenium is just an intelligient wrapper to start, and manage your browser sessions. It doesn&#039;t actually include any web browsers itself.&lt;br /&gt;
&lt;br /&gt;
Moodle HQ run all behat tests against both Firefox and Chrome multiple times per day. Other combinations, including Microsoft Edge, are also supported.&lt;br /&gt;
&lt;br /&gt;
To use Behat, you will need a recent version of your preferred browser, as well as a &amp;lt;tt&amp;gt;driver&amp;lt;/tt&amp;gt; for that browser. The driver is responsible for communication between Selenium (or Moodle directly) and the browser.&lt;br /&gt;
&lt;br /&gt;
Both the browser, and its driver, must be placed inside your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt; - this may be somewhere like &amp;lt;tt&amp;gt;/usr/bin&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;/usr/local/bin&amp;lt;/tt&amp;gt;, or perhaps a user bin directory like &amp;lt;tt&amp;gt;~/bin&amp;lt;/tt&amp;gt; which is present in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Chrome =====&lt;br /&gt;
You can download Google Chrome from [https://www.google.com.au/chrome https://www.google.com.au/chrome].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://chromedriver.chromium.org/downloads correct version of the chromedriver] as per the&lt;br /&gt;
documentation. Alternatively you can make use of the [[Running_acceptance_test#chromedriver-wrapper|chromedriver-wrapper utility]] noted in the Recommended extras sections.&lt;br /&gt;
&lt;br /&gt;
Either the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;, or the &amp;lt;tt&amp;gt;chromedriver-wrapper/bin&amp;lt;/tt&amp;gt; folder must be in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
===== Firefox =====&lt;br /&gt;
You can download Mozilla Firefox from [https://www.mozilla.org/en-US/firefox/new/ https://www.mozilla.org/en-US/firefox/new/].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://github.com/mozilla/geckodriver/releases correct version of geckodriver as per the [https://firefox-source-docs.mozilla.org/testing/geckodriver/Support.html documentation].&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Set up Moodle ====&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat&lt;br /&gt;
# Set the following in your Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $CFG-&amp;gt;behat_dataroot = &#039;/path/to/the/dataroot/you/created&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://127.0.0.1/path/to/your/site&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_prefix = &#039;beh_&#039;;&lt;br /&gt;
# We recommend that you also include the &amp;lt;tt&amp;gt;behat-browser-config&amp;lt;/tt&amp;gt; if you have not done so already.&lt;br /&gt;
    require_once(&#039;/path/to/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Notes about the behat_wwwroot =====&lt;br /&gt;
You will need to set the &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to your Moodle site, but it &#039;&#039;must&#039;&#039; use a different value to your &amp;lt;tt&amp;gt;$CFG-&amp;gt;wwwroot&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
One common way to do this is to use &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; for behat, but &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; for standard use. Alternatively you can add an additional hostname in your &amp;lt;tt&amp;gt;/etc/hosts&amp;lt;/tt&amp;gt; file and use this instead.&lt;br /&gt;
&lt;br /&gt;
If you use Docker, then you may be able to use &amp;lt;tt&amp;gt;host.docker.internal&amp;lt;/tt&amp;gt; where your site is hosted on the docker host&lt;br /&gt;
==== Configure Behat for Moodle ====&lt;br /&gt;
After setting your configuration, you can simply initialise behat:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will install all required Composer dependencies, install a new Moodle site, and put configuration in place&lt;br /&gt;
&lt;br /&gt;
When it finishes it will give advice on how to run Behat.&lt;br /&gt;
==== Run Behat tests ====&lt;br /&gt;
Before running behat, ensure that your Selenium server is running.&lt;br /&gt;
&lt;br /&gt;
The easiest way to run behat, and test that everything works is by simply running it using the command provided when you&lt;br /&gt;
initialised Behat. If you didn&#039;t make a note of it, you can just run the initialisation again.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will give you a command which you can then run. This command will run every behat scenario, which will take a considerable amount of time. This command will look a bit like this:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
To make this more useful you an combine it with flags, for example to only run certain &amp;lt;tt&amp;gt;tags&amp;lt;/tt&amp;gt; or for a specific Behat Feature file, or Scenario.&lt;br /&gt;
&lt;br /&gt;
To run all features/scenarios which are tagged with &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --tags=@mod_forum&lt;br /&gt;
To run one specific feature file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature&lt;br /&gt;
To run one specific scenario within a feature file:&lt;br /&gt;
    # To run the Scenario on line 38 of the file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature:38&lt;br /&gt;
To run one specific scenario by name:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --name=&amp;quot;As a teacher I can see my own response&amp;quot;&lt;br /&gt;
See the upstream documentation on Behat, and Gherkin filters for more information.&lt;br /&gt;
===== Running using a different browser =====&lt;br /&gt;
The default browser in Behat is &amp;lt;tt&amp;gt;Firefox&amp;lt;/tt&amp;gt;. To specify a different browser profile, you can add the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument. For example, to use Chrome in Headless mode:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
If you are using the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; utility, then you can use any profile listed in [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config|moodle-browser-config]]. Otherwise you can write your own browser profile configuration.&lt;br /&gt;
=== Advanced testing ===&lt;br /&gt;
==== Run tests without Selenium (chromedriver, geckodriver) ====&lt;br /&gt;
Historically, Behat required Selenium server, however browsers now make use of their own automation layer. For example, Firefox uses &amp;lt;tt&amp;gt;Geckodriver&amp;lt;/tt&amp;gt; and Chrome uses &amp;lt;tt&amp;gt;Chromedriver&amp;lt;/tt&amp;gt;. As a result the use of Selenium itself is now optional.&lt;br /&gt;
&lt;br /&gt;
The moodle-browser-config tool includes standard profiles to use these drivers directly and without the use of Selenium.&lt;br /&gt;
&lt;br /&gt;
To use the drivers directly, you must run the driver itself, for example to run &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ chromedriver&lt;br /&gt;
To run &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ geckodriver&lt;br /&gt;
Note: geckodriver runs on port 4444 by default. You cannot geckodriver at the same time as selenium.&lt;br /&gt;
&lt;br /&gt;
After starting your preferred browser, you can then run behat and specify an alternative profile, for example:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=geckodriver&lt;br /&gt;
==== Headless browsers ====&lt;br /&gt;
There are a number of reasons that you may prefer to use a headless browser. It can be particularly helpful if you are running the tests on a remote system, for example over SSH, or if you do not want to be interrupted by browsers popping up on your machine.&lt;br /&gt;
&lt;br /&gt;
The following headless profiles are some of those provided in the moodle-browser-config tool as standard:&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessfirefox&amp;lt;/tt&amp;gt; Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessgeckodriver&amp;lt;/tt&amp;gt; Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschrome&amp;lt;/tt&amp;gt; Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschromedriver&amp;lt;/tt&amp;gt; Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
These can be provided to the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument to behat:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
==== Parallel runs ====&lt;br /&gt;
Out-of-the-box, Moodle will configure Behat to run a single Moodle installation with all tests run in series. This is great for developer use where you are running a single test. or a small suite of tests. However this can be quite slow. A lot of time is spent waiting in Behat for things to happen. This may be for a page to load, for additional content to load, or even explicit waits because some interactions must be deliberately slowed down. As a result, a system running behat will not have a particularly high load most of the time.&lt;br /&gt;
&lt;br /&gt;
If you want to run a large suite of tests then it is possible to take advantage of the relatively low resource consumption by running several behat runners in parallel. This is commonly referred to as a &#039;&#039;&#039;Parallel run&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A parallel run of behat takes the same codebase and creates several installations rather than just a single Moodle installation. The behat Feature files are then grouped and allocated between each of the separate installations.&lt;br /&gt;
&lt;br /&gt;
To support this, each of the parallels runs needs its own:&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_dataroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* database&lt;br /&gt;
Rather than using an entirely separate database, the same database is actually used, but a different &amp;lt;tt&amp;gt;behat_prefix&amp;lt;/tt&amp;gt; is used to prefix the table names in the database differently.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
The Behat initialisation command is responsible for preparing Moodle to run a standard run. You&#039;ll have used this before when installing for a standard run:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
The same command can be used to prepare Moodle for a parallel run by specifying the &amp;lt;tt&amp;gt;--parallel&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-j&amp;lt;/tt&amp;gt; parameter:&lt;br /&gt;
    // Below command will initialise moodle to run 3 parallel tests.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3&lt;br /&gt;
This can be combined with the &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; flag to prepare Behat to run with all installed themes.&lt;br /&gt;
&lt;br /&gt;
A number of advanced options are also available but you are unlikely to need these:&lt;br /&gt;
# &amp;lt;tt&amp;gt;-m=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--maxruns=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; 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;
# &amp;lt;tt&amp;gt;--fromrun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;-o&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--optimize-runs&amp;lt;/tt&amp;gt; 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;
You can view details of all of these using the &amp;lt;tt&amp;gt;--help&amp;lt;/tt&amp;gt; flag to &amp;lt;tt&amp;gt;admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Running Parallel tests =====&lt;br /&gt;
You can use the Moodle behat runner to run all tests, including Standard runs. It is an intelligient wrapper around the standard &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; command which specifies the configuration file, and other required features.&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php&lt;br /&gt;
Many of the standard options and parameters that can be passed to &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; can also be passed to the Moodle runner, for example:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; Run tests which match the specified tags&lt;br /&gt;
# &amp;lt;tt&amp;gt;--name=&amp;quot;Scenario name&amp;quot;&amp;lt;/tt&amp;gt; Run a test matching the supplied scenario name&lt;br /&gt;
# &amp;lt;tt&amp;gt;--feature=&amp;quot;/path/to/test.feature&amp;quot;&amp;lt;/tt&amp;gt; Run a specific feature file.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; Features for specified theme will be executed.&lt;br /&gt;
The runner also includes a number of custom parameters relating to parallel runs:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--fromrun&amp;lt;/tt&amp;gt; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun&amp;lt;/tt&amp;gt; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; feature is particularly useful and can be used to replace a string in the command with the run number. This is useful when specifying output formats, and rerun files as noted below.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The following example demonstrates how Behat might be initialised with three parallel runs, to run on all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme&lt;br /&gt;
And then to run all tests matching the &amp;lt;tt&amp;gt;@tool_myplugin&amp;lt;/tt&amp;gt; tag, against the &amp;lt;tt&amp;gt;classic&amp;lt;/tt&amp;gt; theme:&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;classic&amp;quot;&lt;br /&gt;
===== Custom parameters 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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&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;
       // ...&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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;],&lt;br /&gt;
    ];&lt;br /&gt;
==== Tests filters ====&lt;br /&gt;
With the &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; or the &amp;lt;tt&amp;gt;-name&amp;lt;/tt&amp;gt; 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;
* &amp;lt;tt&amp;gt;@javascript&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_file_upload&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_alert&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_switch_window&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; window&amp;lt;/tt&amp;gt; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_switch_iframe&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; iframe&amp;lt;/tt&amp;gt; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_cross_browser&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@componentname&amp;lt;/tt&amp;gt;: 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;
==== Output formats ====&lt;br /&gt;
Behat is able to output in a number of different formats, and to different locations as required.&lt;br /&gt;
&lt;br /&gt;
This can be achieved by specifying the &amp;lt;tt&amp;gt;--format&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;--out&amp;lt;/tt&amp;gt; parameters when running behat, for example:&lt;br /&gt;
    // Run behat, using the &#039;pretty&#039; format and outputting the value to /tmp/pretty.txt&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt&lt;br /&gt;
It is also possible to output to multiple formats simultaneously by repeating the arguments, for example:&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
The following output formats are supported:&lt;br /&gt;
# &amp;lt;tt&amp;gt;progress&amp;lt;/tt&amp;gt;: Prints one character per step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;pretty&amp;lt;/tt&amp;gt;: Prints the feature as is.&lt;br /&gt;
# &amp;lt;tt&amp;gt;junit&amp;lt;/tt&amp;gt;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_progress&amp;lt;/tt&amp;gt;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_list&amp;lt;/tt&amp;gt;: List all scenarios.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_stepcount&amp;lt;/tt&amp;gt;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_screenshot&amp;lt;/tt&amp;gt;: Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump image only&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump html only.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump both.&lt;br /&gt;
## &amp;lt;tt&amp;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;&amp;lt;/tt&amp;gt;&lt;br /&gt;
Note: 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;
When working with parallel runs, you may wish to have an output for each run. If you were to specify a standard path for this then each of the parallel runs would overwrite the others file. The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; option allows this to be handled:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
In this example, the &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; argument is provided with a value of &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt;. Anywhere that &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt; appears in the command it will be replaced with the run number. The above command will generate a set of commands like:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun1/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_1.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun2/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_2.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun3/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_3.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
==== Rerun failed scenarios ====&lt;br /&gt;
With slow systems or parallel run you may experience see some random failures. These may happen when your system is too slow, when it is too fast, or where a page depends on external dependencies.&lt;br /&gt;
&lt;br /&gt;
To help with this it is possible to rerun any failed scenarios using the &amp;lt;tt&amp;gt;--rerun&amp;lt;/tt&amp;gt; option to Behat.&lt;br /&gt;
&lt;br /&gt;
The following example runs Behat with the rerun option:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
If any single test fails then the command will return a non-zero exit code. Running the same command again will mean that only failed scenarios are run.&lt;br /&gt;
&lt;br /&gt;
For a parallel run it can be called as follows:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
The Moodle behat runner also includes an &amp;lt;tt&amp;gt;--auto-rerun&amp;lt;/tt&amp;gt; option which will automatically rerun failed scenarios exactly once.&lt;br /&gt;
==== Running behat with specified theme ====&lt;br /&gt;
Behat can be run with any installed theme, but it must be initialised with the &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; option first.&lt;br /&gt;
&lt;br /&gt;
After configuring the theme can be specified using the &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; parameter.&lt;br /&gt;
&lt;br /&gt;
Note: The default theme in Moodle (boost) has the suite name &amp;lt;tt&amp;gt;default&amp;lt;/tt&amp;gt;.&lt;br /&gt;
    // Initialise Behat for all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --add-core-features-to-theme&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against all initalised themes:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the default theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=default&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the &#039;classic&#039; theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=classic&lt;br /&gt;
==== Using Docker to start selenium server ====&lt;br /&gt;
There are a wide range of docker images available which contain a browser with Selenium. You will probably be using the official SeleniumHQ images unless you have a specific reason not to.&lt;br /&gt;
&lt;br /&gt;
The complete list of available SeleniumHQ images is available at [https://hub.docker.com/u/selenium/ https://hub.docker.com/u/selenium/].&lt;br /&gt;
&lt;br /&gt;
Moodle uses the &#039;&#039;standalone&#039;&#039; version and any recent version with version 3.141.59 or higher is supported.&lt;br /&gt;
&lt;br /&gt;
For any test which uploads files, for example when interacting with the filepicker, you must also ensure that the Moodle directory is mounted as on your local filesystem.&lt;br /&gt;
&lt;br /&gt;
An example usage is:&lt;br /&gt;
    $ docker run -d -p 4444:4444 -v `pwd`:`pwd` selenium/standalone-firefox:latest&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In the Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; file you must change the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to an address that can be reached from within the docker image.&lt;br /&gt;
&lt;br /&gt;
You cannot use &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; as this will be the IP address of the docker container itself.&lt;br /&gt;
&lt;br /&gt;
On some more recent versions of Docker you can use &amp;lt;tt&amp;gt;http://host.docker.internal/&amp;lt;/tt&amp;gt;, for example if my site is located at &amp;lt;tt&amp;gt;/sm&amp;lt;/tt&amp;gt; on my development machine, I can set the following:&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://host.docker.internal/sm&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Manually configuring other browsers =====&lt;br /&gt;
If you would prefer not to use the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool but still wish to specify different browsers then you can do so using the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_profiles&amp;lt;/tt&amp;gt; array. Each key/value pair contains a profile name, and the configuration for that profile. For example:&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;chrome&#039; =&amp;gt; [&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;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
=== Increasing timeouts ===&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it may just mean that your browser did not finish generating parts of the UI before behat tried was finished.&lt;br /&gt;
&lt;br /&gt;
If you find that this happens regularly on different scenarios then you may want to increase the timeout:&lt;br /&gt;
    $CFG-&amp;gt;behat_increasetimeout = 2;&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: This is usually an indicator that your development machine is not well tuned. A better option would be to find out where the bottleneck is. This is usually the database configuration.&lt;br /&gt;
&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;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/syntaxhighlight&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;
=== Tests are failing ===&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;
=== The tests are failing, and the error message is completely useless ===&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;
=== 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;
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;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&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;
&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;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Quick setup and testing using moodle-docker ===&lt;br /&gt;
This is a quick guide to help locally pass tests for your developments, before submitting them:&lt;br /&gt;
# Set up a default Moodle install using [https://github.com/moodlehq/moodle-docker moodle-docker], with the database and Moodle version of your choice. See its README for more details. This will start some docker containers.&lt;br /&gt;
# Initialize behat to start testing with this command, from the webserver container: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# Run the behat test of your choice, from the webserver container. For instance: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/behat --config /var/www/behatdata/behatrun/behat/behat.yml --tags tool_task&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And you&#039;ll see something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
Moodle 4.0dev (Build: 20210507), 0b47ea0a44a092f9000729ca7b15fff23111538b&lt;br /&gt;
Php: 7.3.26, mysqli: 5.7.21, OS: Linux 5.4.0-66-generic x86_64&lt;br /&gt;
Run optional tests:&lt;br /&gt;
&lt;br /&gt;
Accessibility: No&lt;br /&gt;
Server OS &amp;quot;Linux&amp;quot;, Browser: &amp;quot;firefox&amp;quot;&lt;br /&gt;
Started at 09-05-2021, 06:00&lt;br /&gt;
...................................................................... 70&lt;br /&gt;
...............................&lt;br /&gt;
12 scenarios (12 passed)&lt;br /&gt;
101 steps (101 passed)&lt;br /&gt;
2m53.27s (47.61Mb)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Information to re-home ==&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Disable behat context or features to run in theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&lt;br /&gt;
To disable specific contexts and features from being executed by a specific theme/suite you can create a &amp;lt;tt&amp;gt;/theme/{MYTHEME}/tests/behat/blacklist.json&amp;lt;/tt&amp;gt; file with following format.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
The above will:&lt;br /&gt;
# disable the use of step_definitions from &amp;lt;tt&amp;gt;behat_grade&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;behat_navigation&amp;lt;/tt&amp;gt; while running theme suite; and&lt;br /&gt;
# disable running of scenarios in &amp;lt;tt&amp;gt;auth/tests/behat/login.feature&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;grade/tests/behat/grade_hidden_items.feature&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Write new tests and behat methods ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&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;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=61557</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=61557"/>
		<updated>2021-12-07T15:12:36Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Chrome */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle uses [https://behat.org Behat], a php framework for automated functional testing, as part of a suite of testing tools.&lt;br /&gt;
&lt;br /&gt;
Behat takes a set of Features, Scenarios, and Steps, and uses these to step through actions and test results using a real web browser. This is possible thanks to a protocol implemented in most modern web browsers called Webdriver.&lt;br /&gt;
&lt;br /&gt;
This documentation covers how to run Behat tests within Moodle, including requirements, setup, useful tips and tricks, and basic troubleshooting.&lt;br /&gt;
== Requirements ==&lt;br /&gt;
# Any recent OS with [[Releases|supported version of Moodle]] installed&lt;br /&gt;
# A recent browser (We support Firefox, Chrome as standard, but other browsers are possible)&lt;br /&gt;
# The WebDriver implementation for your browser&lt;br /&gt;
# A recent version of Selenium (Optional, but recommended)&lt;br /&gt;
# A recent Java Runtime Environment (Required if using Selenium)&lt;br /&gt;
=== Recommended extras ===&lt;br /&gt;
Some extra software is also recommended for testing with Behat.&lt;br /&gt;
==== Pre-configured browser profiles: moodle-browser-config ====&lt;br /&gt;
Available for [[Releases|all supported versions of Moodle]], the [https://github.com/andrewnicols/moodle-browser-config moodle-browser-config] is a recommended inclusion for Behat. This configuration tooling provides a range of standard browser profiles for testing.&lt;br /&gt;
&lt;br /&gt;
Note: In future, the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool may be included as composer dependency to Moodle but this is currently not the case.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
# Check out the moodle-browser-config repository:&lt;br /&gt;
    // Change directory to your git root, for example:&lt;br /&gt;
    $ cd ~/git&lt;br /&gt;
    // Clone the moodle-browser-config repository&lt;br /&gt;
    git clone https://github.com/andrewnicols/moodle-browser-config&lt;br /&gt;
# Open your Moodle installation&#039;s &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; in your preferred editor, and require the tool&#039;s init.php:&lt;br /&gt;
    require_once(&#039;/path/to/your/git/dir/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Provided profiles =====&lt;br /&gt;
The full list of profiles which are included with &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; are provided in its [https://github.com/andrewnicols/moodle-browser-config own documentation].&lt;br /&gt;
&lt;br /&gt;
The following is a summary of the profiles that most users may be interested in.&lt;br /&gt;
&lt;br /&gt;
You can also provide your own custom profiles, including for remote services such as Browserstack, and Saucelabs, as&lt;br /&gt;
well as for other browsers supporting the W3C Webdriver specification.&lt;br /&gt;
&lt;br /&gt;
Please note that &amp;lt;tt&amp;gt;Safari&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;Safaridriver&amp;lt;/tt&amp;gt; are not currently supported as they do not meet the W3C WebDriver specification.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Profile name&lt;br /&gt;
! Description&lt;br /&gt;
! Uses Selenium?&lt;br /&gt;
! Displays GUI?&lt;br /&gt;
|-&lt;br /&gt;
| firefox&lt;br /&gt;
| Use Firefox via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessfirefox&lt;br /&gt;
| Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| geckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessgeckodriver&lt;br /&gt;
| Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chrome&lt;br /&gt;
| Use Chrome via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschrome&lt;br /&gt;
| Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| chromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlesschromedriver&lt;br /&gt;
| Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edge&lt;br /&gt;
| Use Edge via Selenium&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedge&lt;br /&gt;
| Use Edge via Selenium, without displaying the GUI&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
| edgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
| headlessedgedriver&lt;br /&gt;
| Use Edge with Edgedriver directly, without displaying the GUI&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
|}&lt;br /&gt;
==== chromedriver-wrapper ====&lt;br /&gt;
When using Google Chrome, you must use the correct version of the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; browser driver for the version of Chrome that you use.&lt;br /&gt;
&lt;br /&gt;
Since Google Chrome automatically updates on a regular basis, you will need to regularly upgrade your driver to match the version of Chrome that you are using.&lt;br /&gt;
&lt;br /&gt;
To make this easier, a &amp;lt;tt&amp;gt;chromedriver-wrapper&amp;lt;/tt&amp;gt; utility has been written. This inspects the version of Chrome that is in your path, and downloads the correct version of &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; for that version, before starting it.&lt;br /&gt;
&lt;br /&gt;
Installation instructinos can be found at [https://github.com/andrewnicols/chromedriver-wrapper https://github.com/andrewnicols/chromedriver-wrapper].&lt;br /&gt;
== Getting started (basic) ==&lt;br /&gt;
This is a quick walk through to get Behat running for the first time.&lt;br /&gt;
=== Setting up ===&lt;br /&gt;
There are a number of ways of configuring Behat on Moodle. This is one of the simplest.&lt;br /&gt;
&lt;br /&gt;
These notes assume that you have already installed a supported Java Runtime Environment, and the [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config]] tool.&lt;br /&gt;
==== Setting up Selenium ====&lt;br /&gt;
Generally we recommend use of Selenium, though this is not a fixed requirement. You can use the browser&#039;s driver implementation directly but this is harder to setup for the first time.&lt;br /&gt;
&lt;br /&gt;
Selenium is written in Java, and requires a recent version of the JRE to run. Please ensure that you have this installed prior to starting.&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.9.5 / 3.10.2 / 3.11.0 Moodle will work with any modern version of Selenium. At time of writing that is &amp;lt;tt&amp;gt;3.141.59&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;4.0.0-beta-3&amp;lt;/tt&amp;gt;.&lt;br /&gt;
# Download the Selenium Server (Grid) from [https://www.selenium.dev/downloads/ https://www.selenium.dev/downloads/]. This is a single JAR file, put it anywhere handy.&lt;br /&gt;
# Start Selenium&lt;br /&gt;
    # Version 3.141.59:&lt;br /&gt;
    $ java -jar selenium-server-standalone-3.141.59.jar&lt;br /&gt;
    &lt;br /&gt;
    # Version 4.0.0 and later:&lt;br /&gt;
    $ java -jar selenium-server-4.0.0-beta-3.jar standalone&lt;br /&gt;
You can optionally specify a number of settings, depending on the version of Selenium that you are using, including the port to run on.&lt;br /&gt;
See the help for the version of Selenium that you are using.&lt;br /&gt;
==== Setting up your browsers ====&lt;br /&gt;
Selenium is just an intelligient wrapper to start, and manage your browser sessions. It doesn&#039;t actually include any web browsers itself.&lt;br /&gt;
&lt;br /&gt;
Moodle HQ run all behat tests against both Firefox and Chrome multiple times per day. Other combinations, including Microsoft Edge, are also supported.&lt;br /&gt;
&lt;br /&gt;
To use Behat, you will need a recent version of your preferred browser, as well as a &amp;lt;tt&amp;gt;driver&amp;lt;/tt&amp;gt; for that browser. The driver is responsible for communication between Selenium (or Moodle directly) and the browser.&lt;br /&gt;
&lt;br /&gt;
Both the browser, and its driver, must be placed inside your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt; - this may be somewhere like &amp;lt;tt&amp;gt;/usr/bin&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;/usr/local/bin&amp;lt;/tt&amp;gt;, or perhaps a user bin directory like &amp;lt;tt&amp;gt;~/bin&amp;lt;/tt&amp;gt; which is present in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Chrome =====&lt;br /&gt;
You can download Google Chrome from [https://www.google.com.au/chrome https://www.google.com.au/chrome].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://chromedriver.chromium.org/downloads correct version of the chromedriver] as per the&lt;br /&gt;
documentation. Alternatively you can make use of the [[Running_acceptance_test#chromedriver-wrapper|chromedriver-wrapper utility]] noted in the Recommended extras sections.&lt;br /&gt;
&lt;br /&gt;
Either the &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;, or the &amp;lt;tt&amp;gt;chromedriver-wrapper/bin&amp;lt;/tt&amp;gt; folder must be in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
===== Firefox =====&lt;br /&gt;
You can download Mozilla Firefox from [https://www.mozilla.org/en-US/firefox/new/ https://www.mozilla.org/en-US/firefox/new/].&lt;br /&gt;
&lt;br /&gt;
You will need the [https://github.com/mozilla/geckodriver/releases correct version of geckodriver as per the [https://firefox-source-docs.mozilla.org/testing/geckodriver/Support.html documentation].&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt; binary must be in a directory in your &amp;lt;tt&amp;gt;$PATH&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Set up Moodle ====&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat&lt;br /&gt;
# Set the following in your Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $CFG-&amp;gt;behat_dataroot = &#039;/path/to/the/dataroot/you/created&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://127.0.0.1/path/to/your/site&#039;;&lt;br /&gt;
    $CFG-&amp;gt;behat_prefix = &#039;beh_&#039;;&lt;br /&gt;
# We recommend that you also include the &amp;lt;tt&amp;gt;behat-browser-config&amp;lt;/tt&amp;gt; if you have not done so already.&lt;br /&gt;
    require_once(&#039;/path/to/moodle-browser-config/init.php&#039;);&lt;br /&gt;
===== Notes about the behat_wwwroot =====&lt;br /&gt;
You will need to set the &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to your Moodle site, but it &#039;&#039;must&#039;&#039; use a different value to your &amp;lt;tt&amp;gt;$CFG-&amp;gt;wwwroot&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
One common way to do this is to use &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; for behat, but &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; for standard use. Alternatively you can add an additional hostname in your &amp;lt;tt&amp;gt;/etc/hosts&amp;lt;/tt&amp;gt; file and use this instead.&lt;br /&gt;
&lt;br /&gt;
If you use Docker, then you may be able to use &amp;lt;tt&amp;gt;host.docker.internal&amp;lt;/tt&amp;gt; where your site is hosted on the docker host&lt;br /&gt;
==== Configure Behat for Moodle ====&lt;br /&gt;
After setting your configuration, you can simply initialise behat:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will install all required Composer dependencies, install a new Moodle site, and put configuration in place&lt;br /&gt;
&lt;br /&gt;
When it finishes it will give advice on how to run Behat.&lt;br /&gt;
==== Run Behat tests ====&lt;br /&gt;
Before running behat, ensure that your Selenium server is running.&lt;br /&gt;
&lt;br /&gt;
The easiest way to run behat, and test that everything works is by simply running it using the command provided when you&lt;br /&gt;
initialised Behat. If you didn&#039;t make a note of it, you can just run the initialisation again.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
This will give you a command which you can then run. This command will run every behat scenario, which will take a considerable amount of time. This command will look a bit like this:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
To make this more useful you an combine it with flags, for example to only run certain &amp;lt;tt&amp;gt;tags&amp;lt;/tt&amp;gt; or for a specific Behat Feature file, or Scenario.&lt;br /&gt;
&lt;br /&gt;
To run all features/scenarios which are tagged with &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --tags=@mod_forum&lt;br /&gt;
To run one specific feature file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature&lt;br /&gt;
To run one specific scenario within a feature file:&lt;br /&gt;
    # To run the Scenario on line 38 of the file:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml `pwd`/mod/forum/tests/behat/private_replies.feature:38&lt;br /&gt;
To run one specific scenario by name:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --name=&amp;quot;As a teacher I can see my own response&amp;quot;&lt;br /&gt;
See the upstream documentation on Behat, and Gherkin filters for more information.&lt;br /&gt;
===== Running using a different browser =====&lt;br /&gt;
The default browser in Behat is &amp;lt;tt&amp;gt;Firefox&amp;lt;/tt&amp;gt;. To specify a different browser profile, you can add the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument. For example, to use Chrome in Headless mode:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
If you are using the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; utility, then you can use any profile listed in [[Running_acceptance_test#Pre-configured_browser_profiles:_moodle-browser-config|moodle-browser-config]]. Otherwise you can write your own browser profile configuration.&lt;br /&gt;
=== Advanced testing ===&lt;br /&gt;
==== Run tests without Selenium (chromedriver, geckodriver) ====&lt;br /&gt;
Historically, Behat required Selenium server, however browsers now make use of their own automation layer. For example, Firefox uses &amp;lt;tt&amp;gt;Geckodriver&amp;lt;/tt&amp;gt; and Chrome uses &amp;lt;tt&amp;gt;Chromedriver&amp;lt;/tt&amp;gt;. As a result the use of Selenium itself is now optional.&lt;br /&gt;
&lt;br /&gt;
The moodle-browser-config tool includes standard profiles to use these drivers directly and without the use of Selenium.&lt;br /&gt;
&lt;br /&gt;
To use the drivers directly, you must run the driver itself, for example to run &amp;lt;tt&amp;gt;chromedriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ chromedriver&lt;br /&gt;
To run &amp;lt;tt&amp;gt;geckodriver&amp;lt;/tt&amp;gt;:&lt;br /&gt;
    $ geckodriver&lt;br /&gt;
Note: geckodriver runs on port 4444 by default. You cannot geckodriver at the same time as selenium.&lt;br /&gt;
&lt;br /&gt;
After starting your preferred browser, you can then run behat and specify an alternative profile, for example:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=geckodriver&lt;br /&gt;
==== Headless browsers ====&lt;br /&gt;
There are a number of reasons that you may prefer to use a headless browser. It can be particularly helpful if you are running the tests on a remote system, for example over SSH, or if you do not want to be interrupted by browsers popping up on your machine.&lt;br /&gt;
&lt;br /&gt;
The following headless profiles are some of those provided in the moodle-browser-config tool as standard:&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessfirefox&amp;lt;/tt&amp;gt; Use Firefox via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlessgeckodriver&amp;lt;/tt&amp;gt; Use Firefox with Geckodriver directly, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschrome&amp;lt;/tt&amp;gt; Use Chrome via Selenium, without displaying the GUI&lt;br /&gt;
# &amp;lt;tt&amp;gt;headlesschromedriver&amp;lt;/tt&amp;gt; Use Chrome with Chromedriver directly, without displaying the GUI&lt;br /&gt;
These can be provided to the &amp;lt;tt&amp;gt;--profile&amp;lt;/tt&amp;gt; argument to behat:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --profile=headlesschrome&lt;br /&gt;
==== Parallel runs ====&lt;br /&gt;
Out-of-the-box, Moodle will configure Behat to run a single Moodle installation with all tests run in series. This is great for developer use where you are running a single test. or a small suite of tests. However this can be quite slow. A lot of time is spent waiting in Behat for things to happen. This may be for a page to load, for additional content to load, or even explicit waits because some interactions must be deliberately slowed down. As a result, a system running behat will not have a particularly high load most of the time.&lt;br /&gt;
&lt;br /&gt;
If you want to run a large suite of tests then it is possible to take advantage of the relatively low resource consumption by running several behat runners in parallel. This is commonly referredt o as a &#039;&#039;&#039;Parallel run&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A parallel run of behat takes the same codebase and creates several installations rather than just a single Moodle installation. The behat Feature files are then grouped and allocated between each of the separate installations.&lt;br /&gt;
&lt;br /&gt;
To support this, each of the parallels runs needs its own:&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* &amp;lt;tt&amp;gt;behat_dataroot&amp;lt;/tt&amp;gt;&lt;br /&gt;
* database&lt;br /&gt;
Rather than using an entirely separate database, the same database is actually used, but a different &amp;lt;tt&amp;gt;behat_prefix&amp;lt;/tt&amp;gt; is used to prefix the table names in the database differently.&lt;br /&gt;
===== Installation =====&lt;br /&gt;
The Behat initialisation command is responsible for preparing Moodle to run a standard run. You&#039;ll have used this before when installing for a standard run:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php&lt;br /&gt;
The same command can be used to prepare Moodle for a parallel run by specifying the &amp;lt;tt&amp;gt;--parallel&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-j&amp;lt;/tt&amp;gt; parameter:&lt;br /&gt;
    // Below command will initialise moodle to run 3 parallel tests.&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3&lt;br /&gt;
This can be combined with the &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; flag to prepare Behat to run with all installed themes.&lt;br /&gt;
&lt;br /&gt;
A number of advanced options are also available but you are unlikely to need these:&lt;br /&gt;
# &amp;lt;tt&amp;gt;-m=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--maxruns=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; 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;
# &amp;lt;tt&amp;gt;--fromrun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run from. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun=&amp;lt;number&amp;gt;&amp;lt;/tt&amp;gt; Initialise site to run specified run till. Used for running acceptance tests on different vms&lt;br /&gt;
# &amp;lt;tt&amp;gt;-o&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--optimize-runs&amp;lt;/tt&amp;gt; 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;
You can view details of all of these using the &amp;lt;tt&amp;gt;--help&amp;lt;/tt&amp;gt; flag to &amp;lt;tt&amp;gt;admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&lt;br /&gt;
===== Running Parallel tests =====&lt;br /&gt;
You can use the Moodle behat runner to run all tests, including Standard runs. It is an intelligient wrapper around the standard &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; command which specifies the configuration file, and other required features.&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php&lt;br /&gt;
Many of the standard options and parameters that can be passed to &amp;lt;tt&amp;gt;./vendor/bin/behat&amp;lt;/tt&amp;gt; can also be passed to the Moodle runner, for example:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; Run tests which match the specified tags&lt;br /&gt;
# &amp;lt;tt&amp;gt;--name=&amp;quot;Scenario name&amp;quot;&amp;lt;/tt&amp;gt; Run a test matching the supplied scenario name&lt;br /&gt;
# &amp;lt;tt&amp;gt;--feature=&amp;quot;/path/to/test.feature&amp;quot;&amp;lt;/tt&amp;gt; Run a specific feature file.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; Features for specified theme will be executed.&lt;br /&gt;
The runner also includes a number of custom parameters relating to parallel runs:&lt;br /&gt;
# &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; Replace args string with run process number, useful for output and reruns.&lt;br /&gt;
# &amp;lt;tt&amp;gt;--fromrun&amp;lt;/tt&amp;gt; Execute run starting from (Used for parallel runs on different vms)&lt;br /&gt;
# &amp;lt;tt&amp;gt;--torun&amp;lt;/tt&amp;gt; Execute run till (Used for parallel runs on different vms)&lt;br /&gt;
The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; feature is particularly useful and can be used to replace a string in the command with the run number. This is useful when specifying output formats, and rerun files as noted below.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The following example demonstrates how Behat might be initialised with three parallel runs, to run on all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --parallel=3 --add-core-features-to-theme&lt;br /&gt;
And then to run all tests matching the &amp;lt;tt&amp;gt;@tool_myplugin&amp;lt;/tt&amp;gt; tag, against the &amp;lt;tt&amp;gt;classic&amp;lt;/tt&amp;gt; theme:&lt;br /&gt;
    $ php admin/tool/behat/cli/run.php --tags=&amp;quot;@tool_myplugin&amp;quot; --suite=&amp;quot;classic&amp;quot;&lt;br /&gt;
===== Custom parameters 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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&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;
       // ...&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;
    $CFG-&amp;gt;behat_parallel_run = [&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4444/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4445/wd/hub&#039;],&lt;br /&gt;
        [&#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4446/wd/hub&#039;],&lt;br /&gt;
    ];&lt;br /&gt;
==== Tests filters ====&lt;br /&gt;
With the &amp;lt;tt&amp;gt;--tags&amp;lt;/tt&amp;gt; or the &amp;lt;tt&amp;gt;-name&amp;lt;/tt&amp;gt; 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;
* &amp;lt;tt&amp;gt;@javascript&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_file_upload&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_alert&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@_switch_window&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; window&amp;lt;/tt&amp;gt; step should be tagged as not all browsers manage them properly.&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_switch_iframe&amp;lt;/tt&amp;gt;: All the tests that are using the &amp;lt;tt&amp;gt;I switch to &amp;quot;NAME&amp;quot; iframe&amp;lt;/tt&amp;gt; steps should be tagged as it is an advanced feature and some browsers may have problems dealing with them&lt;br /&gt;
* &amp;lt;tt&amp;gt;@_cross_browser&amp;lt;/tt&amp;gt;: 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;
* &amp;lt;tt&amp;gt;@componentname&amp;lt;/tt&amp;gt;: 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;
==== Output formats ====&lt;br /&gt;
Behat is able to output in a number of different formats, and to different locations as required.&lt;br /&gt;
&lt;br /&gt;
This can be achieved by specifying the &amp;lt;tt&amp;gt;--format&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;--out&amp;lt;/tt&amp;gt; parameters when running behat, for example:&lt;br /&gt;
    // Run behat, using the &#039;pretty&#039; format and outputting the value to /tmp/pretty.txt&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt&lt;br /&gt;
It is also possible to output to multiple formats simultaneously by repeating the arguments, for example:&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.1 option for output is:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
The following output formats are supported:&lt;br /&gt;
# &amp;lt;tt&amp;gt;progress&amp;lt;/tt&amp;gt;: Prints one character per step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;pretty&amp;lt;/tt&amp;gt;: Prints the feature as is.&lt;br /&gt;
# &amp;lt;tt&amp;gt;junit&amp;lt;/tt&amp;gt;: Outputs the failures in JUnit compatible files.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_progress&amp;lt;/tt&amp;gt;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_list&amp;lt;/tt&amp;gt;: List all scenarios.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_stepcount&amp;lt;/tt&amp;gt;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &amp;lt;tt&amp;gt;moodle_screenshot&amp;lt;/tt&amp;gt;: Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump image only&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump html only.&lt;br /&gt;
## &amp;lt;tt&amp;gt;--format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;&amp;lt;/tt&amp;gt;: will dump both.&lt;br /&gt;
## &amp;lt;tt&amp;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;&amp;lt;/tt&amp;gt;&lt;br /&gt;
Note: 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;
When working with parallel runs, you may wish to have an output for each run. If you were to specify a standard path for this then each of the parallel runs would overwrite the others file. The &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; option allows this to be handled:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
In this example, the &amp;lt;tt&amp;gt;--replace&amp;lt;/tt&amp;gt; argument is provided with a value of &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt;. Anywhere that &amp;lt;tt&amp;gt;{runprocess}&amp;lt;/tt&amp;gt; appears in the command it will be replaced with the run number. The above command will generate a set of commands like:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun1/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_1.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun2/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_2.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun3/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_3.txt \&lt;br /&gt;
        --format=moodle_progress --out=std&lt;br /&gt;
==== Rerun failed scenarios ====&lt;br /&gt;
With slow systems or parallel run you may experience see some random failures. These may happen when your system is too slow, when it is too fast, or where a page depends on external dependencies.&lt;br /&gt;
&lt;br /&gt;
To help with this it is possible to rerun any failed scenarios using the &amp;lt;tt&amp;gt;--rerun&amp;lt;/tt&amp;gt; option to Behat.&lt;br /&gt;
&lt;br /&gt;
The following example runs Behat with the rerun option:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
If any single test fails then the command will return a non-zero exit code. Running the same command again will mean that only failed scenarios are run.&lt;br /&gt;
&lt;br /&gt;
For a parallel run it can be called as follows:&lt;br /&gt;
    $ admin/tool/behat/cli/run.php \&lt;br /&gt;
        --replace=&amp;quot;{runprocess}&amp;quot; \&lt;br /&gt;
        --format=pretty --out=/tmp/pretty_{runprocess}.txt \&lt;br /&gt;
        --format=moodle_progress --out=std \&lt;br /&gt;
        --rerun&lt;br /&gt;
The Moodle behat runner also includes an &amp;lt;tt&amp;gt;--auto-rerun&amp;lt;/tt&amp;gt; option which will automatically rerun failed scenarios exactly once.&lt;br /&gt;
==== Running behat with specified theme ====&lt;br /&gt;
Behat can be run with any installed theme, but it must be initialised with the &amp;lt;tt&amp;gt;-a&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--add-core-features-to-theme&amp;lt;/tt&amp;gt; option first.&lt;br /&gt;
&lt;br /&gt;
After configuring the theme can be specified using the &amp;lt;tt&amp;gt;--suite&amp;lt;/tt&amp;gt; parameter.&lt;br /&gt;
&lt;br /&gt;
Note: The default theme in Moodle (boost) has the suite name &amp;lt;tt&amp;gt;default&amp;lt;/tt&amp;gt;.&lt;br /&gt;
    // Initialise Behat for all themes:&lt;br /&gt;
    $ php admin/tool/behat/cli/init.php --add-core-features-to-theme&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against all initalised themes:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the default theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=default&lt;br /&gt;
    &lt;br /&gt;
    // Run Behat against just the &#039;classic&#039; theme when all themes were initialised:&lt;br /&gt;
    $ vendor/bin/behat --config /Users/nicols/Sites/moodles/sm/moodledata_behat/behatrun/behat/behat.yml --suite=classic&lt;br /&gt;
==== Using Docker to start selenium server ====&lt;br /&gt;
There are a wide range of docker images available which contain a browser with Selenium. You will probably be using the official SeleniumHQ images unless you have a specific reason not to.&lt;br /&gt;
&lt;br /&gt;
The complete list of available SeleniumHQ images is available at [https://hub.docker.com/u/selenium/ https://hub.docker.com/u/selenium/].&lt;br /&gt;
&lt;br /&gt;
Moodle uses the &#039;&#039;standalone&#039;&#039; version and any recent version with version 3.141.59 or higher is supported.&lt;br /&gt;
&lt;br /&gt;
For any test which uploads files, for example when interacting with the filepicker, you must also ensure that the Moodle directory is mounted as on your local filesystem.&lt;br /&gt;
&lt;br /&gt;
An example usage is:&lt;br /&gt;
    $ docker run -d -p 4444:4444 -v `pwd`:`pwd` selenium/standalone-firefox:latest&lt;br /&gt;
==== Change config.php file ====&lt;br /&gt;
In the Moodle &amp;lt;tt&amp;gt;config.php&amp;lt;/tt&amp;gt; file you must change the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_wwwroot&amp;lt;/tt&amp;gt; to an address that can be reached from within the docker image.&lt;br /&gt;
&lt;br /&gt;
You cannot use &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;127.0.0.1&amp;lt;/tt&amp;gt; as this will be the IP address of the docker container itself.&lt;br /&gt;
&lt;br /&gt;
On some more recent versions of Docker you can use &amp;lt;tt&amp;gt;http://host.docker.internal/&amp;lt;/tt&amp;gt;, for example if my site is located at &amp;lt;tt&amp;gt;/sm&amp;lt;/tt&amp;gt; on my development machine, I can set the following:&lt;br /&gt;
    $CFG-&amp;gt;behat_wwwroot = &#039;http://host.docker.internal/sm&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Manually configuring other browsers =====&lt;br /&gt;
If you would prefer not to use the &amp;lt;tt&amp;gt;moodle-browser-config&amp;lt;/tt&amp;gt; tool but still wish to specify different browsers then you can do so using the &amp;lt;tt&amp;gt;$CFG-&amp;gt;behat_profiles&amp;lt;/tt&amp;gt; array. Each key/value pair contains a profile name, and the configuration for that profile. For example:&lt;br /&gt;
    $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
        &#039;chrome&#039; =&amp;gt; [&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;
[[Acceptance_testing/Browsers|More info about alternative browsers]]&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
=== Increasing timeouts ===&lt;br /&gt;
You may see errors such as:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
Sometimes this indicates a genuine problem with the code, but if you are using a slow computer, it may just mean that your browser did not finish generating parts of the UI before behat tried was finished.&lt;br /&gt;
&lt;br /&gt;
If you find that this happens regularly on different scenarios then you may want to increase the timeout:&lt;br /&gt;
    $CFG-&amp;gt;behat_increasetimeout = 2;&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: This is usually an indicator that your development machine is not well tuned. A better option would be to find out where the bottleneck is. This is usually the database configuration.&lt;br /&gt;
&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;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/util.php --enable&lt;br /&gt;
&amp;lt;/syntaxhighlight&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;
=== Tests are failing ===&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;
=== The tests are failing, and the error message is completely useless ===&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;
=== 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;
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;
=== Selenium server is not running ===&lt;br /&gt;
==== Chrome specific ====&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;
&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;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Quick setup and testing using moodle-docker ===&lt;br /&gt;
This is a quick guide to help locally pass tests for your developments, before submitting them:&lt;br /&gt;
# Set up a default Moodle install using [https://github.com/moodlehq/moodle-docker moodle-docker], with the database and Moodle version of your choice. See its README for more details. This will start some docker containers.&lt;br /&gt;
# Initialize behat to start testing with this command, from the webserver container: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# Run the behat test of your choice, from the webserver container. For instance: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/behat --config /var/www/behatdata/behatrun/behat/behat.yml --tags tool_task&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And you&#039;ll see something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
Moodle 4.0dev (Build: 20210507), 0b47ea0a44a092f9000729ca7b15fff23111538b&lt;br /&gt;
Php: 7.3.26, mysqli: 5.7.21, OS: Linux 5.4.0-66-generic x86_64&lt;br /&gt;
Run optional tests:&lt;br /&gt;
&lt;br /&gt;
Accessibility: No&lt;br /&gt;
Server OS &amp;quot;Linux&amp;quot;, Browser: &amp;quot;firefox&amp;quot;&lt;br /&gt;
Started at 09-05-2021, 06:00&lt;br /&gt;
...................................................................... 70&lt;br /&gt;
...............................&lt;br /&gt;
12 scenarios (12 passed)&lt;br /&gt;
101 steps (101 passed)&lt;br /&gt;
2m53.27s (47.61Mb)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Acceptance testing for the mobile app]]&lt;br /&gt;
[[Category:Quality Assurance]][[Category:Behat]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Information to re-home ==&lt;br /&gt;
==== Override behat core context for theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Disable behat context or features to run in theme suite ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&lt;br /&gt;
To disable specific contexts and features from being executed by a specific theme/suite you can create a &amp;lt;tt&amp;gt;/theme/{MYTHEME}/tests/behat/blacklist.json&amp;lt;/tt&amp;gt; file with following format.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&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;/syntaxhighlight&amp;gt;&lt;br /&gt;
The above will:&lt;br /&gt;
# disable the use of step_definitions from &amp;lt;tt&amp;gt;behat_grade&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;behat_navigation&amp;lt;/tt&amp;gt; while running theme suite; and&lt;br /&gt;
# disable running of scenarios in &amp;lt;tt&amp;gt;auth/tests/behat/login.feature&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;grade/tests/behat/grade_hidden_items.feature&amp;lt;/tt&amp;gt;.&lt;br /&gt;
==== Override core behat selectors ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&lt;br /&gt;
&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;
==== Write new tests and behat methods ====&lt;br /&gt;
Note: This documentation needs to be rehomed to a location related to writing Behat tests.&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;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=PHPUnit_integration&amp;diff=61556</id>
		<title>PHPUnit integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=PHPUnit_integration&amp;diff=61556"/>
		<updated>2021-12-01T12:30:09Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Organisation of test files */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle PHPUnit integration was created to simplify using of PHPUnit framework in Moodle. It consists of specialised bootstrap script, utility scripts that initialise testing environment and highly optimised custom test case classes that handle automatic global state resetting after test that includes global variables, database rollback and purging of dataroot. We also try to bridge the gap between the design and coding style of Moodle and PHPUnit.&lt;br /&gt;
&lt;br /&gt;
Most of the documentation at http://www.phpunit.de/manual/3.6/en/index.html is relevant to Moodle PHPUnit integration, exceptions and additions are described below.&lt;br /&gt;
&lt;br /&gt;
=Definitions=&lt;br /&gt;
These definitions may differ in each testing framework or programming language. The definitions here should be valid for PHPUnit framework with our Moodle tweaks.&lt;br /&gt;
&lt;br /&gt;
;Test suite: is a collection of test cases or other suites usually related to one area of the product. It is defined in PHPUnit configuration files (phpunit.xml by default).&lt;br /&gt;
&lt;br /&gt;
;Test file: is a file with *_test.php name which contains a test case. (*_Test.php in PHPUnit)&lt;br /&gt;
&lt;br /&gt;
;Test case: is a class with test methods. Their name should match the name of the test file (*_test). Moodle tests extend basic_testcase or advanced_testcase. (PHPUnit\Framework\TestCase in PHPUnit)&lt;br /&gt;
&lt;br /&gt;
;Test: is a public method starting with &amp;quot;test_&amp;quot; prefix defined in test case class. It is the implementation of the test procedure.&lt;br /&gt;
&lt;br /&gt;
;Assertion: is the actual comparison of expected and actual behaviour of the tested code.&lt;br /&gt;
&lt;br /&gt;
=Organisation of test files=&lt;br /&gt;
All testing related files are stored in &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;/tests/&amp;lt;/syntaxhighlight&amp;gt; subdirectories:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/*_test.php&amp;lt;/syntaxhighlight&amp;gt; are PHPUnit test files&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/fixtures/&amp;lt;/syntaxhighlight&amp;gt; contains auxiliary files used in tests&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/performance/&amp;lt;/syntaxhighlight&amp;gt; is reserved for performance testing scripts&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/generator/&amp;lt;/syntaxhighlight&amp;gt; for generators&lt;br /&gt;
* &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;.../tests/other/&amp;lt;/syntaxhighlight&amp;gt; is used for everything else that does not fit&lt;br /&gt;
&lt;br /&gt;
==Class and file naming rules==&lt;br /&gt;
=== Actual (Moodle 3.11 and up) ===&lt;br /&gt;
The class names of all testcases need to be unique, with namespaces being used to guarantee this.&lt;br /&gt;
&lt;br /&gt;
* All test case class names should match the file name, for example: the class name for &amp;lt;tt&amp;gt;mod/forum/test/this_test.php&amp;lt;/tt&amp;gt; should be &amp;lt;tt&amp;gt;this_test&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* All test case classes end with &#039;&#039;&#039;_test&#039;&#039;&#039; suffix.&lt;br /&gt;
* All test case classes should use the namespace they belong to, for example: the namespace for &amp;lt;tt&amp;gt;mod/forum/test/this_test.php&amp;lt;/tt&amp;gt; should be &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;. With sub-namespaces allowed to better match what is being tested.&lt;br /&gt;
&lt;br /&gt;
Read about the rules for namespaces @ [[Coding style#Namespaces_within_.2A.2A.2Ftests_directories|coding style]] (grand summary = 100% the same rules that are applied to **/classes directories).&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
* class &#039;&#039;&#039;lib_test&#039;&#039;&#039; (with namespace &amp;lt;tt&amp;gt;mod_forum&amp;lt;/tt&amp;gt;) is expected in file &#039;&#039;&#039;/mod/forum/tests/lib_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit mod/forum/tests/lib_test.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* class &#039;&#039;&#039;text_test&#039;&#039;&#039; (with namespace &amp;lt;tt&amp;gt;core&amp;lt;/tt&amp;gt;) is expected in file &#039;&#039;&#039;/lib/texts/text_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit lib/texts/text_test.php&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Previous (until Moodle 3.10) ===&lt;br /&gt;
Frankenstyle prefix is used to guarantee this. Since 2.6 it is possible to use automatic class loader when executing individual unit tests.&lt;br /&gt;
&lt;br /&gt;
* All test case classes start with Frankenstyle prefix, for example: &#039;&#039;&#039;mod_forum_&#039;&#039;&#039;, &#039;&#039;&#039;block_html_&#039;&#039;&#039;, &#039;&#039;&#039;core_&#039;&#039;&#039;.&lt;br /&gt;
* All test case classes end with &#039;&#039;&#039;_testcase&#039;&#039;&#039; suffix.&lt;br /&gt;
* File names are constructed from the class names - the Frankenstyle prefix is removed, suffix &#039;&#039;&#039;_testcase&#039;&#039;&#039; is replaced with &#039;&#039;&#039;_test&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Finally, but not less important, test case classes can be (recommended) namespaces. Read about the rules to do it properly @ [[Coding style#Namespaces_within_.2A.2A.2Ftests_directories|coding style]] (grand summary = 100% the same rules that are applied to **/classes directories).&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
* class &#039;&#039;&#039;mod_forum_lib_testcase&#039;&#039;&#039; is expected in file &#039;&#039;&#039;/mod/forum/tests/lib_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit mod_forum_lib_testcase&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* class &#039;&#039;&#039;core_text_testcase&#039;&#039;&#039; is expected in file &#039;&#039;&#039;/lib/texts/text_test.php&#039;&#039;&#039;, executed as &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit core_text_testcase&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=basic_testcase=&lt;br /&gt;
&#039;&#039;basic_testcase&#039;&#039; is useful for simple tests that do not modify database or global variables. If something accidentally changes global state the test fails. This test case class is nearly identical to PHPUnit_Framework_TestCase used in PHPUnit documentation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php // File: mod/myplugin/tests/sample_test.php&lt;br /&gt;
&lt;br /&gt;
namespace mod_myplugin;&lt;br /&gt;
&lt;br /&gt;
class sample_test extends \basic_testcase {&lt;br /&gt;
    public function test_equals() {&lt;br /&gt;
        $a = 1 + 2;&lt;br /&gt;
        $this-&amp;gt;assertEquals(3, $a);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=advanced_testcase=&lt;br /&gt;
By default each test starts with fresh new moodle installation. Test may modify database content, files or global variables. It is possible to use data generators to create new course, categories, module instances and other objects, alternatively table data can be preloaded from XML or CSV files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php // File: mod/myplugin/tests/complex_test.php&lt;br /&gt;
&lt;br /&gt;
namespace mod_myplugin;&lt;br /&gt;
&lt;br /&gt;
class complex_test extends \advanced_testcase {&lt;br /&gt;
    public function test_isadmin() {&lt;br /&gt;
        global $DB;&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;resetAfterTest(true);          // reset all changes automatically after this test&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;assertFalse(is_siteadmin());   // by default no user is logged-in&lt;br /&gt;
        $this-&amp;gt;setUser(2);                    // switch $USER&lt;br /&gt;
        $this-&amp;gt;assertTrue(is_siteadmin());    // admin is logged-in now&lt;br /&gt;
&lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;user&#039;, array()); // lets do something crazy&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;resetAllData();                // that was not a good idea, let&#039;s go back&lt;br /&gt;
        $this-&amp;gt;assertTrue($admin = $DB-&amp;gt;record_exists(&#039;user&#039;, array(&#039;id&#039;=&amp;gt;2)));&lt;br /&gt;
        $this-&amp;gt;assertFalse(is_siteadmin());&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extra methods==&lt;br /&gt;
; resetAfterTest(bool) : true means reset automatically after test, false means keep changes to next test method, default null means detect changes&lt;br /&gt;
; resetAllData() : reset global state in the middle of a test&lt;br /&gt;
; setAdminUser() : set current $USER as admin&lt;br /&gt;
; setGuestUser() : set current $USER as guest&lt;br /&gt;
; setUser() : set current $USER to a specific user - use getDataGenerator() to create one&lt;br /&gt;
; getDataGenerator() : returns data generator instance - use if you need to add new courses, users, etc.&lt;br /&gt;
; preventResetByRollback() : terminates active transactions, useful only when test contains own database transaction handling&lt;br /&gt;
; createXXXDataSet() : creates in memory structure of database table contents, used in loadDataSet() (eg: createXMLDataSet(), createCsvDataSet(), createFlatXMLDataSet())&lt;br /&gt;
; loadDataSet() : bulk loading of table contents&lt;br /&gt;
; getDebuggingMessages() : Return debugging messages from the current test. (Moodle 2.4 and upwards)&lt;br /&gt;
; resetDebugging() : Clear all previous debugging messages in current test. (Moodle 2.4 and upwards)&lt;br /&gt;
; assertDebuggingCalled() : Assert that exactly debugging was just called once. (Moodle 2.4 and upwards)&lt;br /&gt;
; assertDebuggingNotCalled() : Assert no debugging happened. (Moodle 2.4 and upwards)&lt;br /&gt;
; assertDebuggingCalledCount() : Asserts how many times debugging has been called. (Moodle 3.1 and upwards)&lt;br /&gt;
; [[Writing PHPUnit tests#Testing sending of messages|redirectMessages()]]: Captures ongoing messages for later testing (Moodle 2.4 and upwards)&lt;br /&gt;
; [[Writing PHPUnit tests#Testing_sending_of_emails|redirectEmails()]]: Captures ongoing emails for later testing (Moodle 2.6 and upwards)&lt;br /&gt;
&lt;br /&gt;
==Restrictions==&lt;br /&gt;
* it is not possible to modify database structure such as create new table or drop columns from advanced_testcase.&lt;br /&gt;
&lt;br /&gt;
=Moodle specific features=&lt;br /&gt;
* detection of global state changes - helps with detection of unintended changes in database&lt;br /&gt;
* highly optimised global state reset&lt;br /&gt;
* dataset loading - this feature is copied from PHPUnit database testcases&lt;br /&gt;
* automatic generation of phpunit.xml - init script builds list of plugin testcases&lt;br /&gt;
* database driver testing class - used for functional DB tests&lt;br /&gt;
* SimpleTest emulation class - helps with migration of old tests&lt;br /&gt;
* debugging() interception - enables to control and test any expected debug output. (Moodle 2.4 and upwards)&lt;br /&gt;
&lt;br /&gt;
=Limitations=&lt;br /&gt;
* no Selenium support&lt;br /&gt;
* no support for PHPUnit_Extensions_Database_TestCase - it is possible to use data set loader only&lt;br /&gt;
&lt;br /&gt;
=See also=&lt;br /&gt;
* [[Writing PHPUnit tests]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;diff=61555</id>
		<title>Writing PHPUnit tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;diff=61555"/>
		<updated>2021-11-30T15:52:21Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Assertions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
Moodle PHPUnit integration is designed to allow easy adding of new tests. At the start of each test the state is automatically reset to fresh new installation (unless explicitly told not to reset).&lt;br /&gt;
&lt;br /&gt;
=Namespaces=&lt;br /&gt;
&lt;br /&gt;
All the stuff under **/tests directories is [[Coding style#Namespaces_within_.2A.2A.2Ftests_directories|subject to some simple rules]] when using namespaces. They apply to test cases, fixtures, generators and, in general, any class within those directories. Take a look to them! (grand summary = 100% the same rules that are applied to **/classes directories).&lt;br /&gt;
&lt;br /&gt;
=Testcase classes=&lt;br /&gt;
&lt;br /&gt;
There are three basic test class that are supposed to used in all Moodle unit tests - basic_testcase, advanced_testcase and provider_testcase. &#039;&#039;&#039;Please note it is strongly recommended to put only one testcase into each class file.&#039;&#039;&#039;&lt;br /&gt;
;basic_testcase : Very simple tests that do not modify database, dataroot or any PHP globals. It can be used for example when trying examples from the official PHPUnit tutorial.&lt;br /&gt;
;advanced_testcase : Enhanced testcase class enhanced for easy testing of Moodle code.&lt;br /&gt;
;provider_testcase: Enhanced testcase class, enhanced for easy testing of [[Privacy API|Privacy Providers]].&lt;br /&gt;
&lt;br /&gt;
There is a fourth testcase class that is specially designed for testing of our Moodle database layer, it should not be used for other purposes.&lt;br /&gt;
&lt;br /&gt;
== Assertions ==&lt;br /&gt;
&lt;br /&gt;
The complete list of assertions can be found in the links below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Moodle version&lt;br /&gt;
! PHPUnit version&lt;br /&gt;
!Links&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.11&lt;br /&gt;
| PHPUnit 9.5&lt;br /&gt;
|[https://phpunit.readthedocs.io/en/9.5/assertions.html Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.10&lt;br /&gt;
| PHPUnit 8.5&lt;br /&gt;
|[https://phpunit.readthedocs.io/en/8.5/assertions.html Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.7 - 3.9&lt;br /&gt;
| PHPUnit 7.5&lt;br /&gt;
|[https://phpunit.readthedocs.io/en/7.5/assertions.html Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.4 - 3.6&lt;br /&gt;
| PHPUnit 6.5&lt;br /&gt;
|[https://phpunit.de/manual/6.5/en/assertions.html Documentation]&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Sample plugin testcase==&lt;br /&gt;
&lt;br /&gt;
PHPUnit tests are located in &amp;lt;tt&amp;gt;tests/*_test.php&amp;lt;/tt&amp;gt; files in your plugin, for example &amp;lt;tt&amp;gt;mod/myplugin/tests/sample_test.php&amp;lt;/tt&amp;gt;, the file should contain only one class that extends &amp;lt;tt&amp;gt;advanced_testcase&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 namespace mod_myplugin;&lt;br /&gt;
&lt;br /&gt;
 class sample_test extends \advanced_testcase {&lt;br /&gt;
     public function test_adding() {&lt;br /&gt;
         $this-&amp;gt;assertEquals(2, 1+2);&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See [[PHPUnit integration#Class and file naming rules]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Inclusion of Moodle library files==&lt;br /&gt;
&lt;br /&gt;
If you want to include some Moodle library files you should always declare &#039;&#039;&#039;global $CFG&#039;&#039;&#039;. The reason is that testcase files may be included from non-moodle code which does not make the global $CFG available automatically.&lt;br /&gt;
&lt;br /&gt;
==Automatic state reset==&lt;br /&gt;
By default after each test Moodle database and dataroot is automatically reset to the original state which was present right after installation. make sure to use $this-&amp;gt;resetAfterTest() to indicate that the database or changes of standard global variables are expected.&lt;br /&gt;
&lt;br /&gt;
If you received the error &amp;quot;Warning: unexpected database modification, resetting DB state&amp;quot; it is because the test is not using &amp;lt;tt&amp;gt;$this-&amp;gt;resetAfterTest()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 namespace mod_myplugin;&lt;br /&gt;
&lt;br /&gt;
 class test_something extends \advanced_testcase {&lt;br /&gt;
     public function test_deleting() {&lt;br /&gt;
         global $DB;&lt;br /&gt;
         $this-&amp;gt;resetAfterTest(true);&lt;br /&gt;
         $DB-&amp;gt;delete_records(&#039;user&#039;);&lt;br /&gt;
         $this-&amp;gt;assertEmpty($DB-&amp;gt;get_records(&#039;user&#039;));&lt;br /&gt;
     }&lt;br /&gt;
     public function test_user_table_was_reset() {&lt;br /&gt;
         global $DB;&lt;br /&gt;
         $this-&amp;gt;assertEquals(2, $DB-&amp;gt;count_records(&#039;user&#039;, array()));&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Generators=&lt;br /&gt;
&lt;br /&gt;
Tests that need to modify default installation may use generators to create new courses, users, etc. All examples on this page should be used from test methods of a test class derived from advanced_testcase.&lt;br /&gt;
&lt;br /&gt;
Note if you are using PHPUnit [https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers @dataProvider] functions to provide parameters to unit tests, you can not use the data generator or change the user etc in the data provider function. Data providers &#039;&#039;&#039;must not instantiate/create data&#039;&#039;&#039;. Just define it. And then, the test body can proceed with the instantiation/creation.&lt;br /&gt;
&lt;br /&gt;
==Creating users==&lt;br /&gt;
At the start of each test there are only two users present - guest and administrator. If you need to add more test accounts use:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $user = $this-&amp;gt;getDataGenerator()-&amp;gt;create_user();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also specify properties of the user account, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $user1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_user(array(&#039;email&#039;=&amp;gt;&#039;user1@example.com&#039;, &#039;username&#039;=&amp;gt;&#039;user1&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default no user is logged-in, use setUser() method to change current $USER value:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setUser($user1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Guest and admin accounts have a shortcut methods:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setGuestUser();&lt;br /&gt;
 $this-&amp;gt;setAdminUser();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Null can be used to set current user back to not-logged-in:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setUser(null);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating course categories==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $category1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category();&lt;br /&gt;
 $category2 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category(array(&#039;name&#039;=&amp;gt;&#039;Some subcategory&#039;, &#039;parent&#039;=&amp;gt;$category1-&amp;gt;id));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating courses==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $course1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 &lt;br /&gt;
 $category = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category();&lt;br /&gt;
 $course2 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course(array(&#039;name&#039;=&amp;gt;&#039;Some course&#039;, &#039;category&#039;=&amp;gt;$category-&amp;gt;id));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating activities==&lt;br /&gt;
&lt;br /&gt;
Some activity plugins include instance generators. The generator class are defined in plugindirectory/tests/generator/lib.php.&lt;br /&gt;
&lt;br /&gt;
Example of creation of new course with one page resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $course = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 $generator = $this-&amp;gt;getDataGenerator()-&amp;gt;get_plugin_generator(&#039;mod_page&#039;);&lt;br /&gt;
 $generator-&amp;gt;create_instance(array(&#039;course&#039;=&amp;gt;$course-&amp;gt;id));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following is functionally the same, but a bit shorter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $course = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 $page = $this-&amp;gt;getDataGenerator()-&amp;gt;create_module(&#039;page&#039;, array(&#039;course&#039; =&amp;gt; $course-&amp;gt;id));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating cohorts==&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
Since 2.4 there the data generator supports creation of new cohorts.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 $cohort = $this-&amp;gt;getDataGenerator()-&amp;gt;create_cohort();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Simplified user enrolments==&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
Instead of standard enrolment API it is possible to use simplified method in data generator. It is intended to be used with self and manual enrolment plugins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid);&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid, $teacherroleid);&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid, $teacherroleid, &#039;manual&#039;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating scales==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_scale();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_scale(array(&#039;name&#039; =&amp;gt; $name, &#039;scale&#039; =&amp;gt; $scale, &#039;courseid&#039; =&amp;gt; $courseid, &#039;userid&#039; =&amp;gt; $userid, &#039;description&#039; =&amp;gt; description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating roles==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_role();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_role(array(&#039;shortname&#039; =&amp;gt; $shortname, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; description, &#039;archetype&#039; =&amp;gt; $archetype));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating tags==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_tag();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_tag(array(&lt;br /&gt;
    &#039;userid&#039; =&amp;gt; $userid, &lt;br /&gt;
    &#039;rawname&#039; =&amp;gt; $rawname,&lt;br /&gt;
    &#039;name&#039; =&amp;gt; $name, &lt;br /&gt;
    &#039;description&#039; =&amp;gt; $description, &lt;br /&gt;
    &#039;descriptionformat&#039; =&amp;gt; $descriptionformat,&lt;br /&gt;
    &#039;flag&#039; =&amp;gt; $flag&lt;br /&gt;
));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Groups==&lt;br /&gt;
&lt;br /&gt;
===Creating groups===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; $description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Adding users to groups===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group_member(array(&#039;userid&#039; =&amp;gt; $userid, &#039;groupid&#039; =&amp;gt; $groupid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group_member(array(&#039;userid&#039; =&amp;gt; $userid, &#039;groupid&#039; =&amp;gt; $groupid, &#039;component&#039; =&amp;gt; $component, &#039;itemid&#039; =&amp;gt; $itemid));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating groupings===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; $description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Adding groups to groupings===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping_group(array(&#039;groupingid&#039; =&amp;gt; $groupingid, &#039;groupid&#039; =&amp;gt; $groupid));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Repositories==&lt;br /&gt;
&lt;br /&gt;
===Creating repository instances===&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Some respository plugins include instance generators. The generator class are defined in plugindirectory/tests/generator/lib.php..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_repository($type, $record, $options);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating repository types===&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Some respository plugins include type generators. The generator class are defined in plugindirectory/tests/generator/lib.php..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_repository_type($type, $record, $options);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating grades==&lt;br /&gt;
&lt;br /&gt;
===Grade categories===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_category(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_category(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;fullname&#039; =&amp;gt; $fullname));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Grade items===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item(array(&#039;itemtype&#039; =&amp;gt; $itemtype, &#039;itemname&#039; =&amp;gt; $itemname, &#039;outcomeid&#039; =&amp;gt; $outcomeid, &#039;scaleid&#039; =&amp;gt; $scaleid, &#039;gradetype&#039; =&amp;gt; $gradetype));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Outcomes===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_outcome();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item(array(&#039;fullname&#039; =&amp;gt; $fullname));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Other types of plugin==&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Any other type of plugin can have a generator. The generator class should extend component_generator_base, and then you can get an instance using $mygenerator = $this-&amp;gt;getDataGenerator()-&amp;gt;get_plugin_generator($frankenstylecomponentname);&lt;br /&gt;
&lt;br /&gt;
For some types of plugin, like mod documented above, there may be a more specific class than component_generator_base to extend, like testing_module_generator. That will give a consistent set of method names to use. Otherwise, you can create whatever methods you like on your generator, to create the different things you need to work whith.&lt;br /&gt;
&lt;br /&gt;
=Long tests=&lt;br /&gt;
&lt;br /&gt;
All standard test should execute as fast as possible. Tests that take a loner time to execute (&amp;gt;10s) or are otherwise expensive (such as querying external servers that might be flooded by all dev machines) should be execute only when PHPUNIT_LONGTEST is true. This constant can be set in phpunit.xml or directly in config.php.&lt;br /&gt;
&lt;br /&gt;
=Large test data=&lt;br /&gt;
See advanced_testcase::createXMLDataSet() and advanced_testcase::createCsvDataSet() and related functions there for easier ways to manage large test data sets within files rather than arrays in code. See [[PHPUnit_integration#Extra_methods]]&lt;br /&gt;
&lt;br /&gt;
=Testing sending of messages=&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
You can temporarily redirect all messages sent via message_send() to a message sink object. This allows developers to verify that the tested code is sending expected messages.&lt;br /&gt;
&lt;br /&gt;
To test code using messaging first disable the use of transactions and then redirect the messaging into a new message sink, you can inspect the results later.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;preventResetByRollback();&lt;br /&gt;
$sink = $this-&amp;gt;redirectMessages();&lt;br /&gt;
//... code that is sending messages&lt;br /&gt;
$messages = $sink-&amp;gt;get_messages();&lt;br /&gt;
$this-&amp;gt;assertEquals(3, count($messages));&lt;br /&gt;
//.. test messages were generated in correct order with appropriate content&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Testing sending of emails=&lt;br /&gt;
{{Moodle 2.6}}&lt;br /&gt;
You can temporarily redirect emails sent via email_to_user() to a email message sink object. This allows developers to verify that the tested code is sending expected emails.&lt;br /&gt;
&lt;br /&gt;
To test code using messaging first unset &#039;noemailever&#039; setting and then redirect the emails into a new message sink where you can inspect the results later.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
unset_config(&#039;noemailever&#039;);&lt;br /&gt;
$sink = $this-&amp;gt;redirectEmails();&lt;br /&gt;
//... code that is sending email&lt;br /&gt;
$messages = $sink-&amp;gt;get_messages();&lt;br /&gt;
$this-&amp;gt;assertEquals(1, count($messages));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Logstores=&lt;br /&gt;
You can test events which were written to a logstore, but you must disable transactions, enable at least one valid logstore, and disable logstore buffering to ensure that the events are written to the database before the tests execute.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$this-&amp;gt;preventResetByRollback();&lt;br /&gt;
set_config(&#039;enabled_stores&#039;, &#039;logstore_standard&#039;, &#039;tool_log&#039;);&lt;br /&gt;
set_config(&#039;buffersize&#039;, 0, &#039;logstore_standard&#039;);&lt;br /&gt;
get_log_manager(true);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Check your coverage=&lt;br /&gt;
{{Moodle 3.7}}&lt;br /&gt;
PHPUnit has the ability to generate code coverage information for your unit tests.&lt;br /&gt;
&lt;br /&gt;
Prior to Moodle 3.7, this coverage would load all files and generate coverage for everything regardless of whether that file could be covered at all, or whether it was intentionally covered.&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.7 the &#039;&#039;&#039;phpunit.xml&#039;&#039;&#039; configuration contains generated coverage include and exclude information for each component.&lt;br /&gt;
&lt;br /&gt;
==Generating include and exclude configuration==&lt;br /&gt;
{{Moodle 3.11}}&lt;br /&gt;
You can programatically describe which files will be checked for coverage by creating a &amp;lt;tt&amp;gt;coverage.php&amp;lt;/tt&amp;gt; file alongside the tests that you are writing.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;coverage.php&amp;lt;/tt&amp;gt; file allows you to list include and exclude files and folders within the component being&lt;br /&gt;
tested. All paths specified are relative to the component being tested. For example, when working with &#039;&#039;&#039;mod_forum&#039;&#039;&#039; your&lt;br /&gt;
code will be in &#039;&#039;&#039;mod/forum&#039;&#039;&#039;, and its unit tests will be in &#039;&#039;&#039;mod/forum/tests/example_test.php&#039;&#039;&#039;. The coverage file&lt;br /&gt;
for this would be in &#039;&#039;&#039;mod/forum/tests/coverage.php&#039;&#039;&#039; and all paths specified would be relative to &#039;&#039;&#039;mod/forum&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
It is possible to specify a combination of included files, included folders, excluded files, and&lt;br /&gt;
excluded folders. This would allow you, for example, to include the entire &#039;&#039;&#039;classes&#039;&#039;&#039; directory, but exclude&lt;br /&gt;
a specific file or folder within it.&lt;br /&gt;
&lt;br /&gt;
The following is an example &amp;lt;tt&amp;gt;coverage.php&amp;lt;/tt&amp;gt; file from &#039;&#039;&#039;mod_forum&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
Note: For Moodle versions 3.7 to 3.10, the [https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;amp;oldid=58177#Check_your_coverage syntax used] was slightly different.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return new class extends phpunit_coverage_info {&lt;br /&gt;
    /** @var array The list of folders relative to the plugin root to include in coverage generation. */&lt;br /&gt;
    protected $includelistfolders = [&lt;br /&gt;
        &#039;classes&#039;,&lt;br /&gt;
        &#039;externallib.php&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /** @var array The list of files relative to the plugin root to include in coverage generation. */&lt;br /&gt;
    protected $includelistfiles = [];&lt;br /&gt;
&lt;br /&gt;
    /** @var array The list of folders relative to the plugin root to exclude from coverage generation. */&lt;br /&gt;
    protected $excludelistfolders = [];&lt;br /&gt;
&lt;br /&gt;
    /** @var array The list of files relative to the plugin root to exclude from coverage generation. */&lt;br /&gt;
    protected $excludelistfiles = [];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also, note that you can better define which class or function each test is effectively covering by using the &amp;lt;tt&amp;gt;@covers&amp;lt;/tt&amp;gt; annotation as [https://phpunit.readthedocs.io/en/9.5/code-coverage-analysis.html#specifying-covered-code-parts described in the documention].&lt;br /&gt;
&lt;br /&gt;
=Best practice=&lt;br /&gt;
&lt;br /&gt;
There are several best practices, suggestions, and things to avoid which you should consider when writing unit tests. Some of these are described below.&lt;br /&gt;
&lt;br /&gt;
==Check your coverage==&lt;br /&gt;
PHPUnit has the ability to generate code coverage information for your unit tests and this is well supported since&lt;br /&gt;
Moodle 3.7. We recommend that you consider checking the coverage of your plugins when you write your code.&lt;br /&gt;
&lt;br /&gt;
==Keep use of resetAfterTest to a minimum==&lt;br /&gt;
Although many of the examples described above use the &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;resetAfterTest&amp;lt;/syntaxhighlight&amp;gt; nomenclature to reset the database and filesystem after your test completes, you should ideally not use this unless you have to.&lt;br /&gt;
Generally speaking you should aim to write code which is mockable, and does not require real fixtures.&lt;br /&gt;
Use of resetAfterTest will also slow your tests down.&lt;br /&gt;
&lt;br /&gt;
==Be careful with shared setUp and instance variables==&lt;br /&gt;
&lt;br /&gt;
You should be careful of how you create and use instance variables in PHPUnit tests for two main reasons:&lt;br /&gt;
&lt;br /&gt;
Firstly, if you create any fixtures in the setUp, or call the resetAfterTest function, these fixtures and conditions will apply for _all_ tests in the testsuite.&lt;br /&gt;
You will not be able to add another test to the suite which does not require these conditions without those conditions being fulfilled anyway.&lt;br /&gt;
This can lead to slow tests.&lt;br /&gt;
&lt;br /&gt;
Secondly, because of the way in which PHPUnit operates. it creates an instance of each testcase during its bootstrap phase. These are stored in memory until the _entire suite_ completes.&lt;br /&gt;
This means that any fixture which is setup and not actively discarded will not be garbage collected and lead to memory bloat.&lt;br /&gt;
In severe cases this can lead to memory exhaustion.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Make use of the dataProvider functionality==&lt;br /&gt;
&lt;br /&gt;
The dataProvider functionality of PHPUnit is an extremely powerful and useful feature which allows you to verify a function quickly and easily with a range of different conditions.&lt;br /&gt;
However, the following rules should be followed when using dataProviders:&lt;br /&gt;
* Keep addition of resettable data requring resetAfterTest to a minimum - this will lead to many slow tests&lt;br /&gt;
* Data providers &#039;&#039;&#039;must not instantiate/create data&#039;&#039;&#039;. Just define it. And then, the test body can proceed with the instantiation/creation. The dataProvider is called after the testSuite is instantiated, but before any tests are run. Each test will run a full setUp and tearDown, which will destroy any data which was created.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Test function accepts parameters passed from the specified data provider.&lt;br /&gt;
 *&lt;br /&gt;
 * @dataProvider foobar_provider&lt;br /&gt;
 * @param int $foor&lt;br /&gt;
 * @param int $bar&lt;br /&gt;
 */&lt;br /&gt;
public function test_foobar(int $foo, int $bar) {&lt;br /&gt;
    // Perform the tests here.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Data provider for {@see self::test_foobar()}.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array List of data sets - (string) data set name =&amp;gt; (array) data&lt;br /&gt;
 */&lt;br /&gt;
public function foobar_provider(): array {&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;Same numbers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;foo&#039; =&amp;gt; 42,&lt;br /&gt;
            &#039;bar&#039; =&amp;gt; 42,&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;Different numbers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;foo&#039; =&amp;gt; 21,&lt;br /&gt;
            &#039;bar&#039; =&amp;gt; 84,&lt;br /&gt;
        ],&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Extra test settings=&lt;br /&gt;
&lt;br /&gt;
Usually the test should not interact with any external systems and it should work the same on all systems. But sometimes you need to specify some option for connection to external systems or system configuration. It is intentionally not possible to use $CFG settings from config.php.&lt;br /&gt;
&lt;br /&gt;
There are several ways how to inject your custom settings:&lt;br /&gt;
* define test setting constants in your phpunit.xml file&lt;br /&gt;
* define test setting constants in your config.php&lt;br /&gt;
&lt;br /&gt;
These constants may be then used in your test or plugin code.&lt;br /&gt;
&lt;br /&gt;
=Upgrading unit tests to work with Moodle 3.11 and up (PHPUnit 9.5)=&lt;br /&gt;
{{Moodle 3.11}}&lt;br /&gt;
&lt;br /&gt;
With Moodle 3.11, &#039;&#039;&#039;PHPUnit was upgraded to 9.5&#039;&#039;&#039; (from 8.5 being used in previous versions). This was done to &#039;&#039;&#039;better align&#039;&#039;&#039; the testing environment with PHP versions supported by Moodle 3.11 (7.3, 7.4 and [[Moodle and PHP|8.0]]) (see MDL-71036 and linked issues for more details).&lt;br /&gt;
&lt;br /&gt;
While a lot of existing tests will work without modification with PHPUnit 9.5, you will get a &#039;&#039;&#039;good number of deprecation warnings&#039;&#039;&#039; (&amp;quot;W&amp;quot; in the tests output) that &#039;&#039;&#039;should be replaced by their new counterparts as soon as possible&#039;&#039;&#039;, because all those warnings will become errors with next PHPUnit upgrade.&lt;br /&gt;
&lt;br /&gt;
To find more information about the changes coming with PHPUnit 9.5, it&#039;s recommended to read the following resources:&lt;br /&gt;
&lt;br /&gt;
* [https://thephp.cc/news/2020/02/migrating-to-phpunit-9 A good article] explaining all the main changes in the release.&lt;br /&gt;
* [https://phpunit.de/announcements/phpunit-9.html PHPUnit 9 release Announcement].&lt;br /&gt;
* These multiple detailed changelogs (because all them have included modifications requiring changes): [https://github.com/sebastianbergmann/phpunit/blob/9.0.0/ChangeLog-9.0.md 9.0], [https://github.com/sebastianbergmann/phpunit/blob/9.1.0/ChangeLog-9.1.md 9.1], [https://github.com/sebastianbergmann/phpunit/blob/9.3.0/ChangeLog-9.3.md 9.3], [https://github.com/sebastianbergmann/phpunit/blob/9.5.0/ChangeLog-9.5.md 9.5]&lt;br /&gt;
* [https://phpunit.readthedocs.io/en/9.5/ Official PHPUnit manual].&lt;br /&gt;
&lt;br /&gt;
A good summary of all the &#039;&#039;&#039;changes and replacements to perform&#039;&#039;&#039; is available in the [https://github.com/moodle/moodle/blob/e3a46964dc6d8ca1558c6e1e8dfdf3c1745eeaed/lib/upgrade.txt#L5-L65 lib/upgrade.txt] file. With main points being:&lt;br /&gt;
* All the changes that were deprecated with PHPUnit 8.5 (see the section below) are now removed and will lead to errors.&lt;br /&gt;
* &amp;lt;tt&amp;gt;assertContains()&amp;lt;/tt&amp;gt; now performs stricter comparison (like &amp;lt;tt&amp;gt;assertSame()&amp;lt;/tt&amp;gt; does). New &amp;lt;tt&amp;gt;assertContainsEquals()&amp;lt;/tt&amp;gt; has been created to provide the old behavior.&lt;br /&gt;
* Changes to the &amp;lt;tt&amp;gt;phpunit.xml&amp;lt;/tt&amp;gt; schema, mostly internal. These only will impact if you are using custom &amp;lt;tt&amp;gt;phpunit.xml&amp;lt;/tt&amp;gt; files:&lt;br /&gt;
** The previous &amp;lt;tt&amp;gt;&amp;lt;filter&amp;gt;&amp;lt;/tt&amp;gt; section is now (better) called &amp;lt;tt&amp;gt;&amp;lt;coverage&amp;gt;&amp;lt;/tt&amp;gt;. And, within it:&lt;br /&gt;
*** &amp;lt;tt&amp;gt;&amp;lt;whitelist&amp;gt;&amp;lt;/tt&amp;gt; has been replaced by &amp;lt;tt&amp;gt;&amp;lt;include&amp;gt;&amp;lt;/tt&amp;gt;.&lt;br /&gt;
*** &amp;lt;tt&amp;gt;&amp;lt;exclude&amp;gt;&amp;lt;/tt&amp;gt; is not a child of &amp;lt;tt&amp;gt;&amp;lt;whitelist&amp;gt;&amp;lt;/tt&amp;gt; anymore, but of &amp;lt;tt&amp;gt;&amp;lt;coverage&amp;gt;&amp;lt;/tt&amp;gt;.&lt;br /&gt;
** But with implications when defining the [[#Check your coverage|coverage information]] because &amp;lt;tt&amp;gt;$whitelistxxx&amp;lt;/tt&amp;gt; properties used by the &amp;lt;tt&amp;gt;coverage.php&amp;lt;/tt&amp;gt; files have been deprecated, instead use &amp;lt;tt&amp;gt;includelistfolders&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;includelistfiles&amp;lt;/tt&amp;gt; (to better map the elements in the xml).&lt;br /&gt;
* Warning: It&#039;s not possible to run individual test files any more. Use any of the alternative execution methods (filter, suite, config) to specify which tests you want to run. This will be hopefully fixed in MDL-71049 once it has been agreed which the best way to proceed is.&lt;br /&gt;
* Deprecations, deprecations, deprecations. Lots of them in often used assertions: file assertions, regexp assertions, exception expectations... again, note that all them will become errors with the next PHPUnit update, so &#039;&#039;&#039;the recommendation is to update them ASAP&#039;&#039;&#039;.&lt;br /&gt;
Finally, it&#039;s also a good idea to [https://github.com/moodle/moodle/compare/fc335f5...713722c browse the changes associated with the issue], hopefully with useful explanations in the commit messages.&lt;br /&gt;
&lt;br /&gt;
=Upgrading unit tests to work with Moodle 3.10 and up (PHPUnit 8.5)=&lt;br /&gt;
{{Moodle 3.10}}&lt;br /&gt;
&lt;br /&gt;
With Moodle 3.10, &#039;&#039;&#039;PHPUnit was upgraded to 8.5&#039;&#039;&#039; (from 7.5 being used in older version). This was done to &#039;&#039;&#039;better align&#039;&#039;&#039; the testing environment with PHP versions supported by Moodle 3.10 (7.2, 7.3 and 7.4) and also to provide an &#039;&#039;&#039;easier jump to PHPUnit 9.x&#039;&#039;&#039; that will be needed for Moodle 3.11 (php versions 7.3, 7.4 and 8.0), removing a lot of deprecated stuff in advance. (see MDL-67673 and MDL-64600 for more details).&lt;br /&gt;
&lt;br /&gt;
While 99% of existing tests will work without modification with PHPUnit 8.5, you will get a &#039;&#039;&#039;good number of deprecation warnings&#039;&#039;&#039; (&amp;quot;W&amp;quot; in the tests output) that &#039;&#039;&#039;should be replaced by their new counterparts as soon as possible&#039;&#039;&#039;, because all those warnings will become errors with next PHPUnit upgrade.&lt;br /&gt;
&lt;br /&gt;
To find more information about the changes coming with PHPUnit 8.5, it&#039;s recommended to read the following resources:&lt;br /&gt;
&lt;br /&gt;
* [https://thephp.cc/news/2019/02/help-my-tests-stopped-working A good article] explaining all the main changes in the release.&lt;br /&gt;
* [https://phpunit.de/announcements/phpunit-8.html PHPUnit 8 release Announcement].&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/blob/130104cf796a88dd1547dc5beb8bd555c2deb55e/ChangeLog-8.0.md Detailed changelog of the release].&lt;br /&gt;
* [https://phpunit.readthedocs.io/en/8.5/ Official PHPUnit manual].&lt;br /&gt;
* A good summary of all the &#039;&#039;&#039;changes and replacements to perform&#039;&#039;&#039; is available in the [https://github.com/moodle/moodle/blob/6594c54b2eef62499d304bfa0939999e3a14246e/lib/upgrade.txt#L5-L37 lib/upgrade.txt] file. With main points being:&lt;br /&gt;
** Support for PHP 7.0 dropped (because all template methods (&amp;lt;tt&amp;gt;setUp()&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;tearDown()&amp;lt;/tt&amp;gt;..) now require to return void. This will mostly impact 3rd-part plugins that were still running the same tests against old branches of Moodle with PHP 7.0 support.&lt;br /&gt;
** &amp;lt;tt&amp;gt;PHPUnit/DBUnit&amp;lt;/tt&amp;gt; has been removed are replaced by a lightweight alternative.&lt;br /&gt;
** Deprecations, deprecations, deprecations. Lots of them in often used assertions (&amp;lt;tt&amp;gt;assertContains()&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;assertEquals()&amp;lt;/tt&amp;gt;...) and also &amp;lt;tt&amp;gt;@expectedExceptionXXX&amp;lt;/tt&amp;gt; annotations. Again, note that all them will become errors with PHPUnit 9.&lt;br /&gt;
* Finally, it&#039;s also a good idea to [https://github.com/moodle/moodle/compare/5903054...b13ec3c browse the changes associated with the issue], hopefully with useful enough explanations in the commit messages.&lt;br /&gt;
&lt;br /&gt;
=Upgrading unit tests to work with Moodle 3.7 and up (PHPUnit 7.5)=&lt;br /&gt;
{{Moodle 3.7}}&lt;br /&gt;
&lt;br /&gt;
With Moodle 3.7, &#039;&#039;&#039;PHPUnit was upgraded to 7.5&#039;&#039;&#039; (from 6.x being used in older version). This was done to better align the testing environment with PHP versions supported by Moodle 3.7 (7.1, 7.2 and 7.3). (see MDL-65204 and linked issues for more details). While internally [https://phpunit.de/announcements/phpunit-7.html a lot of things changed with PHPUnit 7] (PHP 7.1-isms, typed signatures, void returns, assertEquals() changes), thanks to our &#039;&#039;&#039;wrapping layer&#039;&#039;&#039; (basic and advanced testcases...) impact expected into old existing unit tests is expected to be reduced and upgrades, easy to achieve.&lt;br /&gt;
&lt;br /&gt;
To find more information about the changes coming with PHPUnit 7, it&#039;s recommended to read the following resources:&lt;br /&gt;
* [https://phpunit.de/announcements/phpunit-7.html  PHPUnit 7 release Announcement].&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/blob/520723129e2b3fc1dc4c0953e43c9d40e1ecb352/ChangeLog-7.5.md Detailed changelog of the release], paying special attention to the added, deprecated, changed and deleted sections.&lt;br /&gt;
* [https://phpunit.readthedocs.io/en/7.5/ Official PHPUnit manual].&lt;br /&gt;
* Changes performed into core, mainly: new, stricter, signatures ([https://github.com/moodle/moodle/commit/26218b7 26218b7]) and assertEquals() changes, specially important when comparing strings, now performed using strict (===) equals ([https://github.com/moodle/moodle/commit/85f47ba 85f47ba]).&lt;br /&gt;
&lt;br /&gt;
=Upgrading unit tests to work with Moodle 3.4 and up (PHPUnit 6)=&lt;br /&gt;
{{Moodle 3.4}}&lt;br /&gt;
&lt;br /&gt;
With Moodle 3.4, &#039;&#039;&#039;PHPUnit was upgraded to 6.4&#039;&#039;&#039; (from 5.5 being used in older version). This was done to better align the testing environment with PHP versions supported by Moodle 3.4 (7.0, 7.1 and 7.2). (see MDL-60611 and linked issues for more details). While internally [https://github.com/sebastianbergmann/phpunit/wiki/Release-Announcement-for-PHPUnit-6.0.0#backwards-compatibility-issues a lot of things changed with PHPUnit 6] (namespaced classes being the more noticeable), thanks to our &#039;&#039;&#039;wrapping layer&#039;&#039;&#039; (basic and advanced testcases...) impact expected into old existing unit tests is expected to be reduced and upgrades, easy to achieve.&lt;br /&gt;
&lt;br /&gt;
Still, in &#039;&#039;&#039;some cases, it will impossible to maintain compatibility&#039;&#039;&#039; of tests between old (pre 3.4) tests and new ones, especially &#039;&#039;&#039;when direct use of any phpunit class is performed&#039;&#039;&#039;. Luckily, both travis and CI tests will detect this situation and it shouldn&#039;t be hard to keep all supported branches in core passing ok. Plugins may be trickier, if the same branch is attempting to work against multiple core branches and they are using some phpunit class directly.&lt;br /&gt;
&lt;br /&gt;
To find more information about the changes coming with PHPUnit 6, it&#039;s recommended to read the following resources:&lt;br /&gt;
* [https://thephp.cc/news/2017/02/migrating-to-phpunit-6 A very good mini-guide] showing all the important changes.&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/wiki/Release-Announcement-for-PHPUnit-6.0.0#backwards-compatibility-issues PHPUnit 6 release Announcement].&lt;br /&gt;
* [https://github.com/sebastianbergmann/phpunit/blob/9d0c024d2099531442d862b66b0ad7cf35ed8e78/ChangeLog-6.0.md Detailed changelog of the release], paying special attention to the changed and deleted sections.&lt;br /&gt;
* Changes performed into core, mainly: namespace class renaming ([https://github.com/moodle/moodle/commit/801a372dadb6e11c8781547603e3f0a59ce5638f 801a372]) and deprecated stuff ([https://github.com/moodle/moodle/commit/796e48a58bf18533bdca423fff7949ab119101c4 796e48a])&lt;br /&gt;
&lt;br /&gt;
=See also=&lt;br /&gt;
* [[PHPUnit integration]]&lt;br /&gt;
* [[PHPUnit]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=PHPUnit&amp;diff=61554</id>
		<title>PHPUnit</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=PHPUnit&amp;diff=61554"/>
		<updated>2021-11-30T15:43:55Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* PHPUnit versions */  Add Links to documentation&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.3}}&lt;br /&gt;
=What is PHPUnit=&lt;br /&gt;
PHPUnit by Sebastian Bergmann is an advanced unit testing framework for PHP. It is installed as Composer dependency and is not part of Moodle installation. To run PHPUnit tests, you have to manually install it on your development computer or test server.&lt;br /&gt;
&lt;br /&gt;
Read the excellent guide at&lt;br /&gt;
* [https://phpunit.de/documentation.html PHPUnit Manual]&lt;br /&gt;
=Installation of PHPUnit via Composer=&lt;br /&gt;
* Install Composer&lt;br /&gt;
Instructions for installing composer on all platforms are here: https://getcomposer.org/download/&lt;br /&gt;
&lt;br /&gt;
Install the composer.phar file to your moodle folder.&lt;br /&gt;
* Execute Composer installer&lt;br /&gt;
 cd /your/moodle/dirroot&lt;br /&gt;
&lt;br /&gt;
 php composer.phar install&lt;br /&gt;
(If that gives you connection problems try...)&lt;br /&gt;
 php composer.phar install --prefer-source&lt;br /&gt;
Troubleshooting:&lt;br /&gt;
* On Windows if you are behind a proxy you will need to setup an environment variable called HTTP_PROXY with a value detailing your HTTP Proxy address and port before composer will correctly download files.&lt;br /&gt;
* You may be prompted for github credentials when installing composer dependencies.&lt;br /&gt;
** This is used to generate an personal access token to avoid being rate limited by github.&lt;br /&gt;
** If you have Two Factor Authentication enabled on your github account, or do not wish to supply your own credentials you will need to generate a token manually:&lt;br /&gt;
*** Visit https://github.com/settings/applications and request personal access token&lt;br /&gt;
*** Copy this token and add it to your [https://gist.github.com/andrewnicols/c5377ed25a9df1006ce1 ~/.composer/config.json]&lt;br /&gt;
** ( See [https://github.com/composer/composer/issues/2280 composer issue #2280] for further details of this bug.)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Detailed instructions:&lt;br /&gt;
* [http://getcomposer.org/doc/00-intro.md Composer documentation]&lt;br /&gt;
Detailed instructions:&lt;br /&gt;
* [[PHPUnit installation in Windows]]&lt;br /&gt;
* [[PHPUnit installation in OS X]]&lt;br /&gt;
== Uninstalling previous PEAR based version ==&lt;br /&gt;
Before using composer, this page used to suggest to install phpunit via PEAR. If you did so, you may wish to uninstall that package now. This should work:&lt;br /&gt;
   $ pear uninstall phpunit/DbUnit&lt;br /&gt;
   $ pear uninstall phpunit/PHPUnit&lt;br /&gt;
= PHPUnit versions =&lt;br /&gt;
The following table shows what PHPUnit version is installed in which Moodle version when using the default composer setup.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Moodle version&lt;br /&gt;
! PHPUnit version&lt;br /&gt;
!Links&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.11&lt;br /&gt;
| PHPUnit 9.5&lt;br /&gt;
|[https://phpunit.readthedocs.io/en/9.5/ Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.10&lt;br /&gt;
| PHPUnit 8.5&lt;br /&gt;
|[https://phpunit.readthedocs.io/en/8.5/ Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.7 - 3.9&lt;br /&gt;
| PHPUnit 7.5&lt;br /&gt;
|[https://phpunit.readthedocs.io/en/7.5/ Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.4 - 3.6&lt;br /&gt;
| PHPUnit 6.5&lt;br /&gt;
|[https://phpunit.de/manual/6.5/en/ Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.2 - 3.3&lt;br /&gt;
| PHPUnit 5.5&lt;br /&gt;
|[https://phpunit.de/manual/5.5/en/ Documentation]&lt;br /&gt;
|-&lt;br /&gt;
| Moodle 3.1&lt;br /&gt;
| PHPUnit 4.8.27&lt;br /&gt;
|[https://phpunit.de/manual/4.8/en/ Documention 4.8]&lt;br /&gt;
|}&lt;br /&gt;
=Initialisation of test environment=&lt;br /&gt;
Our PHPUnit integration requires a dedicated database and dataroot. First, add a new dataroot directory and prefix into your config.php, you can find examples in config-dist.php (scroll down to &#039;Section 9&#039;).&lt;br /&gt;
 $CFG-&amp;gt;phpunit_prefix = &#039;phpu_&#039;;&lt;br /&gt;
 $CFG-&amp;gt;phpunit_dataroot = &#039;/home/example/phpu_moodledata&#039;;&lt;br /&gt;
Some PHPUnit tests require a live internet connection. If your system does not have a direct connection to the Internet, you also need to specify your proxy in config.php - even though you normally specify it by using the admin settings user interface. (If you do not use a proxy, you can skip this step.) Check the settings on the relevant admin settings page, or from the mdl_config table in your database, if you are unsure of the correct values.&lt;br /&gt;
 // Normal proxy settings&lt;br /&gt;
 $CFG-&amp;gt;proxyhost = &#039;wwwcache.example.org&#039;;&lt;br /&gt;
 $CFG-&amp;gt;proxyport = 80;&lt;br /&gt;
 $CFG-&amp;gt;proxytype = &#039;HTTP&#039;;&lt;br /&gt;
 $CFG-&amp;gt;proxybypass = &#039;localhost, 127.0.0.1, .example.org&#039;;&lt;br /&gt;
 // Omit the next lines if your proxy doesn&#039;t need a username/password:&lt;br /&gt;
 $CFG-&amp;gt;proxyuser = &#039;systemusername&#039;;&lt;br /&gt;
 $CFG-&amp;gt;proxypassword = &#039;systempassword&#039;;&lt;br /&gt;
From Moodle 2.8.5 onwards, you can also provide specific database settings for unit testing (if these are not provided, the standard database settings will be used):&lt;br /&gt;
&amp;lt;pre&amp;gt;$CFG-&amp;gt;phpunit_dbtype    = &#039;pgsql&#039;;      // &#039;pgsql&#039;, &#039;mariadb&#039;, &#039;mysqli&#039;, &#039;mssql&#039;, &#039;sqlsrv&#039; or &#039;oci&#039;&lt;br /&gt;
$CFG-&amp;gt;phpunit_dblibrary = &#039;native&#039;;     // &#039;native&#039; only at the moment&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbhost    = &#039;127.0.0.1&#039;;  // eg &#039;localhost&#039; or &#039;db.isp.com&#039; or IP&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbname    = &#039;mytestdb&#039;;     // database name, eg moodle&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbuser    = &#039;postgres&#039;;   // your database username&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbpass    = &#039;some_password&#039;;   // your database password&amp;lt;/pre&amp;gt;&lt;br /&gt;
Then you need to initialise the test environment using following command.&lt;br /&gt;
 cd /home/example/moodle&lt;br /&gt;
 php admin/tool/phpunit/cli/init.php&lt;br /&gt;
This command has to be repeated after any upgrade, plugin (un)installation or if you have added tests to a plugin you are developing:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; make sure that your php cli executable (or the one you want to use) is correctly on your path as the individual init scripts will call it repeatedly. Also, ensure en_AU locale is installed on your server.&lt;br /&gt;
== LDAP ==&lt;br /&gt;
If you want to run LDAP unit tests you must have a working, configured LDAP environment on your test server. There must be a basic LDAP tree structure in place or tests will fail with &amp;quot;Search: No such object&amp;quot;. Build an LDAP tree structure in a new shell prompt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ ldapadd -H ldap://127.0.0.1 -D &amp;quot;cn=admin,dc=yourcomputer,dc=local&amp;quot; -W&lt;br /&gt;
dn:dc=yourcomputer,dc=local&lt;br /&gt;
objectClass:dcObject&lt;br /&gt;
objectClass:organizationalUnit&lt;br /&gt;
dc:yourcomputer&lt;br /&gt;
ou:YOURCOMPUTER&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
In config.php tell Moodle where to look for your test LDAP environment:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Constants for auth/ldap tests.&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_HOST_URL&#039;, &#039;ldap://127.0.0.1&#039;);&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_BIND_DN&#039;, &#039;cn=admin,dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_BIND_PW&#039;, &#039;*&#039;);&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_DOMAIN&#039;, &#039;dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
&lt;br /&gt;
// Constants for enrol/ldap tests.&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_HOST_URL&#039;, &#039;ldap://127.0.0.1&#039;);&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_BIND_DN&#039;, &#039;cn=admin,dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_BIND_PW&#039;, &#039;*&#039;);&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_DOMAIN&#039;, &#039;dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
&lt;br /&gt;
// Constants for lib/ldap tests.&lt;br /&gt;
define(&#039;TEST_LDAPLIB_HOST_URL&#039;, &#039;ldap://127.0.0.1&#039;);&lt;br /&gt;
define(&#039;TEST_LDAPLIB_BIND_DN&#039;, &#039;cn=admin,dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
define(&#039;TEST_LDAPLIB_BIND_PW&#039;, &#039;*&#039;);&lt;br /&gt;
define(&#039;TEST_LDAPLIB_DOMAIN&#039;, &#039;dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
=Test execution=&lt;br /&gt;
To execute all test suites from main configuration file execute the &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit&amp;lt;/syntaxhighlight&amp;gt; script from your &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;$CFG-&amp;gt;dirroot&amp;lt;/syntaxhighlight&amp;gt; directory.&lt;br /&gt;
 cd /home/example/moodle&lt;br /&gt;
 vendor/bin/phpunit&lt;br /&gt;
The rest of examples uses &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;phpunit&amp;lt;/syntaxhighlight&amp;gt;, please substitute it with &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;vendor/bin/phpunit&amp;lt;/syntaxhighlight&amp;gt; or create a shortcut in your dirroot.&lt;br /&gt;
In IDEs, you may need to specify the path to the PHPUnit configuration file. Use the absolute path to &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;phpunit.xml&amp;lt;/syntaxhighlight&amp;gt; from your &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;$CFG-&amp;gt;dirroot&amp;lt;/syntaxhighlight&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is an alternative script for running of tests via web interface: &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;admin/tool/phpunit/webrunner.php&amp;lt;/syntaxhighlight&amp;gt;. Use this as the last resort only when you cannot use the command-line interface on your test server. It will most probably break due to permissions problems if you try to execute it both from command-line and from webrunner. This feature is not officially supported.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===How to run only some tests===&lt;br /&gt;
==== Running a single test quickly (PHPUnit 9) ====&lt;br /&gt;
{{Moodle 3.11}}&lt;br /&gt;
The fastest way to run a single test in PHPUnit 9.5 and higher (Moodle 3.11 and higher) is to use the filter argument:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --filter tool_dataprivacy_metadata_registry_testcase&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
To run all tests provided by the single component, use suite and the name it has in the phpunit.xml file. Example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --testsuite workshopform_accumulative_testsuite&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Alternatively if you have config files built for each component:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit -c mod/workshop/form/accumulative/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Running a single test quickly (PHPUnit 8) ====&lt;br /&gt;
{{Moodle 3.10}}&lt;br /&gt;
The fastest way to run a single test in PHPUnit 8.5 and lower (Moodle 3.10 and lower):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit my/tests/filename.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
so, run this command in the CLI to see a real test in action:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit cohort/tests/cohortlib_test.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also run a single test method inside a class:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --filter test_function_name path/to/file.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note: You should be careful because it may be possible to run tests this way which are not included in the normal run if, for example, the file is not placed in the correct location. If you use this method, do at least one full test run (or --group run, as below) to ensure the test can be found.&lt;br /&gt;
&lt;br /&gt;
Filters can also be applied to capture a group of similar tests across all testsuites:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --filter test_flag_user&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
It is also possible to run all tests in a component (subsystem or plugin) by using the testsuite option:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --testsuite mod_forum_testsuite&lt;br /&gt;
vendor/bin/phpunit --testsuite core_privacy_testsuite&lt;br /&gt;
vendor/bin/phpunit --testsuite core_privacy_testsuite --filter test_component_is_compliant&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Using the @group annotation ====&lt;br /&gt;
If you add annotations like&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
namespace qtype_stack;&lt;br /&gt;
/**&lt;br /&gt;
 * Unit tests for {@link stack_cas_keyval} @ qtype/stack/tests/cas_keyval_test.php.&lt;br /&gt;
 * @group qtype_stack&lt;br /&gt;
 */&lt;br /&gt;
class cas_keyval_test extends \basic_testcase {&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
to all the classes in your plugin, then you can run just the tests for your plugin by doing&lt;br /&gt;
 phpunit --group qtype_stack&lt;br /&gt;
Therefore, it is suggested that you annotate all your tests with the Frankenstyle name of your plugin.&lt;br /&gt;
==== Using multiple phpunit.xml files ====&lt;br /&gt;
It&#039;s easy to create alternative phpunit.xml files defining which tests must be run together. For reference, take a look to the default /phpunit.xml available in your base directory once the testing environment has been initialised. After creating the custom file you will be able to run those tests with&lt;br /&gt;
 vendor/bin/phpunit -c path/to/your/alternative/phpunit/file.xml&lt;br /&gt;
Also, for commodity, you can use this command:&lt;br /&gt;
 php admin/tool/phpunit/cli/util.php --buildcomponentconfigs&lt;br /&gt;
It will, automatically, create one valid phpunit.xml file within each component (plugin or subsystem) and other important directories, so later you will be able to execute tests like&lt;br /&gt;
 vendor/bin/phpunit -c mod/forum[/phpunit.xml]  // Note that it&#039;s not needed to specify the name of the file (if it is &#039;phpunit.xml&#039;).&lt;br /&gt;
 vendor/bin/phpunit -c question&lt;br /&gt;
 vendor/bin/phpunit -c question/type/calculated&lt;br /&gt;
 vendor/bin/phpunit -c backup&lt;br /&gt;
 vendor/bin/phpunit -c lib/dml&lt;br /&gt;
 ...&lt;br /&gt;
or, also&lt;br /&gt;
 cd directory/with/phpunit.xml&lt;br /&gt;
 phpunit&lt;br /&gt;
=External test resources=&lt;br /&gt;
{{Moodle 2.6}}&lt;br /&gt;
By default Moodle phpunit tests contact http://download.moodle.org server when testing curl related functionality. Optionally you may checkout a local copy of the test scripts and access it instead:&lt;br /&gt;
# clone https://github.com/moodlehq/moodle-exttests to web directory&lt;br /&gt;
# add to your config.php or modify phpunit.xml file &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;define(&#039;TEST_EXTERNAL_FILES_HTTP_URL&#039;, &#039;http://localhost/moodle-exttests&#039;);&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=Writing new tests=&lt;br /&gt;
* read [http://www.phpunit.de/manual/current/en/ official PHPUnit online documentation]&lt;br /&gt;
* see [[Writing PHPUnit tests]]&lt;br /&gt;
=Conversion of existing SimpleTests=&lt;br /&gt;
* see [[SimpleTest conversion]]&lt;br /&gt;
=PHPUnit support in IDEs=&lt;br /&gt;
* [[Setting up Eclipse]]&lt;br /&gt;
* [[Setting up Netbeans]]&lt;br /&gt;
* [[Setting up PhpStorm]]&lt;br /&gt;
=Common Unit Test Problems=&lt;br /&gt;
[[Common unit test problems]]&lt;br /&gt;
=Performance=&lt;br /&gt;
A typical run of full PHPUnit tests for Moodle 2.7 takes 10-20 minutes depending on the machine. If tests run slowly for you:&lt;br /&gt;
* Ensure you are using a database and filesystem running on the same machine that is running the tests (not on a remote server).&lt;br /&gt;
* Apply developer-only performance settings to your database: [[Postgres Tuning For Developers]]&lt;br /&gt;
=Command line tips=&lt;br /&gt;
* Run all tests for a plugin (mod_mymodule in this example): vendor/bin/phpunit --testsuite=mod_mymodule_testsuite &lt;br /&gt;
* Run only named test: vendor/bin/phpunit --filter=test_my_test_function_name&lt;br /&gt;
* Display each test name before running it (useful e.g. if it crashes before the end and you want to know which test it was running at that point) vendor/bin/phpunit --debug (you will probably want to redirect this to a file as it gets very long).&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Course_Custom_fields&amp;diff=61466</id>
		<title>Course Custom fields</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Course_Custom_fields&amp;diff=61466"/>
		<updated>2021-10-25T11:55:38Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: Complete 1st Edit session.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction==&lt;br /&gt;
This plugin type allows you to add course custom field types of which instances can be added to a course. For example, if you want to display radio buttons on the course edit page, then create a radio custom course field plugin, then create an instance of it.&lt;br /&gt;
==Default Custom Fields==&lt;br /&gt;
Moodle supports five default course custom fields field plugins:&lt;br /&gt;
#checkbox&lt;br /&gt;
#datetime&lt;br /&gt;
#menu&lt;br /&gt;
#text&lt;br /&gt;
#textarea&lt;br /&gt;
== Required Files ==&lt;br /&gt;
* version.php&lt;br /&gt;
* language file e.g &#039;&#039;lang/en/customfield_radio.php&#039;&#039;&lt;br /&gt;
* classes/data_controller.php&lt;br /&gt;
* classes/field_controller.php&lt;br /&gt;
=== The Class Files ===&lt;br /&gt;
The two required files in the classes folder must follow the [[Automatic class loading|autoloading and namespacing]] rules e.g. &#039;&#039;&#039;&#039;&#039;customfield_radio&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
==== Field Controller ====&lt;br /&gt;
This class is to define the custom field configuration when the user is creating the field.  The class must be called &#039;&#039;&#039;&#039;&#039;field_controller&#039;&#039;&#039;&#039;&#039; (for autoloading)  must extend the &#039;&#039;&#039;&#039;&#039;\core_customfield\field_controller&#039;&#039;&#039;&#039;&#039; class.&lt;br /&gt;
&lt;br /&gt;
The class must define a TYPE constant e.g.&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Plugin type&lt;br /&gt;
*/&lt;br /&gt;
const TYPE = &#039;radio&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== config_form_definition($mform) ======&lt;br /&gt;
The only method that must be overridden is &#039;&#039;&#039;&#039;&#039;config_form_definition()&#039;&#039;&#039;&#039;&#039; which has a Moodle form passed in as the parameter to which it must attached form elements.  Not that for the element values to be saved, the element names must be in the format &#039;&#039;configdata[configname]&#039;&#039; e.g. &#039;&#039;configdata[cfgdefault].&#039;&#039;&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Add fields for editing a radio field in admin.&lt;br /&gt;
*&lt;br /&gt;
* @param \MoodleQuickForm $mform&lt;br /&gt;
*/&lt;br /&gt;
public function config_form_definition(\MoodleQuickForm $mform) {&lt;br /&gt;
	$mform-&amp;gt;addElement(&#039;header&#039;, &#039;header_oursettings&#039;, get_string(&#039;oursettings&#039;, &#039;customfield_radio&#039;));&lt;br /&gt;
	$mform-&amp;gt;setExpanded(&#039;header_oursettings&#039;, true);&lt;br /&gt;
&lt;br /&gt;
	$mform-&amp;gt;addElement(&#039;text&#039;, &#039;configdata[cfgquestion]&#039;, get_string(&#039;getquestionprompt&#039;, &#039;customfield_radio&#039;));&lt;br /&gt;
	$mform-&amp;gt;setType(&#039;configdata[cfgquestion]&#039;, PARAM_TEXT);&lt;br /&gt;
	$mform-&amp;gt;addRule(&#039;configdata[cfgquestion]&#039;, null, &#039;maxlength&#039;, 150, &#039;client&#039;);&lt;br /&gt;
	$mform-&amp;gt;addRule(&#039;configdata[cfgquestion]&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&lt;br /&gt;
	$radioarray=array();&lt;br /&gt;
	$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, &#039;configdata[cfgdefault]&#039;, &#039;&#039;, get_string(&#039;yes&#039;), 1);&lt;br /&gt;
	$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, &#039;configdata[cfgdefault]&#039;, &#039;&#039;, get_string(&#039;no&#039;), 0);&lt;br /&gt;
	$mform-&amp;gt;addGroup($radioarray, &#039;cfgradioar&#039;, get_string(&#039;defaultprompt&#039;, &#039;customfield_radio&#039;), array(&#039; &#039;), false);&lt;br /&gt;
	$mform-&amp;gt;setDefault(&#039;configdata[cfgdefault]&#039;, 0);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;There is another function that is useful to be defined.&lt;br /&gt;
&lt;br /&gt;
====== config_form_validation($formdata, $formfiles) ======&lt;br /&gt;
This function is used to validate the form values from &#039;&#039;&#039;&#039;&#039;config_form_definition().&#039;&#039;&#039;&#039;&#039;  It has the submitted form data and files passed in a parameters.&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Validate the data on the field configuration form&lt;br /&gt;
*&lt;br /&gt;
* @param array $data from the add/edit profile field form&lt;br /&gt;
* @param array $files&lt;br /&gt;
* @return array associative array of error messages&lt;br /&gt;
*/&lt;br /&gt;
public function config_form_validation(array $data, $files = array()) : array {&lt;br /&gt;
	$errors = parent::config_form_validation($data, $files);&lt;br /&gt;
&lt;br /&gt;
	if ($data[&#039;configdata&#039;][&#039;uniquevalues&#039;]) {&lt;br /&gt;
		$errors[&#039;configdata[uniquevalues]&#039;] = get_string(&#039;nouniqueallowed&#039;, &#039;customfield_radio&#039;);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Please see the &#039;&#039;&#039;&#039;&#039;\core_customfield\field_controller&#039;&#039;&#039;&#039;&#039; class defined in &#039;&#039;&#039;/customfield/classes/field_controller.php&#039;&#039;&#039; for other functions.&lt;br /&gt;
&lt;br /&gt;
==== Data Controller ====&lt;br /&gt;
The data controller is the class that deals with the UI in the course edit form. It must be called &#039;&#039;&#039;&#039;&#039;data_controller&#039;&#039;&#039;&#039;&#039; (for autoloading) and must extend the &#039;&#039;&#039;&#039;&#039;\core_customfield\data_controller&#039;&#039;&#039;&#039;&#039; class. It must override the following methods:&lt;br /&gt;
====== datafield() ======&lt;br /&gt;
Should return the name of the field in the db table {customfield_data} where the data is stored and must be one of the following:&lt;br /&gt;
* &#039;&#039;&#039;intvalue&#039;&#039;&#039; - can store integer values, this field is indexed&lt;br /&gt;
* &#039;&#039;&#039;decvalue&#039;&#039;&#039; - can store decimal values&lt;br /&gt;
* &#039;&#039;&#039;shortcharvalue&#039;&#039;&#039; - can store character values up to 255 characters long, this field is indexed&lt;br /&gt;
* &#039;&#039;&#039;charvalue&#039;&#039;&#039; - can store character values up to 1333 characters long, this field is not indexed &lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; - can store character values of unlimited length (&amp;quot;text&amp;quot; field in the db)&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Return the name of the field where the information is stored&lt;br /&gt;
* @return string&lt;br /&gt;
*/&lt;br /&gt;
public function datafield() : string {&lt;br /&gt;
	return &#039;intvalue&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====== instance_form_definition($mform) ======&lt;br /&gt;
Adds a field element to the instance course edit form. The form is passed into this function.&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Add fields for editing a radio field.&lt;br /&gt;
*&lt;br /&gt;
* @param \MoodleQuickForm $mform&lt;br /&gt;
*/&lt;br /&gt;
public function instance_form_definition(\MoodleQuickForm $mform) {&lt;br /&gt;
	$field = $this-&amp;gt;get_field();&lt;br /&gt;
	$config = $field-&amp;gt;get(&#039;configdata&#039;);&lt;br /&gt;
	$elementname = $this-&amp;gt;get_form_element_name();&lt;br /&gt;
&lt;br /&gt;
	$radioarray=array();&lt;br /&gt;
	$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, $elementname, &#039;&#039;, get_string(&#039;yes&#039;), 1);&lt;br /&gt;
	$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, $elementname, &#039;&#039;, get_string(&#039;no&#039;), 0);&lt;br /&gt;
	$mform-&amp;gt;addGroup($radioarray, &#039;cfgradioar&#039;, $config[&#039;cfgquestion&#039;], array(&#039; &#039;), false);&lt;br /&gt;
	$mform-&amp;gt;setDefault($elementname, $config[&#039;cfgdefault&#039;]);&lt;br /&gt;
	$mform-&amp;gt;setType($elementname, PARAM_INT);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Please see the &#039;&#039;&#039;&#039;&#039;\core_customfield\data_controller&#039;&#039;&#039;&#039;&#039; class defined in &#039;&#039;&#039;/customfield/classes/data_controller.php&#039;&#039;&#039; for other functions.&lt;br /&gt;
==See Also==&lt;br /&gt;
* [[User profile fields|User Profile Fields]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Course_Custom_fields&amp;diff=61465</id>
		<title>Course Custom fields</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Course_Custom_fields&amp;diff=61465"/>
		<updated>2021-10-25T11:36:06Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: Initial Edit of this page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction==&lt;br /&gt;
This plugin type allows you to add course custom field types of which instances can be added to a course. For example, if you want to display radio buttons on the course edit page, then create a radio custom course field plugin, then create an instance of it.&lt;br /&gt;
==Default Custom Fields==&lt;br /&gt;
Moodle supports five default course custom fields field plugins:&lt;br /&gt;
#checkbox&lt;br /&gt;
#datetime&lt;br /&gt;
#menu&lt;br /&gt;
#text&lt;br /&gt;
#textarea&lt;br /&gt;
&lt;br /&gt;
== Required Files ==&lt;br /&gt;
&lt;br /&gt;
* version.php&lt;br /&gt;
* language file e.g &#039;&#039;lang/en/customfield_radio.php&#039;&#039;&lt;br /&gt;
* classes/data_controller.php&lt;br /&gt;
* classes/field_controller.php&lt;br /&gt;
&lt;br /&gt;
=== The Class Files ===&lt;br /&gt;
The two required files in the classes folder must follow the [[Automatic class loading|autoloading and namespacing]] rules e.g. &#039;&#039;&#039;&#039;&#039;customfield_radio&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Field Controller ====&lt;br /&gt;
&lt;br /&gt;
==== Data Controller ====&lt;br /&gt;
The data controller is the class that deals with the UI in the course edit form.  It must be called &#039;&#039;&#039;&#039;&#039;data_controller&#039;&#039;&#039;&#039;&#039; (for autoloading) and must extend the &#039;&#039;&#039;&#039;&#039;\core_customfield\data_controller&#039;&#039;&#039;&#039;&#039; class.  It must override the following methods:&lt;br /&gt;
&lt;br /&gt;
====== datafield() ======&lt;br /&gt;
Should return the name of the field in the db table {customfield_data} where the data is stored and must be one of the following:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;intvalue&#039;&#039;&#039; - can store integer values, this field is indexed&lt;br /&gt;
* &#039;&#039;&#039;decvalue&#039;&#039;&#039; - can store decimal values&lt;br /&gt;
* &#039;&#039;&#039;shortcharvalue&#039;&#039;&#039; - can store character values up to 255 characters long, this field is indexed&lt;br /&gt;
* &#039;&#039;&#039;charvalue&#039;&#039;&#039; - can store character values up to 1333 characters long, this field is not indexed &lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; - can store character values of unlimited length (&amp;quot;text&amp;quot; field in the db)&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Return the name of the field where the information is stored&lt;br /&gt;
* @return string&lt;br /&gt;
*/&lt;br /&gt;
public function datafield() : string {&lt;br /&gt;
	return &#039;intvalue&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== instance_form_definition($mform) ======&lt;br /&gt;
Adds a field element to the instance course edit form.  The form is passed into this function.&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
* Add fields for editing a radio field.&lt;br /&gt;
*&lt;br /&gt;
* @param \MoodleQuickForm $mform&lt;br /&gt;
*/&lt;br /&gt;
public function instance_form_definition(\MoodleQuickForm $mform) {&lt;br /&gt;
	$field = $this-&amp;gt;get_field();&lt;br /&gt;
	$config = $field-&amp;gt;get(&#039;configdata&#039;);&lt;br /&gt;
	$elementname = $this-&amp;gt;get_form_element_name();&lt;br /&gt;
&lt;br /&gt;
	$radioarray=array();&lt;br /&gt;
	$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, $elementname, &#039;&#039;, get_string(&#039;yes&#039;), 1);&lt;br /&gt;
	$radioarray[] = $mform-&amp;gt;createElement(&#039;radio&#039;, $elementname, &#039;&#039;, get_string(&#039;no&#039;), 0);&lt;br /&gt;
	$mform-&amp;gt;addGroup($radioarray, &#039;cfgradioar&#039;, $config[&#039;cfgquestion&#039;], array(&#039; &#039;), false);&lt;br /&gt;
	$mform-&amp;gt;setDefault($elementname, $config[&#039;cfgdefault&#039;]);&lt;br /&gt;
	$mform-&amp;gt;setType($elementname, PARAM_INT);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Please see the &#039;&#039;&#039;&#039;&#039;\core_customfield\data_controller&#039;&#039;&#039;&#039;&#039; class defined in &#039;&#039;&#039;/customfield/classes/data_controller.php&#039;&#039;&#039; for other functions.&lt;br /&gt;
&lt;br /&gt;
==See Also==&lt;br /&gt;
&lt;br /&gt;
* [[User profile fields|User Profile Fields]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Plugin_types&amp;diff=61464</id>
		<title>Plugin types</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Plugin_types&amp;diff=61464"/>
		<updated>2021-10-25T10:53:42Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: Fixed Custom Fields text&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Plugins development}}&lt;br /&gt;
The M in Moodle stands for modular. The easiest and most maintainable way to add new functionality to Moodle is by writing one of these types of plugin. &lt;br /&gt;
== List of Moodle plugin types ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Plugin type&lt;br /&gt;
! Component name ([[Frankenstyle]])&lt;br /&gt;
! Moodle path&lt;br /&gt;
! Description&lt;br /&gt;
! Moodle versions&lt;br /&gt;
|-&lt;br /&gt;
| [[Activity modules]]&lt;br /&gt;
| mod&lt;br /&gt;
| /mod&lt;br /&gt;
| Activity modules are essential types of plugins in Moodle as they provide activities in courses. For example: Forum, Quiz and Assignment.&lt;br /&gt;
| 1.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Antivirus plugins]]&lt;br /&gt;
| antivirus&lt;br /&gt;
| /lib/antivirus&lt;br /&gt;
| Antivirus scanner plugins provide functionality for virus scanning user uploaded files using third-party virus scanning tools in Moodle. For example: ClamAV.&lt;br /&gt;
| 3.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Assign_submission_plugins|Assignment submission plugins]]&lt;br /&gt;
| assignsubmission&lt;br /&gt;
| /mod/assign/submission&lt;br /&gt;
| Different forms of assignment submissions&lt;br /&gt;
| 2.3+&lt;br /&gt;
|-&lt;br /&gt;
| [[Assign_feedback_plugins|Assignment feedback plugins]]&lt;br /&gt;
| assignfeedback&lt;br /&gt;
| /mod/assign/feedback&lt;br /&gt;
| Different forms of assignment feedbacks&lt;br /&gt;
| 2.3+&lt;br /&gt;
|-&lt;br /&gt;
| [[Book tools]]&lt;br /&gt;
| booktool&lt;br /&gt;
| /mod/book/tool&lt;br /&gt;
| Small information-displays or tools that can be moved around pages&lt;br /&gt;
| 2.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Course Custom fields]]&lt;br /&gt;
| customfield&lt;br /&gt;
| /customfield/field&lt;br /&gt;
| Custom field types, used e. g. in Custom course fields&lt;br /&gt;
| 3.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[Database fields]]&lt;br /&gt;
| datafield&lt;br /&gt;
| /mod/data/field&lt;br /&gt;
| Different types of data that may be added to the Database activity module&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Database presets]]&lt;br /&gt;
| datapreset&lt;br /&gt;
| /mod/data/preset&lt;br /&gt;
| Pre-defined templates for the Database activity module&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[External tool source|LTI sources]]&lt;br /&gt;
| ltisource&lt;br /&gt;
| /mod/lti/source&lt;br /&gt;
| LTI providers can be added to external tools easily through the external tools interface see [https://docs.moodle.org/en/External_tool Documentation on External Tools]. This type of plugin is specific to LTI providers that need a plugin that can register custom handlers to process LTI messages&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[File Converters]]&lt;br /&gt;
| fileconverter&lt;br /&gt;
| /files/converter&lt;br /&gt;
| Allow conversion between different types of user-submitted file. For example from .doc to PDF.&lt;br /&gt;
| 3.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[LTI services]]&lt;br /&gt;
| ltiservice&lt;br /&gt;
| /mod/lti/service&lt;br /&gt;
| Allows the implementation of LTI services as described by the IMS LTI specification&lt;br /&gt;
| 2.8+&lt;br /&gt;
|-&lt;br /&gt;
| [[Machine learning backends]]&lt;br /&gt;
| mlbackend&lt;br /&gt;
| /lib/mlbackend&lt;br /&gt;
| Prediction processors for analytics API&lt;br /&gt;
| 3.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Forum reports]]&lt;br /&gt;
| forumreport&lt;br /&gt;
| /mod/forum/report&lt;br /&gt;
| Display various reports in the forum activity&lt;br /&gt;
| 3.8+&lt;br /&gt;
|-&lt;br /&gt;
| [[Quiz reports]]&lt;br /&gt;
| quiz&lt;br /&gt;
| /mod/quiz/report&lt;br /&gt;
| Display and analyse the results of quizzes, or just plug miscellaneous behaviour into the quiz module&lt;br /&gt;
| 1.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Quiz access rules]]&lt;br /&gt;
| quizaccess&lt;br /&gt;
| /mod/quiz/accessrule&lt;br /&gt;
| Add conditions to when or where quizzes can be attempted, for example only from some IP addresses, or student must enter a password first&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[SCORM reports]]&lt;br /&gt;
| scormreport&lt;br /&gt;
| /mod/scorm/report&lt;br /&gt;
| Analysis of SCORM attempts&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Workshop grading strategies]]&lt;br /&gt;
| workshopform&lt;br /&gt;
| /mod/workshop/form&lt;br /&gt;
| Define the type of the grading form and implement the calculation of the grade for submission in the [[Workshop]] module&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Workshop allocation methods]]&lt;br /&gt;
| workshopallocation&lt;br /&gt;
| /mod/workshop/allocation&lt;br /&gt;
| Define ways how submissions are assigned for assessment in the [[Workshop]] module&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Workshop evaluation methods]]&lt;br /&gt;
| workshopeval&lt;br /&gt;
| /mod/workshop/eval&lt;br /&gt;
| Implement the calculation of the grade for assessment (grading grade) in the [[Workshop]] module&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Blocks]]&lt;br /&gt;
| block&lt;br /&gt;
| /blocks&lt;br /&gt;
| Small information-displays or tools that can be moved around pages&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Question types]]&lt;br /&gt;
| qtype&lt;br /&gt;
| /question/type&lt;br /&gt;
| Different types of question (e.g. multiple-choice, drag-and-drop) that can be used in quizzes and other activities&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Question behaviours]]&lt;br /&gt;
| qbehaviour&lt;br /&gt;
| /question/behaviour&lt;br /&gt;
| Control how student interact with questions during an attempt&lt;br /&gt;
| 2.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Question formats|Question import/export formats]]&lt;br /&gt;
| qformat&lt;br /&gt;
| /question/format&lt;br /&gt;
| Import and export question definitions to/from the question bank&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Filters|Text filters]]&lt;br /&gt;
| filter&lt;br /&gt;
| /filter&lt;br /&gt;
| Automatically convert, highlight, and transmogrify text posted into Moodle.&lt;br /&gt;
| 1.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Editors]]&lt;br /&gt;
| editor&lt;br /&gt;
| /lib/editor&lt;br /&gt;
| Alternative text editors for editing content&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Atto|Atto editor plugins]]&lt;br /&gt;
| atto&lt;br /&gt;
| /lib/editor/atto/plugins&lt;br /&gt;
| Extra functionality for the Atto text editor&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[TinyMCE editor plugins]]&lt;br /&gt;
| tinymce&lt;br /&gt;
| /lib/editor/tinymce/plugins&lt;br /&gt;
| Extra functionality for the TinyMCE text editor.&lt;br /&gt;
| 2.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Enrolment plugins]]&lt;br /&gt;
| enrol&lt;br /&gt;
| /enrol&lt;br /&gt;
| Ways to control who is enrolled in courses&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Authentication plugins]]&lt;br /&gt;
| auth&lt;br /&gt;
| /auth&lt;br /&gt;
| Allows connection to external sources of authentication&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Admin tools]]&lt;br /&gt;
| tool&lt;br /&gt;
| /admin/tool&lt;br /&gt;
| Provides utility scripts useful for various site administration and maintenance tasks&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Log stores]]&lt;br /&gt;
| logstore&lt;br /&gt;
| /admin/tool/log/store&lt;br /&gt;
| Event logs storage back-ends&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[Availability conditions]]&lt;br /&gt;
| availability&lt;br /&gt;
| /availability/condition&lt;br /&gt;
| Conditions to restrict user access to activities and sections.&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[Calendar types]]&lt;br /&gt;
| calendartype&lt;br /&gt;
| /calendar/type&lt;br /&gt;
| Defines how dates are displayed throughout Moodle&lt;br /&gt;
| 2.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Messaging consumers]]&lt;br /&gt;
| message&lt;br /&gt;
| /message/output&lt;br /&gt;
| Represent various targets where messages and notifications can be sent to (email, sms, jabber, ...)&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Course formats]]&lt;br /&gt;
| format&lt;br /&gt;
| /course/format&lt;br /&gt;
| Different ways of laying out the activities and blocks in a course&lt;br /&gt;
| 1.3+&lt;br /&gt;
|-&lt;br /&gt;
| [[Data formats]]&lt;br /&gt;
| dataformat&lt;br /&gt;
| /dataformat&lt;br /&gt;
| Formats for data exporting and downloading&lt;br /&gt;
| 3.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[User profile fields]]&lt;br /&gt;
| profilefield&lt;br /&gt;
| /user/profile/field&lt;br /&gt;
| Add new types of data to user profiles&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Reports]]&lt;br /&gt;
| report&lt;br /&gt;
| /report&lt;br /&gt;
| Provides useful views of data in a Moodle site for admins and teachers&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Course reports]]&lt;br /&gt;
| coursereport&lt;br /&gt;
| /course/report&lt;br /&gt;
| Reports of activity within the course&lt;br /&gt;
| Up to 2.1 (for 2.2+ see [[Reports]])&lt;br /&gt;
|-&lt;br /&gt;
| [[Gradebook export]]&lt;br /&gt;
| gradeexport&lt;br /&gt;
| /grade/export&lt;br /&gt;
| Export grades in various formats&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Gradebook import]]&lt;br /&gt;
| gradeimport&lt;br /&gt;
| /grade/import&lt;br /&gt;
| Import grades in various formats &lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Gradebook reports]]&lt;br /&gt;
| gradereport&lt;br /&gt;
| /grade/report&lt;br /&gt;
| Display/edit grades in various layouts and reports&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Grading methods|Advanced grading methods]]&lt;br /&gt;
| gradingform&lt;br /&gt;
| /grade/grading/form&lt;br /&gt;
| Interfaces for actually performing grading in activity modules (eg Rubrics)&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[MNet services]]&lt;br /&gt;
| mnetservice&lt;br /&gt;
| /mnet/service&lt;br /&gt;
| Allows to implement remote services for the [[MNet]] environment (deprecated, use web services instead)&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Webservice protocols]]&lt;br /&gt;
| webservice&lt;br /&gt;
| /webservice&lt;br /&gt;
| Define new protocols for web service communication (such as SOAP, XML-RPC, JSON, REST ...)&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Repository plugins]]&lt;br /&gt;
| repository&lt;br /&gt;
| /repository&lt;br /&gt;
| Connect to external sources of files to use in Moodle&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Portfolio plugins]]&lt;br /&gt;
| portfolio&lt;br /&gt;
| /portfolio&lt;br /&gt;
| Connect external portfolio services as destinations for users to store Moodle content&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Search engines]]&lt;br /&gt;
| search&lt;br /&gt;
| /search/engine&lt;br /&gt;
| Search engine backends to index Moodle&#039;s contents.&lt;br /&gt;
| 3.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Media players]]&lt;br /&gt;
| media&lt;br /&gt;
| /media/player&lt;br /&gt;
| Pluggable media players&lt;br /&gt;
| 3.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Plagiarism plugins]]&lt;br /&gt;
| plagiarism&lt;br /&gt;
| /plagiarism&lt;br /&gt;
| Define external services to process submitted files and content&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Cache store]]&lt;br /&gt;
| cachestore&lt;br /&gt;
| /cache/stores&lt;br /&gt;
| Cache storage back-ends.&lt;br /&gt;
| 2.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Cache locks]]&lt;br /&gt;
| cachelock&lt;br /&gt;
| /cache/locks&lt;br /&gt;
| Cache lock implementations.&lt;br /&gt;
| 2.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Themes]]&lt;br /&gt;
| theme&lt;br /&gt;
| /theme&lt;br /&gt;
| Change the look of Moodle by changing the the HTML and the CSS. &lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Local plugins]]&lt;br /&gt;
| local&lt;br /&gt;
| /local&lt;br /&gt;
| Generic plugins for local customisations&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Assignment types|Legacy assignment types]]&lt;br /&gt;
| assignment&lt;br /&gt;
| /mod/assignment/type&lt;br /&gt;
| Different forms of assignments to be graded by teachers&lt;br /&gt;
| 1.x - 2.2&lt;br /&gt;
|-&lt;br /&gt;
| [[Admin reports|Legacy admin reports]]&lt;br /&gt;
| report&lt;br /&gt;
| /admin/report&lt;br /&gt;
| Provides useful views of data in a Moodle site, for admins only.&lt;br /&gt;
| Up to 2.1 (for 2.2+ see [[Reports]])&lt;br /&gt;
|-&lt;br /&gt;
| [[Content bank content types]]&lt;br /&gt;
| contenttype&lt;br /&gt;
| /contentbank/contenttype&lt;br /&gt;
| Content types to upload, create or edit in the content bank and use all over the Moodle site&lt;br /&gt;
| 3.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[H5P libraries]]&lt;br /&gt;
| h5plib&lt;br /&gt;
| /h5p/h5plib&lt;br /&gt;
| Plugin type for the particular versions of the H5P integration library.&lt;br /&gt;
| 3.9+&lt;br /&gt;
|}&lt;br /&gt;
== Obtaining the list of plugin types known to your Moodle ==&lt;br /&gt;
To get the most exact list of types in your version of Moodle, use the following script. Put it to a file in the root directory of your Moodle installation and execute it via command line.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
define(&#039;CLI_SCRIPT&#039;, true);&lt;br /&gt;
require(&#039;config.php&#039;);&lt;br /&gt;
&lt;br /&gt;
$pluginman = core_plugin_manager::instance();&lt;br /&gt;
&lt;br /&gt;
foreach ($pluginman-&amp;gt;get_plugin_types() as $type =&amp;gt; $dir) {&lt;br /&gt;
    $dir = substr($dir, strlen($CFG-&amp;gt;dirroot));&lt;br /&gt;
    printf(&amp;quot;%-20s %-50s %s&amp;quot;.PHP_EOL, $type, $pluginman-&amp;gt;plugintype_name_plural($type), $dir);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Things you can find in all plugins==&lt;br /&gt;
Although there are many different types of plugin, there are some things that work the same way in all plugin types. Please see [[Plugin files]] page that describes things that work the same in all plugin types.&lt;br /&gt;
== Naming conventions ==&lt;br /&gt;
Warning if you have to choose a plugin (directory) name. The name is validated by the method &amp;lt;tt&amp;gt;lib/classes/component.php::is_valid_plugin_name()&amp;lt;/tt&amp;gt; with a regexp: &amp;lt;tt&amp;gt;/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/&amp;lt;/tt&amp;gt;. In particular, the minus (-) character is not considered as valid, and the plugin will be silently ignored if the name is not valid.&lt;br /&gt;
&lt;br /&gt;
There is an exception for [[Activity modules|activity modules]] that can not have the underscore in their name for legacy reasons.&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Guidelines_for_contributed_code|Guidelines for contributing code]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [[Frankenstyle]]&lt;br /&gt;
* [http://moodle.org/plugins Moodle Plugins directory] &lt;br /&gt;
* [[Tutorial]] to help you learn how to write plugins for Moodle from start to finish, while showing you how to navigate the most important developer documentation along the way.&lt;br /&gt;
[[Category:Coding guidelines|Plugins]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Payment_API&amp;diff=58704</id>
		<title>Payment API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Payment_API&amp;diff=58704"/>
		<updated>2021-04-28T10:32:04Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Triger the payment modal */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.10}}&lt;br /&gt;
&lt;br /&gt;
Payment API allows Moodle components to have financial interactions with users.&lt;br /&gt;
&lt;br /&gt;
=== Trigger the payment modal ===&lt;br /&gt;
&lt;br /&gt;
In order to trigger the payment modal, all you need to do is to place an HTML element with data-action attribute equal to core_payment/triggerPayment in the page. You can use a mustache template with a content similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;button&lt;br /&gt;
    data-action=&amp;quot;core_payment/triggerPayment&amp;quot;&lt;br /&gt;
    data-component=&amp;quot;enrol_fee&amp;quot;&lt;br /&gt;
    data-paymentarea=&amp;quot;fee&amp;quot;&lt;br /&gt;
    data-itemid=&amp;quot;{{itemid}}&amp;quot;&lt;br /&gt;
    data-cost=&amp;quot;{{coststring}}&amp;quot;&lt;br /&gt;
    data-description=&amp;quot;{{description}}&amp;quot;&lt;br /&gt;
&amp;gt;&lt;br /&gt;
    {{# str }} sendpaymentbutton, enrol_fee {{/ str }}&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== The service_provider class ===&lt;br /&gt;
&lt;br /&gt;
All components that want to use the payment API have to implement the \core_payment\local\callback\service_provider interface.&lt;br /&gt;
&lt;br /&gt;
==== get_payable ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_payable(string $paymentarea, int $itemid): \core_payment\local\entities\payable;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is the function that the payment subsystem calls to retrieve the amount and currency of what is being sold, along with the accountid that payments should be paid to.&lt;br /&gt;
&lt;br /&gt;
==== deliver_order ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function deliver_order(string $paymentarea, int $itemid, int $paymentid, int $userid): bool;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is the function that the payment subsystem calls when a user makes a successful payment. This function should give what the user paid for to them.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* User documentation [[:en:Payment gateways|Payment gateways]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=58255</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=58255"/>
		<updated>2021-01-28T12:01:43Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Useful links */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
Since version 3.8, Moodle supports [https://github.com/lukehoban/es6features#readme ECMAScript 2015 features] (aka ES6) in a cross browser compatible way thanks to [https://babeljs.io/ Babel JS]. In order to achieve the compatibility with older browsers Babel will compile the newer ES6 features back into ES5 Javascript. Unfortunately this means that in order for your Javascript changes to show in the browser they must be compiled by running [[Grunt]], even with the cachejs config setting set to false (i.e. &amp;quot;Development mode&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
Note that, for Moodle 3.10 and up (see MDLSITE-6130), any new Javascript module &#039;&#039;&#039;must&#039;&#039;&#039; be written on ES6.&lt;br /&gt;
&lt;br /&gt;
== Install NVM and Node ==&lt;br /&gt;
&lt;br /&gt;
The recommended way of installing NodeJS is via the [https://github.com/nvm-sh/nvm Node Version Manager], or NVM. NVM allows you to have several different versions of NodeJS installed at and in-use at any once on your computer. Supported versions of Moodle all use version {{NodeJSExactVersion}} of NodeJS.&lt;br /&gt;
&lt;br /&gt;
https://github.com/nvm-sh/nvm#installing-and-updating&lt;br /&gt;
&lt;br /&gt;
Confirm it is working:&lt;br /&gt;
&lt;br /&gt;
 $ nvm --version&lt;br /&gt;
 0.35.3&lt;br /&gt;
&lt;br /&gt;
After you have installed &#039;&#039;&#039;nvm&#039;&#039;&#039;, you should install the correct version of NodeJS by running the following commands from your Moodle directory:&lt;br /&gt;
&lt;br /&gt;
 nvm install&lt;br /&gt;
 nvm use&lt;br /&gt;
&lt;br /&gt;
If your primary use of NodeJS is for Moodle then we recommend that you set NodeJS version {{NodeJSExactVersion}} as your default version. You can do this by running:&lt;br /&gt;
&lt;br /&gt;
 nvm alias default {{NodeJSExactVersion}}&lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[[grunt]]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
Once this is done, you can run the the following commands from your Moodle directory:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This may mention vulnerabilities, that&#039;s fine and doesn&#039;t apply.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
== Development mode (Moodle v2.9 to v3.7)  ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing. &lt;br /&gt;
Or, &amp;quot;No define call for theme_XXX/loader&amp;quot; as you are probably missing the &#039;src&#039; folder with relevant JS files. which might happen when you turn debugging ON on a theme that was bought, without &#039;src&#039; folder :-(&lt;br /&gt;
&lt;br /&gt;
== Development mode (Moodle v3.8 and above)  ==&lt;br /&gt;
&lt;br /&gt;
All Javascript code is now compiled using Babel which means Moodle will only ever serve minified Javascript to the browser, even in development mode. However in development mode Moodle will also send the browser the corresponding source map files for each of the Javascript modules. The source map files will tell the browser how to map the minified source code back to the unminified original source code so that the original source files will be displayed in the sources section of the browser&#039;s development tools.&lt;br /&gt;
&lt;br /&gt;
While in development mode each of the Javascript modules will appear in the browser&#039;s source tree as separate modules (no more giant first.js file!) and they will also be loaded with individual network requests (this is a compromise we had to make thanks to some browser bugs with source map files).&lt;br /&gt;
&lt;br /&gt;
To enable development mode set the &#039;&#039;&#039;cachejs&#039;&#039;&#039; config value to &#039;&#039;&#039;false&#039;&#039;&#039; in the admin settings or directly in your config.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since all Javascript must now be compiled you must run [[Grunt]] in order for you changes to appear in the browser. However rather than running Grunt manually each time on either the whole project or each file you modified, it is recommended that you just run the &#039;&#039;&#039;grunt watch&#039;&#039;&#039; task at the root of your Moodle directory. The grunt watch task will listen for changes to the Javascript files in the Moodle directory and will automatically lint and compile only the file that is changed after each change is detected. This removes the need to manually run grunt after each change.&lt;br /&gt;
&lt;br /&gt;
== Development mode (Moodle v3.10 and above)  ==&lt;br /&gt;
&lt;br /&gt;
All the above for Moodle 3.8 and up applies, plus (see MDLSITE-6130), any new Javascript module must be written on ES6.&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
Note: Once you have run grunt and built your code, you will then need to purge Moodle caches otherwise the changes made to your minified files may not be picked up by Moodle.&lt;br /&gt;
&lt;br /&gt;
== ES6 Modules (Moodle v3.8 and above) ==&lt;br /&gt;
&lt;br /&gt;
In addition to AMD module syntax Moodle now supports the [https://github.com/lukehoban/es6features#modules ES6 syntax] for writing Javascript modules. All modules (defined using either syntax) are compatible with one another. Behind the scenes the ES6 module syntax is converted into an AMD syntax as part of the Babel compiling process.&lt;br /&gt;
&lt;br /&gt;
Note that, for Moodle 3.10 and up (see MDLSITE-6130), any new Javascript module must be written on ES6.&lt;br /&gt;
&lt;br /&gt;
The call from a PHP file takes the same format&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;myplugin/myfile&#039;,&#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And a minimal ES6 file will work with &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
export const init = () =&amp;gt; {&lt;br /&gt;
    window.console.log(&#039;we have been started&#039;);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export default ===&lt;br /&gt;
There is one slight difference between the ES6 definition for exporting modules and the RequireJS (AMD) definition.&lt;br /&gt;
&lt;br /&gt;
ES6 allows you to export a “default” value which is actually no different to exporting a named value where the name is “default”. Unfortunately, RequireJS allows for unnamed default exports (e.g. you can do &amp;quot;return SomeClass;&amp;quot;) which can be imported by just requiring them in other AMD modules.&lt;br /&gt;
&lt;br /&gt;
That’s a bit confusing, get to the point! Well, it basically means that in Moodle you won’t be able to write an ES6 module that exports both a default and named exports, e.g. &amp;quot;export default function() {...}&amp;quot; and &amp;quot;export const FOO = &#039;bar&#039;&amp;quot; in the same module. The export default will simply override all other exports in that module.&lt;br /&gt;
&lt;br /&gt;
=== Inline Javascript ===&lt;br /&gt;
Another important note is that ES6 support is only for stand alone Javascript files because it relies on the compilation from Babel and Grunt. That means any inline Javascript (either in PHP or in Mustache templates) won&#039;t support the ES6 features. Instead it would be best to keep the inline Javascript as minimal as possible and only use it to load a stand alone Javascript module.&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
export const init = () =&amp;gt; {&lt;br /&gt;
    document.addEventListener(&#039;change&#039;, e =&amp;gt; {&lt;br /&gt;
        const someNode = e.target.closest(&#039;.someclass&#039;);&lt;br /&gt;
        if (someNode) {&lt;br /&gt;
            alert(&#039;It changed!&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For older versions of Moodle prior to 3.8, you will need to use the llegacy ES5 format instead:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
define([], function() {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
            document.addEventListener(&#039;change&#039;, function(e) {&lt;br /&gt;
                var someNode = e.target.closest(&#039;.someclass&#039;);&lt;br /&gt;
                if (someNode) {&lt;br /&gt;
                    alert(&#039;It changed!&#039;);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This code passes the jquery module into our function (parameter $). There are a number of other useful modules available in Moodle, some of which you&#039;ll probably need in a practical application. See [[Useful_core_Javascript_modules]]. Simply list them in both the define() first parameter and the function callback. E.g.,&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
import jQuery from &#039;jquery&#039;; // We recommend that you strongly consider whether you really need jQuery. It is typically not needed in modern code.&lt;br /&gt;
import Str from &#039;core/str&#039;;&lt;br /&gt;
import Ajax from &#039;core/ajax&#039;;&lt;br /&gt;
&lt;br /&gt;
export const init = config =&amp;gt; {&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;[[Frankenstyle]]&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
export const init = (first, last) {&lt;br /&gt;
    window.console.log(`The first name was &#039;${first}&#039; and the last name was &#039;${last}&#039;`);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
import Str from &#039;core/str&#039;;&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Reveal all of the hidden notes.&lt;br /&gt;
 */&lt;br /&gt;
const showAllNotes = () =&amp;gt; {&lt;br /&gt;
    document.querySelectorAll(&#039;.note.hidden&#039;).map(note =&amp;gt; note.removeClass(&#039;hidden&#039;));&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Hide all of the notes.&lt;br /&gt;
 */&lt;br /&gt;
const hideAllNotes = () =&amp;gt; document.querySelectorAll(&#039;.note&#039;).map(note =&amp;gt; note.addClass(&#039;hidden&#039;));&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Return a personalised, formal, greeting.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   {String} name The name of the person to greet&lt;br /&gt;
 * @returns {Promise}&lt;br /&gt;
 */&lt;br /&gt;
export const formal = name =&amp;gt; Str.get_string(&#039;formallygreet&#039;, &#039;block_overview&#039;, name);&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Return a personalised, informal, greeting.&lt;br /&gt;
 *&lt;br /&gt;
 * @param   {String} name The name of the person to greet&lt;br /&gt;
 * @returns {Promise}&lt;br /&gt;
 */&lt;br /&gt;
export const informal = name =&amp;gt; {&lt;br /&gt;
    return Str.get_string(&#039;informallygreet&#039;, &#039;block_overview&#039;, name);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It&#039;s important to note tha tonly functions which are exported will be callable from outside the module. These are part of the public API.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? In a limited number of situations you may not know the modules that you need until you call them. You can make use of dynamic imports to import them when you know what they are. Note: This is not the recommended approach in most cases.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
export const showTheThing = thingToShow =&amp;gt; {&lt;br /&gt;
    // Load the module for this thing.&lt;br /&gt;
    import(`local_examples/local/types/type_${thingToShow.modname}`)&lt;br /&gt;
    .then(thingModule =&amp;gt; {&lt;br /&gt;
        window.console.log(`The ${thingToShow.modname} is now available under thingModule within this scope`);&lt;br /&gt;
&lt;br /&gt;
        return thingModule;&lt;br /&gt;
    });&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// DO NOT DO THIS - IT DOES NOT WORK IN MOODLE&lt;br /&gt;
define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
    return factory(a0);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;init&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
import Countdown from &#039;theme_mytheme/jquery.countdown&#039;);&lt;br /&gt;
import $ from &#039;jquery&#039;;&lt;br /&gt;
&lt;br /&gt;
export const init = params =&amp;gt; {&lt;br /&gt;
    $(&#039;#clock&#039;).countdown(params.targetItem, event =&amp;gt; {&lt;br /&gt;
             $(event.target).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
    });&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Call your Javascript module from your template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;theme_mytheme-clock-{{uniqid}}&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;theme_mytheme/countdowntimer&#039;], function(myModule) {&lt;br /&gt;
    myModule.init({&lt;br /&gt;
        targetItem: &#039;theme_mytheme-clock-{{uniqid}}&#039;&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: If you feel that you need to work around MDL-62468, then you should probably be putting the data into the DOM in your template via data Attributes, or loading it via a Web Service.&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== npm-shrinkwrap.json sha1 / sha512 changes ===&lt;br /&gt;
&lt;br /&gt;
If grunt changes all the hashes in npm-shrinkwrap.json then try this:&lt;br /&gt;
&lt;br /&gt;
 rm -rf node_modules &amp;amp;&amp;amp; npm i&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* [https://assets.moodlemoot.org/sites/15/20171004085436/JavaScript-AMD-with-RequireJS-presented-by-Daniel-Roperto-Catalyst.pdf JavaScript AMD with RequireJS] presented by Daniel Roperto, Catalyst. (MoodleMOOT AU 2017)&lt;br /&gt;
* [[Useful_core_Javascript_modules]]&lt;br /&gt;
*[[Guide_to_adding_third_party_jQuery_for_AMD]] by Patrick Thibaudeau&lt;br /&gt;
*[https://moodle.org/mod/forum/discuss.php?d=378112#p1524459 How to get variables from PHP into javascript AMD modules in M3.5] Justin Hunt, on Moodle forums.&lt;br /&gt;
*MDL-67327 JavaScript issues when not following the official documentation, since Moodle 3.8&lt;br /&gt;
*[https://moodle.org/mod/forum/discuss.php?d=405829 Error from grunt watch - Moodle 3.9]  Forum Discussion&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Form_API&amp;diff=58168</id>
		<title>Form API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Form_API&amp;diff=58168"/>
		<updated>2020-12-12T10:23:21Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* setHelpButton() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
Web Forms in moodle are created using forms API. Form API supports all html elements (checkbox, radio, textbox etc), with improved accessibility and security.&lt;br /&gt;
==Highlights==&lt;br /&gt;
# Tested and optimised for use on major screen-readers Dragon and JAWS. &lt;br /&gt;
# Tableless layout.&lt;br /&gt;
# Process form data securely, with required_param, optional_param and session key.&lt;br /&gt;
# Supports client-side validation&lt;br /&gt;
# Facility to add Moodle help buttons to forms. &lt;br /&gt;
# Support for file repository using [[File_API]]&lt;br /&gt;
# Support for many custom moodle specific and non-specific form elements.&lt;br /&gt;
# Addition for [https://docs.moodle.org/dev/lib/formslib.php_repeat_elements repeated elements].&lt;br /&gt;
# Addition for form elements in advance group&lt;br /&gt;
&lt;br /&gt;
==Usage==&lt;br /&gt;
For creating a form in moodle, you have to create class extending moodleform class and override [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#definition.28.29 definition] for including form elements.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//moodleform is defined in formslib.php&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;
    //Add elements to form&lt;br /&gt;
    public 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(&#039;text&#039;, &#039;email&#039;, get_string(&#039;email&#039;)); // Add elements to your form&lt;br /&gt;
        $mform-&amp;gt;setType(&#039;email&#039;, PARAM_NOTAGS);                   //Set type of element&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;email&#039;, &#039;Please enter email&#039;);        //Default value&lt;br /&gt;
            ...&lt;br /&gt;
    }&lt;br /&gt;
    //Custom validation should be added here&lt;br /&gt;
    function validation($data, $files) {&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then instantiate form (in this case simplehtml_form) on your page.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//include simplehtml_form.php&lt;br /&gt;
require_once(&#039;PATH_TO/simplehtml_form.php&#039;);&lt;br /&gt;
&lt;br /&gt;
//Instantiate simplehtml_form &lt;br /&gt;
$mform = new simplehtml_form();&lt;br /&gt;
&lt;br /&gt;
//Form processing and displaying is done here&lt;br /&gt;
if ($mform-&amp;gt;is_cancelled()) {&lt;br /&gt;
    //Handle form cancel operation, if cancel button is present on form&lt;br /&gt;
} else if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
  //In this case you process validated data. $mform-&amp;gt;get_data() returns data posted in form.&lt;br /&gt;
} else {&lt;br /&gt;
  // this branch is executed if the form is submitted but the data doesn&#039;t validate and the form should be redisplayed&lt;br /&gt;
  // or on the first display of the form.&lt;br /&gt;
&lt;br /&gt;
  //Set default data (if any)&lt;br /&gt;
  $mform-&amp;gt;set_data($toform);&lt;br /&gt;
  //displays the form&lt;br /&gt;
  $mform-&amp;gt;display();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you wish to use the form within a block then you should consider using the render method, as demonstrated below:&lt;br /&gt;
&lt;br /&gt;
Note that the render method does the same as the display method, except returning the HTML rather than outputting it to the browser, as with above make sure you&#039;ve included the file which contains the class for your Moodle form.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class block_yourblock extends block_base{&lt;br /&gt;
	public function init(){&lt;br /&gt;
		$this-&amp;gt;title = &#039;Your Block&#039;;&lt;br /&gt;
	}&lt;br /&gt;
	public function get_content(){&lt;br /&gt;
&lt;br /&gt;
		$this-&amp;gt;content = new stdClass();&lt;br /&gt;
		$this-&amp;gt;content-&amp;gt;text = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
		$mform = new simplehtml_form();&lt;br /&gt;
&lt;br /&gt;
		//Form processing and displaying is done here&lt;br /&gt;
		if ($mform-&amp;gt;is_cancelled()) {&lt;br /&gt;
			//Handle form cancel operation, if cancel button is present on form&lt;br /&gt;
		} else if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
			//In this case you process validated data. $mform-&amp;gt;get_data() returns data posted in form.&lt;br /&gt;
		} else {&lt;br /&gt;
			// this branch is executed if the form is submitted but the data doesn&#039;t validate and the form should be redisplayed&lt;br /&gt;
			// or on the first display of the form.&lt;br /&gt;
&lt;br /&gt;
			//Set default data (if any)&lt;br /&gt;
			$mform-&amp;gt;set_data($toform);&lt;br /&gt;
&lt;br /&gt;
			//displays the form&lt;br /&gt;
			$this-&amp;gt;content-&amp;gt;text = $mform-&amp;gt;render();&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return $this-&amp;gt;content;&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Form elements==&lt;br /&gt;
===Basic form elements===&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#button button]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#checkbox checkbox]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#radio radio]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#select select]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#multi-select multi-select]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#password password]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hidden hidden]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#html html] - div element&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#static static] - Display a static text.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text text]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#textarea textarea]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements header]&lt;br /&gt;
&lt;br /&gt;
=== Advanced form elements===&lt;br /&gt;
&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#autocomplete Autocomplete] - A select box that allows you to start typing to narrow the list of options, or search for results.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#advcheckbox advcheckbox] - Advance checkbox&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#float float]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#passwordunmask passwordunmask] - A password element with option to show the password in plaintext.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#recaptcha recaptcha]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectyesno selectyesno]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectwithlink selectwithlink]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_selector date_selector]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_time_selector date_time_selector]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#duration duration]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#editor editor]&lt;br /&gt;
# [https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filepicker filepicker] - upload single file&lt;br /&gt;
# [https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filemanager filemanager] - upload multiple files&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#tags tags]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addGroup addGroup]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modgrade modgrade]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modvisible modvisible]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#choosecoursefile choosecoursefile]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#grading grading]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#questioncategory questioncategory]&lt;br /&gt;
&lt;br /&gt;
===Custom form elements===&lt;br /&gt;
&lt;br /&gt;
If you need a custom form element you can dynamical register new elements and then use them like this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    MoodleQuickForm::registerElementType(&#039;course_competency_rule&#039;,&lt;br /&gt;
                                         &amp;quot;$CFG-&amp;gt;dirroot/$CFG-&amp;gt;admin/tool/lp/classes/course_competency_rule_form_element.php&amp;quot;,&lt;br /&gt;
                                         &#039;tool_lp_course_competency_rule_form_element&#039;);&lt;br /&gt;
    // Reuse the same options.&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;course_competency_rule&#039;, &#039;competency_rule&#039;, get_string(&#039;uponcoursemodulecompletion&#039;, &#039;tool_lp&#039;), $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See:&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/admin/tool/lp/lib.php#L157-L161&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/admin/tool/lp/classes/course_competency_rule_form_element.php&lt;br /&gt;
&lt;br /&gt;
==Commonly used functions==&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;code php&amp;gt;&lt;br /&gt;
   $this-&amp;gt;add_action_buttons();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setDefault_2|setDefault()]]====&lt;br /&gt;
To set the default value for an element.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#disabledIf|disabledIf()]]====&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;
====[[lib/formslib.php_Form_Definition#hideIf|hideif()]]====&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;
Same syntax as disabledIf. Can do a simple search and replace on disabledIf.  Added in Moodle 3.4.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#addRule|addRule()]]====&lt;br /&gt;
Add rule for server/client side validation. Like text field is required element and is of type email.&lt;br /&gt;
&lt;br /&gt;
====setHelpButton()====&lt;br /&gt;
DEPRECATED - Sets pop-up help button to a form element.  Use addHelpButton() instead.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#addHelpButton|addHelpButton()]]====&lt;br /&gt;
Adds pop-up help button to a form element&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setType|setType()]]====&lt;br /&gt;
PARAM_* types are used to specify how a submitted variable should be cleaned.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#disable_form_change_checker|disable_form_change_checker()]]====&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 &amp;lt;tt&amp;gt;$mform-&amp;gt;disable_form_change_checker()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==FAQ==&lt;br /&gt;
[https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements How to group elements]&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Core_APIs]]&lt;br /&gt;
* [[lib/formslib.php_Usage]] &lt;br /&gt;
* [[lib/formslib.php_Form_Definition]]&lt;br /&gt;
* [[Designing usable forms]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
* [[MForm_Modal]]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Form_API&amp;diff=58167</id>
		<title>Form API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Form_API&amp;diff=58167"/>
		<updated>2020-12-12T10:22:48Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* setHelpButton() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
Web Forms in moodle are created using forms API. Form API supports all html elements (checkbox, radio, textbox etc), with improved accessibility and security.&lt;br /&gt;
==Highlights==&lt;br /&gt;
# Tested and optimised for use on major screen-readers Dragon and JAWS. &lt;br /&gt;
# Tableless layout.&lt;br /&gt;
# Process form data securely, with required_param, optional_param and session key.&lt;br /&gt;
# Supports client-side validation&lt;br /&gt;
# Facility to add Moodle help buttons to forms. &lt;br /&gt;
# Support for file repository using [[File_API]]&lt;br /&gt;
# Support for many custom moodle specific and non-specific form elements.&lt;br /&gt;
# Addition for [https://docs.moodle.org/dev/lib/formslib.php_repeat_elements repeated elements].&lt;br /&gt;
# Addition for form elements in advance group&lt;br /&gt;
&lt;br /&gt;
==Usage==&lt;br /&gt;
For creating a form in moodle, you have to create class extending moodleform class and override [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#definition.28.29 definition] for including form elements.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//moodleform is defined in formslib.php&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;
    //Add elements to form&lt;br /&gt;
    public 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(&#039;text&#039;, &#039;email&#039;, get_string(&#039;email&#039;)); // Add elements to your form&lt;br /&gt;
        $mform-&amp;gt;setType(&#039;email&#039;, PARAM_NOTAGS);                   //Set type of element&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;email&#039;, &#039;Please enter email&#039;);        //Default value&lt;br /&gt;
            ...&lt;br /&gt;
    }&lt;br /&gt;
    //Custom validation should be added here&lt;br /&gt;
    function validation($data, $files) {&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then instantiate form (in this case simplehtml_form) on your page.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//include simplehtml_form.php&lt;br /&gt;
require_once(&#039;PATH_TO/simplehtml_form.php&#039;);&lt;br /&gt;
&lt;br /&gt;
//Instantiate simplehtml_form &lt;br /&gt;
$mform = new simplehtml_form();&lt;br /&gt;
&lt;br /&gt;
//Form processing and displaying is done here&lt;br /&gt;
if ($mform-&amp;gt;is_cancelled()) {&lt;br /&gt;
    //Handle form cancel operation, if cancel button is present on form&lt;br /&gt;
} else if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
  //In this case you process validated data. $mform-&amp;gt;get_data() returns data posted in form.&lt;br /&gt;
} else {&lt;br /&gt;
  // this branch is executed if the form is submitted but the data doesn&#039;t validate and the form should be redisplayed&lt;br /&gt;
  // or on the first display of the form.&lt;br /&gt;
&lt;br /&gt;
  //Set default data (if any)&lt;br /&gt;
  $mform-&amp;gt;set_data($toform);&lt;br /&gt;
  //displays the form&lt;br /&gt;
  $mform-&amp;gt;display();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you wish to use the form within a block then you should consider using the render method, as demonstrated below:&lt;br /&gt;
&lt;br /&gt;
Note that the render method does the same as the display method, except returning the HTML rather than outputting it to the browser, as with above make sure you&#039;ve included the file which contains the class for your Moodle form.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class block_yourblock extends block_base{&lt;br /&gt;
	public function init(){&lt;br /&gt;
		$this-&amp;gt;title = &#039;Your Block&#039;;&lt;br /&gt;
	}&lt;br /&gt;
	public function get_content(){&lt;br /&gt;
&lt;br /&gt;
		$this-&amp;gt;content = new stdClass();&lt;br /&gt;
		$this-&amp;gt;content-&amp;gt;text = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
		$mform = new simplehtml_form();&lt;br /&gt;
&lt;br /&gt;
		//Form processing and displaying is done here&lt;br /&gt;
		if ($mform-&amp;gt;is_cancelled()) {&lt;br /&gt;
			//Handle form cancel operation, if cancel button is present on form&lt;br /&gt;
		} else if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
			//In this case you process validated data. $mform-&amp;gt;get_data() returns data posted in form.&lt;br /&gt;
		} else {&lt;br /&gt;
			// this branch is executed if the form is submitted but the data doesn&#039;t validate and the form should be redisplayed&lt;br /&gt;
			// or on the first display of the form.&lt;br /&gt;
&lt;br /&gt;
			//Set default data (if any)&lt;br /&gt;
			$mform-&amp;gt;set_data($toform);&lt;br /&gt;
&lt;br /&gt;
			//displays the form&lt;br /&gt;
			$this-&amp;gt;content-&amp;gt;text = $mform-&amp;gt;render();&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return $this-&amp;gt;content;&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Form elements==&lt;br /&gt;
===Basic form elements===&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#button button]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#checkbox checkbox]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#radio radio]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#select select]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#multi-select multi-select]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#password password]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hidden hidden]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#html html] - div element&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#static static] - Display a static text.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text text]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#textarea textarea]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements header]&lt;br /&gt;
&lt;br /&gt;
=== Advanced form elements===&lt;br /&gt;
&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#autocomplete Autocomplete] - A select box that allows you to start typing to narrow the list of options, or search for results.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#advcheckbox advcheckbox] - Advance checkbox&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#float float]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#passwordunmask passwordunmask] - A password element with option to show the password in plaintext.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#recaptcha recaptcha]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectyesno selectyesno]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectwithlink selectwithlink]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_selector date_selector]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_time_selector date_time_selector]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#duration duration]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#editor editor]&lt;br /&gt;
# [https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filepicker filepicker] - upload single file&lt;br /&gt;
# [https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filemanager filemanager] - upload multiple files&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#tags tags]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addGroup addGroup]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modgrade modgrade]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modvisible modvisible]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#choosecoursefile choosecoursefile]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#grading grading]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#questioncategory questioncategory]&lt;br /&gt;
&lt;br /&gt;
===Custom form elements===&lt;br /&gt;
&lt;br /&gt;
If you need a custom form element you can dynamical register new elements and then use them like this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    MoodleQuickForm::registerElementType(&#039;course_competency_rule&#039;,&lt;br /&gt;
                                         &amp;quot;$CFG-&amp;gt;dirroot/$CFG-&amp;gt;admin/tool/lp/classes/course_competency_rule_form_element.php&amp;quot;,&lt;br /&gt;
                                         &#039;tool_lp_course_competency_rule_form_element&#039;);&lt;br /&gt;
    // Reuse the same options.&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;course_competency_rule&#039;, &#039;competency_rule&#039;, get_string(&#039;uponcoursemodulecompletion&#039;, &#039;tool_lp&#039;), $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See:&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/admin/tool/lp/lib.php#L157-L161&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/admin/tool/lp/classes/course_competency_rule_form_element.php&lt;br /&gt;
&lt;br /&gt;
==Commonly used functions==&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;code php&amp;gt;&lt;br /&gt;
   $this-&amp;gt;add_action_buttons();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setDefault_2|setDefault()]]====&lt;br /&gt;
To set the default value for an element.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#disabledIf|disabledIf()]]====&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;
====[[lib/formslib.php_Form_Definition#hideIf|hideif()]]====&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;
Same syntax as disabledIf. Can do a simple search and replace on disabledIf.  Added in Moodle 3.4.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#addRule|addRule()]]====&lt;br /&gt;
Add rule for server/client side validation. Like text field is required element and is of type email.&lt;br /&gt;
&lt;br /&gt;
====setHelpButton()====&lt;br /&gt;
DEPRECATED - Sets pop-up help button to a form element.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#addHelpButton|addHelpButton()]]====&lt;br /&gt;
Adds pop-up help button to a form element&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setType|setType()]]====&lt;br /&gt;
PARAM_* types are used to specify how a submitted variable should be cleaned.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#disable_form_change_checker|disable_form_change_checker()]]====&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 &amp;lt;tt&amp;gt;$mform-&amp;gt;disable_form_change_checker()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==FAQ==&lt;br /&gt;
[https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements How to group elements]&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Core_APIs]]&lt;br /&gt;
* [[lib/formslib.php_Usage]] &lt;br /&gt;
* [[lib/formslib.php_Form_Definition]]&lt;br /&gt;
* [[Designing usable forms]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
* [[MForm_Modal]]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=OAuth_2_API&amp;diff=57938</id>
		<title>OAuth 2 API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=OAuth_2_API&amp;diff=57938"/>
		<updated>2020-10-17T09:27:03Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* I setup an OAuth 2 Issuer - how to I use it in code? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== OAuth 2 API ==&lt;br /&gt;
{{ Moodle 3.3 }}&lt;br /&gt;
&lt;br /&gt;
The OAuth 2 API is a set of classes that provide OAuth 2 functionality for integrating with remote systems. They exist in the folder /lib/classes/oauth2/ and there are a few concepts to be aware of.&lt;br /&gt;
&lt;br /&gt;
=== Issuers ===&lt;br /&gt;
An OAuth Issuer is a named external system that provides identity and API access by issuing OAuth access tokens. They are configured manually at &amp;quot;Site administration -&amp;gt; Server -&amp;gt; OAuth 2 Services&amp;quot; and common ones can be quickly created from a template (Google, Office 365 and Facebook). An Issuer has a name and icon (for display on the login page), a Client ID and Client Secret (part of the OAuth spec).&lt;br /&gt;
&lt;br /&gt;
=== Endpoints ===&lt;br /&gt;
An OAuth issuer must have a number of endpoints defined which are the URL&#039;s used to fetch and exchange access tokens, as well as fetch identity information. These will be setup automatically for OAuth services created from a template, or OAuth services using Open ID Connect.&lt;br /&gt;
&lt;br /&gt;
The 3 standard endpoints which must be defined are the &amp;quot;authorization endpoint&amp;quot;, &amp;quot;token endpoint&amp;quot; and &amp;quot;userinfo endpoint&amp;quot; - these are 3 urls which are used by the OAuth protocol to &amp;quot;allow the user to login&amp;quot;, &amp;quot;obtain tokens to access the api&amp;quot; and &amp;quot;get the logged in user information&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
=== Open ID Connect ===&lt;br /&gt;
Open ID Connect is a protocol built on top of OAuth 2 which provides some standardisation and inter-operability for OAuth 2 based services. If a &amp;quot;base service url&amp;quot; is entered for an Issuer - Moodle will attempt to retrieve the &amp;quot;well known configuration&amp;quot; which provides all the information about the other endpoints required to complete the setup for this service. E.g. for Google - the base service url is &amp;quot;https://accounts.google.com/&amp;quot;. By appending &amp;quot;.well-known/openid-configuration&amp;quot; to the url we can find the service description at https://accounts.google.com/.well-known/openid-configuration which contains all the required information for us to automatically complete the setup for this service. This will work with any Open ID connect compliant service.&lt;br /&gt;
&lt;br /&gt;
=== User field mappings ===&lt;br /&gt;
The other information we need to know about an OAuth 2 service is how to map the user information into Moodle user fields. We do this by adding to the list of user field mappings for the Issuer. The mappings for Open ID Connect services are standard and will be automatically created when setting up an Open ID compliant service - for other services you will need to create the mappings manually. Moodle will use this information to import the user profile fields when creating new accounts. The most important user field mappings are the username and email which are used to identify the Moodle account associated with the OAuth 2 login.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== I setup an OAuth 2 Issuer - how to I use it in code? ==&lt;br /&gt;
Any plugin that wants to use the configuration information provided by an OAuth issuer first needs to determine which issuer they should use for authentication. This is typically done by adding a setting that lets the admin choose from the list of configured issuers. An example is the &amp;quot;Google Drive&amp;quot; repository. It&#039;s possible to have multiple, very similar OAuth Issuers configured e.g. One public Google Issuer and one that is restricted to a specific domain. Once we know the Issuer we wish to use we can use it&#039;s ID to get an \core\oauth2\client class which will let us make requests against that API.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Get an issuer from the id.&lt;br /&gt;
$issuer = \core\oauth2\api::get_issuer($issuerid);&lt;br /&gt;
// Get an OAuth client from the issuer.&lt;br /&gt;
$client = \core\oauth2\api::get_user_oauth_client($this-&amp;gt;issuer, $returnurl, $scopes);                           &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Despite what is said in the code documentation, it is best to check that the client is authenticated and if not,  redirect the user to log in.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (!$client-&amp;gt;is_logged_in()) {&lt;br /&gt;
    redirect($client-&amp;gt;get_login_url());&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;What is return URL?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
This is a url that will take you back to the current page. The way OAuth works is that the call to get the OAuth client will check to see if we have a valid session with all of the required scopes. If we don&#039;t - the user will be redirected immediately to the OAuth login page. When they have logged in - they are redirected back to the &amp;quot;returnurl&amp;quot; in Moodle which must end up making the same call to get an OAuth client - except this time we have a valid session so the client is returned. The returnurl MUST include the sesskey as one of the parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;What are the scopes?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
These are named &amp;quot;permission grants&amp;quot; which the user is asked to accept. E.g. &amp;quot;email&amp;quot; is a scope which allows Moodle to see the email address for your logged in account. Each of the requested permissions will be displayed to the user with a description of what they mean and the user will have to &amp;quot;grant&amp;quot; Moodle this level of access - or cancel the operation. It is best practice not to request more scopes than are needed at any one time, and to incrementally request additional scopes as they are going to be used by different features in Moodle. For example - when logging into Moodle we only need to request access to the users basic profile and email address. When accessing a repository - we will need to request read/write access to the users files. Each time we create an oauth client class, we list the scopes that we will be using with this instance of the oauth client - if the user has only approved some of the scopes we request - they will be redirected to a consent screen where they approve the additional level of access.&lt;br /&gt;
&lt;br /&gt;
So - what can I do with my oauth client ?&lt;br /&gt;
&lt;br /&gt;
Make requests! This class extends the moodle curl class but includes authentication information with each request automatically. This means you can use standard functions like $client-&amp;gt;get() or $client-&amp;gt;post() to manually call rest api functions. This class will also honour the configured Moodle security settings like ipaddress black lists and proxy settings. &lt;br /&gt;
&lt;br /&gt;
How do I make it easier to call API functions?&lt;br /&gt;
&lt;br /&gt;
There is an abstract class \core\oauth2\rest which contains helper functions to allow you to wrap a external rest api in an easier to use class. To use it, make a subclass in your own plugin and define the &amp;quot;get_api_functions()&amp;quot; method. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/**                                                                                                                                 &lt;br /&gt;
 * Google Drive Rest API.                                                                                                           &lt;br /&gt;
 *                                                                                                                                  &lt;br /&gt;
 * @package    fileconverter_googledrive                                                                                            &lt;br /&gt;
 * @copyright  2017 Damyon Wiese                                                                                                    &lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later                                                             &lt;br /&gt;
 */                                                                                                                                 &lt;br /&gt;
namespace fileconverter_googledrive;                                                                                                &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();                                                                                                &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
/**                                                                                                                                 &lt;br /&gt;
 * Google Drive Rest API.                                                                                                           &lt;br /&gt;
 *                                                                                                                                  &lt;br /&gt;
 * @copyright  2017 Damyon Wiese                                                                                                    &lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later                                                             &lt;br /&gt;
 */                                                                                                                                 &lt;br /&gt;
class rest extends \core\oauth2\rest {                                                                                              &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    /**                                                                                                                             &lt;br /&gt;
     * Define the functions of the rest API.                                                                                        &lt;br /&gt;
     *                                                                                                                              &lt;br /&gt;
     * @return array Example:                                                                                                       &lt;br /&gt;
     *  [ &#039;listFiles&#039; =&amp;gt; [ &#039;method&#039; =&amp;gt; &#039;get&#039;, &#039;endpoint&#039; =&amp;gt; &#039;http://...&#039;, &#039;args&#039; =&amp;gt; [ &#039;folder&#039; =&amp;gt; PARAM_STRING ] ] ]                &lt;br /&gt;
     */                                                                                                                             &lt;br /&gt;
    public function get_api_functions() {                                                                                           &lt;br /&gt;
        return [                                                                                                                    &lt;br /&gt;
            &#039;create&#039; =&amp;gt; [                                                                                                           &lt;br /&gt;
                &#039;endpoint&#039; =&amp;gt; &#039;https://www.googleapis.com/drive/v3/files&#039;,                                                          &lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;post&#039;,                                                                                                 &lt;br /&gt;
                &#039;args&#039; =&amp;gt; [                                                                                                         &lt;br /&gt;
                    &#039;fields&#039; =&amp;gt; PARAM_RAW                                                                                           &lt;br /&gt;
                ],                                                                                                                  &lt;br /&gt;
                &#039;response&#039; =&amp;gt; &#039;json&#039;                                                                                                &lt;br /&gt;
            ],                                                                                                                      &lt;br /&gt;
            &#039;delete&#039; =&amp;gt; [                                                                                                           &lt;br /&gt;
                &#039;endpoint&#039; =&amp;gt; &#039;https://www.googleapis.com/drive/v3/files/{fileid}&#039;,                                                 &lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;delete&#039;,                                                                                               &lt;br /&gt;
                &#039;args&#039; =&amp;gt; [                                                                                                         &lt;br /&gt;
                    &#039;fileid&#039; =&amp;gt; PARAM_RAW                                                                                           &lt;br /&gt;
                ],                                                                                                                  &lt;br /&gt;
                &#039;response&#039; =&amp;gt; &#039;json&#039;                                                                                                &lt;br /&gt;
            ],                                                                                                                      &lt;br /&gt;
        ];                                                                                                                          &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
}               &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example defines 2 functions in the external API &#039;create&#039; and &#039;delete&#039;. It specifies the http methods to use when calling these functions as well as the list of parameters. It also specifies that these functions return &#039;json&#039; which means that the rest class will automatically decode the json response into an object. The url for each function call can contain parameters in the url (marked with curly braces). These will be replaced when they are passed as arguments to the function. Any remaining arguments will be appended as query parameters. &lt;br /&gt;
&lt;br /&gt;
To use this class - we pass the oauth2 client to the constructor and then use the &amp;quot;call&amp;quot; method to call functions from the api.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$service = new \fileconverter_googledrive\rest($client);           &lt;br /&gt;
&lt;br /&gt;
$params = [&#039;fileid&#039; =&amp;gt; $fileid];                                                                                                                          &lt;br /&gt;
&lt;br /&gt;
$service-&amp;gt;call(&#039;delete&#039;, $params);                         &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How do I call API functions when the user is not logged in (e.g. from a scheduled task)? ==&lt;br /&gt;
&lt;br /&gt;
Moodle allows you to connect a &amp;quot;system account&amp;quot; to any of the OAuth issuers. This is optional - so before enabling functionality that relies on this level of access you should check that the system account has been connected:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($issuer-&amp;gt;is_system_account_connected()) {&lt;br /&gt;
    // Rock and roll.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a system account has been connected - we can use it to get an authenticated oauth client (no redirects involved). &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$client = \core\oauth2\api::get_system_oauth_client($issuer);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This client can now be used to access apis as the Moodle system user.&lt;br /&gt;
&lt;br /&gt;
If your code is going to use additional login scopes with this API - it must list all of the scopes it will use in a callback function so that the Moodle administrator can consent and agree to all the scopes when they connect the system account. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
**                                                                                                                                 &lt;br /&gt;
 * Callback to get the required scopes for system account.                                                                          &lt;br /&gt;
 *                                                                                                                                  &lt;br /&gt;
 * @param \core\oauth2\issuer $issuer                                                                                               &lt;br /&gt;
 * @return string                                                                                                                   &lt;br /&gt;
 */                                                                                                                                 &lt;br /&gt;
function fileconverter_googledrive_oauth2_system_scopes(\core\oauth2\issuer $issuer) {                                              &lt;br /&gt;
    if ($issuer-&amp;gt;get(&#039;id&#039;) == get_config(&#039;fileconverter_googledrive&#039;, &#039;issuerid&#039;)) {                                                &lt;br /&gt;
        return &#039;https://www.googleapis.com/auth/drive&#039;;                                                                             &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
    return &#039;&#039;;                                                                                                                      &lt;br /&gt;
}  &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The way the system account works is that a Moodle admin &amp;quot;connects&amp;quot; the system account by logging in with this account and accepting the permissions from the &amp;quot;Site administration -&amp;gt; Server -&amp;gt; OAuth 2 Services&amp;quot; page. One of the permissions they must accept is for offline access. This means that Moodle will receive a refresh token and can store it against that issuer. When we need to get a logged in OAuth client - we can exchange the refresh token for an access token directly, without having to login. The refresh token is updated by a scheduled task to make sure it never expires.&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Adding_a_web_service_to_a_plugin&amp;diff=57569</id>
		<title>Adding a web service to a plugin</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Adding_a_web_service_to_a_plugin&amp;diff=57569"/>
		<updated>2020-06-07T12:06:28Z</updated>

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

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

		<summary type="html">&lt;p&gt;Bencellis: /* How to get a user token */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
[[Image:Moodle web service function documentation.jpg|thumb]]You need to know how to [https://docs.moodle.org/en/How_to_create_and_enable_a_web_service setup a web service] first.&lt;br /&gt;
To see the API Documentation, connect as Admin and go to &#039;&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; API Documentation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Simple REST request example ==&lt;br /&gt;
&lt;br /&gt;
To quickly test the web service works you can visit the end point from the browser or via curl. For example to call a function via REST protocol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$ curl &amp;quot;https://your.site.com/moodle/webservice/rest/server.php?wstoken=...&amp;amp;wsfunction=...&amp;amp;moodlewsrestformat=json&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Customise the parameters &amp;lt;tt&amp;gt;wstoken&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;wsfunction&amp;lt;/tt&amp;gt; to match the server side setup. Append additional parameters for the function call as needed with &amp;quot;&amp;amp;parameter_name=param&amp;quot;, or if your parameter is an array then use &amp;quot;&amp;amp;array_name[index][parameter_name]=param&amp;quot;. With the core_user_create_users function&#039;s (required) parameters for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$ curl &amp;quot;...&amp;amp;moodlewsrestformat=json&amp;amp;wsfunction=core_user_create_users&amp;amp;moodlewsrestformat=json&amp;amp;users[0][username]=testuser&amp;amp;users[0][firstname]=Anne&amp;amp;users[0][lastname]=Example...&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;moodlewsrestformat&amp;lt;/tt&amp;gt; parameter affects the response format and can be either &amp;lt;tt&amp;gt;xml&amp;lt;/tt&amp;gt; (default) or &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Officially supported protocols ==&lt;br /&gt;
&lt;br /&gt;
; REST : The Moodle REST server accepts GET/POST parameters and return XML/JSON values. This server is not RESTfull.&lt;br /&gt;
; SOAP : The Moodle SOAP server is based on the Zend SOAP server (itself based on the PHP SOAP server). Zend publishes [http://framework.zend.com/manual/en/zend.soap.client.html a Zend SOAP client]. The current server implementation doesn&#039;t fully work with Java/.Net because we didn&#039;t generated a fully describe WSDL yet. If you are working on a Java/.Net client, follow or participate to the tracker issues MDL-28988 / MDL-28989&lt;br /&gt;
; XML-RPC : The Moodle XML-RPC server is based on Zend XML-RPC server. Zend also publishes [http://framework.zend.com/manual/en/zend.xmlrpc.client.html a Zend XML-RPC client].&lt;br /&gt;
; AMF (Versions &amp;lt; Moodle 3.0) : the Moodle AMF server is based on the Zend AMF server. The test client can be found in &#039;&#039;Settings blocks &amp;gt; Site Administration &amp;gt; Development &amp;gt; Web service test client &amp;gt; AMF Test client&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Demo client examples ==&lt;br /&gt;
&lt;br /&gt;
Demo client sample codes can be downloaded on [https://github.com/moodlehq/sample-ws-clients Github].&lt;br /&gt;
&lt;br /&gt;
For HTML5 app creators, you can also find:&lt;br /&gt;
* a nice [https://github.com/jleyva/umm phonegap / Jquery mobile template]&lt;br /&gt;
* a proof of concept of [http://moodle.org/mod/forum/discuss.php?d=189882 javascript cross-domain with Sencha Touch 1.1]&lt;br /&gt;
&lt;br /&gt;
Node.js apps can use the [https://github.com/mudrd8mz/node-moodle-client moodle-client] module.&lt;br /&gt;
&lt;br /&gt;
A [http://moodle.org/mod/forum/discuss.php?d=199453 Java Library for REST] can be found on [http://sourceforge.net/projects/moodlerestjava/ Sourceforge].&lt;br /&gt;
&lt;br /&gt;
A [https://github.com/llagerlof/MoodleRest PHP Library for REST] can be found on GitHub.&lt;br /&gt;
&lt;br /&gt;
A [https://github.com/zaddok/moodle Go Library for REST] can be found on GitHub.&lt;br /&gt;
&lt;br /&gt;
== How to get a user token ==&lt;br /&gt;
{{Moodle_2.2}}&lt;br /&gt;
Your client can call the script located in /login/token.php with a simple HTTP request. We highly recommend to do it securely with HTTPS.&lt;br /&gt;
The required parameters are:&lt;br /&gt;
* username&lt;br /&gt;
* password&lt;br /&gt;
* service shortname - The service shortname is usually hardcoded in the pre-build service (db/service.php files). Moodle administrator will be able to edit shortnames for service created on the fly: MDL-29807. If you want to use the Mobile service, its shortname is &amp;lt;tt&amp;gt;moodle_mobile_app&amp;lt;/tt&amp;gt;. Also useful to know, the database shortname field can be found in the table named external_services.&lt;br /&gt;
&lt;br /&gt;
Request:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
https://www.yourmoodle.com/login/token.php?username=USERNAME&amp;amp;password=PASSWORD&amp;amp;service=SERVICESHORTNAME&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Response: (HTTP)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{token:4ed876sd87g6d8f7g89fsg6987dfh78d}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Response: (HTTPS) - Since at least M3.9&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;token&amp;quot;: &amp;quot;9859148a89546f0efe716a58e340849b&amp;quot;,&lt;br /&gt;
    &amp;quot;privatetoken&amp;quot;: &amp;quot;8RpHJevJ42W7QN23OMkeYcdOYw3YfWgWGKsak7WB3Z88wcApSCVZ9TgY6M5fEO1m&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Difference between Moodle versions ===&lt;br /&gt;
&lt;br /&gt;
* Moodle 2.2 and later: the script can generate user tokens for any service shortname (of course users must be allowed on the service, see [[:en:How to create and enable a web service|How to create and enable a web service]]).&lt;br /&gt;
* Moodle 2.1: the script can only generate tokens for the official built-in mobile service. However the script can returns tokens for other services, they just need to have been previously generated.&lt;br /&gt;
&lt;br /&gt;
=== About service shortname ===&lt;br /&gt;
&lt;br /&gt;
At the moment a service can have a shortname if you:&lt;br /&gt;
* create the service as a built-in service (in db/services.php files) &lt;br /&gt;
* add the shortname manually in the DB. Note: we&#039;ll add the admin UI for shortname later (MDL-30229)&lt;br /&gt;
&lt;br /&gt;
== Text formats ==&lt;br /&gt;
=== Moodle 2.0 to 2.2 ===&lt;br /&gt;
{{Moodle_2.0}}&lt;br /&gt;
HTML is the format sent/received by web service functions. All returned file urls are converted to &#039;http://xxxx/webservice/pluginfile.php/yyyyyyyy&#039;&lt;br /&gt;
&lt;br /&gt;
=== Moodle 2.3 and later ===&lt;br /&gt;
{{Moodle_2.3}}&lt;br /&gt;
Since Moodle 2.3 you can add few GET/POST parameters to your request (for devs who have a good knowledge of File API and format_text()):&lt;br /&gt;
* &#039;&#039;moodlewssettingraw&#039;&#039; =&amp;gt; false by default. If true, the function will not apply format_text() to description/summary/textarea. The function will return the raw content from the DB.&lt;br /&gt;
* &#039;&#039;moodlewssettingfileurl&#039;&#039; =&amp;gt; true by default, returned file urls are converted to &#039;http://xxxx/webservice/pluginfile.php/yyyyyyyy&#039;. If false the raw file url content from the DB is returned (e.g. @@PLUGINFILE@@)&lt;br /&gt;
* &#039;&#039;moodlewssettingfilter&#039;&#039; =&amp;gt; false by default. If true, the function will filter during format_text()&lt;br /&gt;
&lt;br /&gt;
=== Moodle  3.5 and later ===&lt;br /&gt;
{{Moodle_3.5}}&lt;br /&gt;
* &#039;&#039;moodlewssettinglang&#039;&#039; =&amp;gt; to force a session language for when retrieving information (same behaviour than using the language selector in the site)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
* [[Creating a web service and a web service function | Implement a web service and a web service function]]&lt;br /&gt;
* [[Web_services_Roadmap|Web service Roadmap]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web Services]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_a_web_service_client&amp;diff=57566</id>
		<title>Creating a web service client</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_a_web_service_client&amp;diff=57566"/>
		<updated>2020-06-07T11:34:54Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* How to get a user token */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
[[Image:Moodle web service function documentation.jpg|thumb]]You need to know how to [https://docs.moodle.org/en/How_to_create_and_enable_a_web_service setup a web service] first.&lt;br /&gt;
To see the API Documentation, connect as Admin and go to &#039;&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; API Documentation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Simple REST request example ==&lt;br /&gt;
&lt;br /&gt;
To quickly test the web service works you can visit the end point from the browser or via curl. For example to call a function via REST protocol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$ curl &amp;quot;https://your.site.com/moodle/webservice/rest/server.php?wstoken=...&amp;amp;wsfunction=...&amp;amp;moodlewsrestformat=json&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Customise the parameters &amp;lt;tt&amp;gt;wstoken&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;wsfunction&amp;lt;/tt&amp;gt; to match the server side setup. Append additional parameters for the function call as needed with &amp;quot;&amp;amp;parameter_name=param&amp;quot;, or if your parameter is an array then use &amp;quot;&amp;amp;array_name[index][parameter_name]=param&amp;quot;. With the core_user_create_users function&#039;s (required) parameters for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$ curl &amp;quot;...&amp;amp;moodlewsrestformat=json&amp;amp;wsfunction=core_user_create_users&amp;amp;moodlewsrestformat=json&amp;amp;users[0][username]=testuser&amp;amp;users[0][firstname]=Anne&amp;amp;users[0][lastname]=Example...&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;moodlewsrestformat&amp;lt;/tt&amp;gt; parameter affects the response format and can be either &amp;lt;tt&amp;gt;xml&amp;lt;/tt&amp;gt; (default) or &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Officially supported protocols ==&lt;br /&gt;
&lt;br /&gt;
; REST : The Moodle REST server accepts GET/POST parameters and return XML/JSON values. This server is not RESTfull.&lt;br /&gt;
; SOAP : The Moodle SOAP server is based on the Zend SOAP server (itself based on the PHP SOAP server). Zend publishes [http://framework.zend.com/manual/en/zend.soap.client.html a Zend SOAP client]. The current server implementation doesn&#039;t fully work with Java/.Net because we didn&#039;t generated a fully describe WSDL yet. If you are working on a Java/.Net client, follow or participate to the tracker issues MDL-28988 / MDL-28989&lt;br /&gt;
; XML-RPC : The Moodle XML-RPC server is based on Zend XML-RPC server. Zend also publishes [http://framework.zend.com/manual/en/zend.xmlrpc.client.html a Zend XML-RPC client].&lt;br /&gt;
; AMF (Versions &amp;lt; Moodle 3.0) : the Moodle AMF server is based on the Zend AMF server. The test client can be found in &#039;&#039;Settings blocks &amp;gt; Site Administration &amp;gt; Development &amp;gt; Web service test client &amp;gt; AMF Test client&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Demo client examples ==&lt;br /&gt;
&lt;br /&gt;
Demo client sample codes can be downloaded on [https://github.com/moodlehq/sample-ws-clients Github].&lt;br /&gt;
&lt;br /&gt;
For HTML5 app creators, you can also find:&lt;br /&gt;
* a nice [https://github.com/jleyva/umm phonegap / Jquery mobile template]&lt;br /&gt;
* a proof of concept of [http://moodle.org/mod/forum/discuss.php?d=189882 javascript cross-domain with Sencha Touch 1.1]&lt;br /&gt;
&lt;br /&gt;
Node.js apps can use the [https://github.com/mudrd8mz/node-moodle-client moodle-client] module.&lt;br /&gt;
&lt;br /&gt;
A [http://moodle.org/mod/forum/discuss.php?d=199453 Java Library for REST] can be found on [http://sourceforge.net/projects/moodlerestjava/ Sourceforge].&lt;br /&gt;
&lt;br /&gt;
A [https://github.com/llagerlof/MoodleRest PHP Library for REST] can be found on GitHub.&lt;br /&gt;
&lt;br /&gt;
A [https://github.com/zaddok/moodle Go Library for REST] can be found on GitHub.&lt;br /&gt;
&lt;br /&gt;
== How to get a user token ==&lt;br /&gt;
{{Moodle_2.2}}&lt;br /&gt;
Your client can call the script located in /login/token.php with a simple HTTP request. We highly recommend to do it securely with HTTPS.&lt;br /&gt;
The required parameters are:&lt;br /&gt;
* username&lt;br /&gt;
* password&lt;br /&gt;
* service shortname - The service shortname is usually hardcoded in the pre-build service (db/service.php files). Moodle administrator will be able to edit shortnames for service created on the fly: MDL-29807. If you want to use the Mobile service, its shortname is &amp;lt;tt&amp;gt;moodle_mobile_app&amp;lt;/tt&amp;gt;. Also useful to know, the database shortname field can be found in the table named external_services.&lt;br /&gt;
&lt;br /&gt;
Request:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
https://www.yourmoodle.com/login/token.php?username=USERNAME&amp;amp;password=PASSWORD&amp;amp;service=SERVICESHORTNAME&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Response: (HTTP)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{token:4ed876sd87g6d8f7g89fsg6987dfh78d}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Response: (HTTPS)&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;token&amp;quot;: &amp;quot;9859148a89546f0efe716a58e340849b&amp;quot;,&lt;br /&gt;
    &amp;quot;privatetoken&amp;quot;: &amp;quot;8RpHJevJ42W7QN23OMkeYcdOYw3YfWgWGKsak7WB3Z88wcApSCVZ9TgY6M5fEO1m&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Difference between Moodle versions ===&lt;br /&gt;
&lt;br /&gt;
* Moodle 2.2 and later: the script can generate user tokens for any service shortname (of course users must be allowed on the service, see [[:en:How to create and enable a web service|How to create and enable a web service]]).&lt;br /&gt;
* Moodle 2.1: the script can only generate tokens for the official built-in mobile service. However the script can returns tokens for other services, they just need to have been previously generated.&lt;br /&gt;
&lt;br /&gt;
=== About service shortname ===&lt;br /&gt;
&lt;br /&gt;
At the moment a service can have a shortname if you:&lt;br /&gt;
* create the service as a built-in service (in db/services.php files) &lt;br /&gt;
* add the shortname manually in the DB. Note: we&#039;ll add the admin UI for shortname later (MDL-30229)&lt;br /&gt;
&lt;br /&gt;
== Text formats ==&lt;br /&gt;
=== Moodle 2.0 to 2.2 ===&lt;br /&gt;
{{Moodle_2.0}}&lt;br /&gt;
HTML is the format sent/received by web service functions. All returned file urls are converted to &#039;http://xxxx/webservice/pluginfile.php/yyyyyyyy&#039;&lt;br /&gt;
&lt;br /&gt;
=== Moodle 2.3 and later ===&lt;br /&gt;
{{Moodle_2.3}}&lt;br /&gt;
Since Moodle 2.3 you can add few GET/POST parameters to your request (for devs who have a good knowledge of File API and format_text()):&lt;br /&gt;
* &#039;&#039;moodlewssettingraw&#039;&#039; =&amp;gt; false by default. If true, the function will not apply format_text() to description/summary/textarea. The function will return the raw content from the DB.&lt;br /&gt;
* &#039;&#039;moodlewssettingfileurl&#039;&#039; =&amp;gt; true by default, returned file urls are converted to &#039;http://xxxx/webservice/pluginfile.php/yyyyyyyy&#039;. If false the raw file url content from the DB is returned (e.g. @@PLUGINFILE@@)&lt;br /&gt;
* &#039;&#039;moodlewssettingfilter&#039;&#039; =&amp;gt; false by default. If true, the function will filter during format_text()&lt;br /&gt;
&lt;br /&gt;
=== Moodle  3.5 and later ===&lt;br /&gt;
{{Moodle_3.5}}&lt;br /&gt;
* &#039;&#039;moodlewssettinglang&#039;&#039; =&amp;gt; to force a session language for when retrieving information (same behaviour than using the language selector in the site)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
* [[Creating a web service and a web service function | Implement a web service and a web service function]]&lt;br /&gt;
* [[Web_services_Roadmap|Web service Roadmap]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web Services]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Adding_a_web_service_to_a_plugin&amp;diff=57559</id>
		<title>Adding a web service to a plugin</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Adding_a_web_service_to_a_plugin&amp;diff=57559"/>
		<updated>2020-05-31T11:39:22Z</updated>

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

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

		<summary type="html">&lt;p&gt;Bencellis: /* Officially supported protocols */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
[[Image:Moodle web service function documentation.jpg|thumb]]You need to know how to [https://docs.moodle.org/en/How_to_create_and_enable_a_web_service setup a web service] first.&lt;br /&gt;
To see the API Documentation, connect as Admin and go to &#039;&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; API Documentation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Simple REST request example ==&lt;br /&gt;
&lt;br /&gt;
To quickly test the web service works you can visit the end point from the browser or via curl. For example to call a function via REST protocol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$ curl &amp;quot;https://your.site.com/moodle/webservice/rest/server.php?wstoken=...&amp;amp;wsfunction=...&amp;amp;moodlewsrestformat=json&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Customise the parameters &amp;lt;tt&amp;gt;wstoken&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;wsfunction&amp;lt;/tt&amp;gt; to match the server side setup. Append additional parameters for the function call as needed with &amp;quot;&amp;amp;parameter_name=param&amp;quot;, or if your parameter is an array then use &amp;quot;&amp;amp;array_name[index][parameter_name]=param&amp;quot;. With the core_user_create_users function&#039;s (required) parameters for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$ curl &amp;quot;...&amp;amp;moodlewsrestformat=json&amp;amp;wsfunction=core_user_create_users&amp;amp;moodlewsrestformat=json&amp;amp;users[0][username]=testuser&amp;amp;users[0][firstname]=Anne&amp;amp;users[0][lastname]=Example...&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;moodlewsrestformat&amp;lt;/tt&amp;gt; parameter affects the response format and can be either &amp;lt;tt&amp;gt;xml&amp;lt;/tt&amp;gt; (default) or &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Officially supported protocols ==&lt;br /&gt;
&lt;br /&gt;
; REST : The Moodle REST server accepts GET/POST parameters and return XML/JSON values. This server is not RESTfull.&lt;br /&gt;
; SOAP : The Moodle SOAP server is based on the Zend SOAP server (itself based on the PHP SOAP server). Zend publishes [http://framework.zend.com/manual/en/zend.soap.client.html a Zend SOAP client]. The current server implementation doesn&#039;t fully work with Java/.Net because we didn&#039;t generated a fully describe WSDL yet. If you are working on a Java/.Net client, follow or participate to the tracker issues MDL-28988 / MDL-28989&lt;br /&gt;
; XML-RPC : The Moodle XML-RPC server is based on Zend XML-RPC server. Zend also publishes [http://framework.zend.com/manual/en/zend.xmlrpc.client.html a Zend XML-RPC client].&lt;br /&gt;
; AMF (Versions &amp;lt; Moodle 3.0) : the Moodle AMF server is based on the Zend AMF server. The test client can be found in &#039;&#039;Settings blocks &amp;gt; Site Administration &amp;gt; Development &amp;gt; Web service test client &amp;gt; AMF Test client&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Demo client examples ==&lt;br /&gt;
&lt;br /&gt;
Demo client sample codes can be downloaded on [https://github.com/moodlehq/sample-ws-clients Github].&lt;br /&gt;
&lt;br /&gt;
For HTML5 app creators, you can also find:&lt;br /&gt;
* a nice [https://github.com/jleyva/umm phonegap / Jquery mobile template]&lt;br /&gt;
* a proof of concept of [http://moodle.org/mod/forum/discuss.php?d=189882 javascript cross-domain with Sencha Touch 1.1]&lt;br /&gt;
&lt;br /&gt;
Node.js apps can use the [https://github.com/mudrd8mz/node-moodle-client moodle-client] module.&lt;br /&gt;
&lt;br /&gt;
A [http://moodle.org/mod/forum/discuss.php?d=199453 Java Library for REST] can be found on [http://sourceforge.net/projects/moodlerestjava/ Sourceforge].&lt;br /&gt;
&lt;br /&gt;
A [https://github.com/llagerlof/MoodleRest PHP Library for REST] can be found on GitHub.&lt;br /&gt;
&lt;br /&gt;
A [https://github.com/zaddok/moodle Go Library for REST] can be found on GitHub.&lt;br /&gt;
&lt;br /&gt;
== How to get a user token ==&lt;br /&gt;
{{Moodle_2.2}}&lt;br /&gt;
Your client can call the script located in /login/token.php with a simple HTTP request. We highly recommend to do it securely with HTTPS.&lt;br /&gt;
The required parameters are:&lt;br /&gt;
* username&lt;br /&gt;
* password&lt;br /&gt;
* service shortname - The service shortname is usually hardcoded in the pre-build service (db/service.php files). Moodle administrator will be able to edit shortnames for service created on the fly: MDL-29807. If you want to use the Mobile service, its shortname is &amp;lt;tt&amp;gt;moodle_mobile_app&amp;lt;/tt&amp;gt;. Also useful to know, the database shortname field can be found in the table named external_services.&lt;br /&gt;
&lt;br /&gt;
Request:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
https://www.yourmoodle.com/login/token.php?username=USERNAME&amp;amp;password=PASSWORD&amp;amp;service=SERVICESHORTNAME&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{token:4ed876sd87g6d8f7g89fsg6987dfh78d}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Difference between Moodle versions ===&lt;br /&gt;
&lt;br /&gt;
* Moodle 2.2 and later: the script can generate user tokens for any service shortname (of course users must be allowed on the service, see [[:en:How to create and enable a web service|How to create and enable a web service]]).&lt;br /&gt;
* Moodle 2.1: the script can only generate tokens for the official built-in mobile service. However the script can returns tokens for other services, they just need to have been previously generated.&lt;br /&gt;
&lt;br /&gt;
=== About service shortname ===&lt;br /&gt;
&lt;br /&gt;
At the moment a service can have a shortname if you:&lt;br /&gt;
* create the service as a built-in service (in db/services.php files) &lt;br /&gt;
* add the shortname manually in the DB. Note: we&#039;ll add the admin UI for shortname later (MDL-30229)&lt;br /&gt;
&lt;br /&gt;
== Text formats ==&lt;br /&gt;
=== Moodle 2.0 to 2.2 ===&lt;br /&gt;
{{Moodle_2.0}}&lt;br /&gt;
HTML is the format sent/received by web service functions. All returned file urls are converted to &#039;http://xxxx/webservice/pluginfile.php/yyyyyyyy&#039;&lt;br /&gt;
&lt;br /&gt;
=== Moodle 2.3 and later ===&lt;br /&gt;
{{Moodle_2.3}}&lt;br /&gt;
Since Moodle 2.3 you can add few GET/POST parameters to your request (for devs who have a good knowledge of File API and format_text()):&lt;br /&gt;
* &#039;&#039;moodlewssettingraw&#039;&#039; =&amp;gt; false by default. If true, the function will not apply format_text() to description/summary/textarea. The function will return the raw content from the DB.&lt;br /&gt;
* &#039;&#039;moodlewssettingfileurl&#039;&#039; =&amp;gt; true by default, returned file urls are converted to &#039;http://xxxx/webservice/pluginfile.php/yyyyyyyy&#039;. If false the raw file url content from the DB is returned (e.g. @@PLUGINFILE@@)&lt;br /&gt;
* &#039;&#039;moodlewssettingfilter&#039;&#039; =&amp;gt; false by default. If true, the function will filter during format_text()&lt;br /&gt;
&lt;br /&gt;
=== Moodle  3.5 and later ===&lt;br /&gt;
{{Moodle_3.5}}&lt;br /&gt;
* &#039;&#039;moodlewssettinglang&#039;&#039; =&amp;gt; to force a session language for when retrieving information (same behaviour than using the language selector in the site)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
* [[Creating a web service and a web service function | Implement a web service and a web service function]]&lt;br /&gt;
* [[Web_services_Roadmap|Web service Roadmap]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web Services]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Web_services_files_handling&amp;diff=57556</id>
		<title>Web services files handling</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Web_services_files_handling&amp;diff=57556"/>
		<updated>2020-05-30T09:32:22Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* File upload */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 2.0, we provide web service functions to upload and download files. They are:&lt;br /&gt;
&lt;br /&gt;
# moodle_file_get_files (Deprecated, use core_files_get_files since moodle 2.2 onward)&lt;br /&gt;
# moodle_file_upload (Deprecated, use core_files_upload since moodle 2.2 onward)&lt;br /&gt;
&lt;br /&gt;
File contents are encoded in base64, and for web service transmission, it&#039;s not efficient. Mobile devices don&#039;t have enough memory to decode/encode web service request/response containing large files.&lt;br /&gt;
&lt;br /&gt;
So we developed some alternative solutions to upload/download files.&lt;br /&gt;
&lt;br /&gt;
== File upload ==&lt;br /&gt;
&lt;br /&gt;
The entry point is &#039;&#039;/webservice/upload.php&#039;&#039;, simply use HTTP POST method to upload files, it requires a web service token for authentication. If the upload is successfully, the files will be saved, prior to at least Moodle 2.9 in the user private file area but since in the user draft area.  Previously you could force the files to be saved in the draft area by specifying the then  optional parameter: &#039;&#039;filearea=draft&#039;&#039;.  Since at least Moodle 2.9, only two optional parameters are used, &#039;&#039;itemid&#039;&#039; to specify the draft area id – default 0 which is a new draft area - and &#039;&#039;filepath&#039;&#039; default  \‘/\’ to specify the file’s path.&lt;br /&gt;
&lt;br /&gt;
It is envisaged the &#039;&#039;itemid&#039;&#039; parameter will be used when the files are uploaded singularly in separate HTTP calls and the files are required to be in the same draft file area.  The client retrieves the &#039;&#039;itemid&#039;&#039; of the first uploaded file and uses it in subsequent uploads to specify the files must be saved in the same draft file area.&lt;br /&gt;
&lt;br /&gt;
On every successful upload, the file/s information are returned in JSON format. If an error occurs, an error message will be sent back in JSON format too.&lt;br /&gt;
&lt;br /&gt;
Once all the files are uploaded, you can call the webservice that accepts files and pass it the &#039;&#039;itemid&#039;&#039; of the draft area containing the list of files for the request. The service can identify the uploads and manipulate them as necessary.  An example of a webservice that accepts files is: &#039;&#039;mod_assign_save_submission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
To accept file uploads, the the service must allow &amp;quot;files download&amp;quot; (&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; Manage services &amp;gt; Edit service &amp;gt; Advanced button&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
There is an oldish code example on [https://github.com/moodlehq/sample-ws-clients/tree/master/PHP-HTTP-filehandling code example on Github].&lt;br /&gt;
&lt;br /&gt;
== File download ==&lt;br /&gt;
We serve the files through &#039;&#039;/webservice/pluginfile.php&#039;&#039;. This script requires a web service token for authentication. &lt;br /&gt;
&lt;br /&gt;
Look at the [https://github.com/moodlehq/sample-ws-clients/tree/master/PHP-HTTP-filehandling code example on Github].&lt;br /&gt;
&lt;br /&gt;
In case of issue, think to check that:&lt;br /&gt;
# the service associated with the token allow &amp;quot;&#039;&#039;files download&#039;&#039;&amp;quot;  (&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; Manage services &amp;gt; Edit service &amp;gt; Advanced button&#039;&#039;)&lt;br /&gt;
# the web service is valid&lt;br /&gt;
&lt;br /&gt;
Note: you could notice that &#039;&#039;/webservice/pluginfile.php&#039;&#039; has the exact same stucture than &#039;&#039;/pluginfile.php&#039;&#039;. We don&#039;t serve the files through &#039;&#039;/pluginfile.php&#039;&#039; for web service clients because this script requires user&#039;s login session to work. It&#039;s why it might not be an option for web service client.&lt;br /&gt;
&lt;br /&gt;
== Returning files in Web Services ==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.2, you can return a complete file area list via Web Services using the static get_area_files method in external_util.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $forum-&amp;gt;introfiles = external_util::get_area_files($context-&amp;gt;id, &#039;mod_forum&#039;, &#039;intro&#039;, false, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also use the external_files structure definition in combination with the method to return the most common file fields required by WS clientes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     public static function get_forums_by_courses_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;Forum id&#039;),&lt;br /&gt;
                     ....&lt;br /&gt;
                    &#039;introfiles&#039; =&amp;gt; new external_files(&#039;Files in the introduction text&#039;, VALUE_OPTIONAL),&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web_services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web_Services]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Web_services_files_handling&amp;diff=57555</id>
		<title>Web services files handling</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Web_services_files_handling&amp;diff=57555"/>
		<updated>2020-05-30T09:32:03Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* File upload */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 2.0, we provide web service functions to upload and download files. They are:&lt;br /&gt;
&lt;br /&gt;
# moodle_file_get_files (Deprecated, use core_files_get_files since moodle 2.2 onward)&lt;br /&gt;
# moodle_file_upload (Deprecated, use core_files_upload since moodle 2.2 onward)&lt;br /&gt;
&lt;br /&gt;
File contents are encoded in base64, and for web service transmission, it&#039;s not efficient. Mobile devices don&#039;t have enough memory to decode/encode web service request/response containing large files.&lt;br /&gt;
&lt;br /&gt;
So we developed some alternative solutions to upload/download files.&lt;br /&gt;
&lt;br /&gt;
== File upload ==&lt;br /&gt;
&lt;br /&gt;
The entry point is &#039;&#039;/webservice/upload.php&#039;&#039;, simply use HTTP POST method to upload files, it requires a web service token for authentication. If the upload is successfully, the files will be saved, prior to at least Moodle 2.9 in the user private file area but since in the user draft area.  Previously you could force the files to be saved in the draft area by specifying the then  optional parameter: &#039;&#039;filearea=draft&#039;&#039;.  Since at least Moodle 2.9, only two optional parameters are used, &#039;&#039;itemid&#039;&#039; to specify the draft area id – default 0 which is a new draft area - and &#039;&#039;filepath&#039;&#039; default  \‘/\’ to specify the file’s path.&lt;br /&gt;
&lt;br /&gt;
It is envisaged the &#039;&#039;itemid&#039;&#039; parameter will be used when the files are uploaded singularly in separate HTTP calls and the files are required to be in the same draft file area.  The client retrieves the &#039;&#039;itemid&#039;&#039; of the first uploaded file and uses it in subsequent uploads to specify the files must be saved in the same draft file area.&lt;br /&gt;
&lt;br /&gt;
On every successful upload, the file/s information are returned in JSON format. If an error occurs, an error message will be sent back in JSON format too.&lt;br /&gt;
&lt;br /&gt;
Once all the files are uploaded, you can call the webservice that accepts files and pass it the &#039;&#039;itemid&#039;&#039; of the draft area containing the list of files for the request. The service can identify the uploads and manipulate them as necessary.  An example of a webservice that accepts files is: &#039;&#039;mod_assign_save_submission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
To accept file uploads, the the service must allow &amp;quot;files download&amp;quot; (Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; Manage services &amp;gt; Edit service &amp;gt; Advanced button)&lt;br /&gt;
&lt;br /&gt;
There is an oldish code example on [https://github.com/moodlehq/sample-ws-clients/tree/master/PHP-HTTP-filehandling code example on Github].&lt;br /&gt;
&lt;br /&gt;
== File download ==&lt;br /&gt;
We serve the files through &#039;&#039;/webservice/pluginfile.php&#039;&#039;. This script requires a web service token for authentication. &lt;br /&gt;
&lt;br /&gt;
Look at the [https://github.com/moodlehq/sample-ws-clients/tree/master/PHP-HTTP-filehandling code example on Github].&lt;br /&gt;
&lt;br /&gt;
In case of issue, think to check that:&lt;br /&gt;
# the service associated with the token allow &amp;quot;&#039;&#039;files download&#039;&#039;&amp;quot;  (&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; Manage services &amp;gt; Edit service &amp;gt; Advanced button&#039;&#039;)&lt;br /&gt;
# the web service is valid&lt;br /&gt;
&lt;br /&gt;
Note: you could notice that &#039;&#039;/webservice/pluginfile.php&#039;&#039; has the exact same stucture than &#039;&#039;/pluginfile.php&#039;&#039;. We don&#039;t serve the files through &#039;&#039;/pluginfile.php&#039;&#039; for web service clients because this script requires user&#039;s login session to work. It&#039;s why it might not be an option for web service client.&lt;br /&gt;
&lt;br /&gt;
== Returning files in Web Services ==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.2, you can return a complete file area list via Web Services using the static get_area_files method in external_util.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $forum-&amp;gt;introfiles = external_util::get_area_files($context-&amp;gt;id, &#039;mod_forum&#039;, &#039;intro&#039;, false, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also use the external_files structure definition in combination with the method to return the most common file fields required by WS clientes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     public static function get_forums_by_courses_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;Forum id&#039;),&lt;br /&gt;
                     ....&lt;br /&gt;
                    &#039;introfiles&#039; =&amp;gt; new external_files(&#039;Files in the introduction text&#039;, VALUE_OPTIONAL),&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web_services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web_Services]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Web_services_files_handling&amp;diff=57554</id>
		<title>Web services files handling</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Web_services_files_handling&amp;diff=57554"/>
		<updated>2020-05-30T09:30:23Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* File upload */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 2.0, we provide web service functions to upload and download files. They are:&lt;br /&gt;
&lt;br /&gt;
# moodle_file_get_files (Deprecated, use core_files_get_files since moodle 2.2 onward)&lt;br /&gt;
# moodle_file_upload (Deprecated, use core_files_upload since moodle 2.2 onward)&lt;br /&gt;
&lt;br /&gt;
File contents are encoded in base64, and for web service transmission, it&#039;s not efficient. Mobile devices don&#039;t have enough memory to decode/encode web service request/response containing large files.&lt;br /&gt;
&lt;br /&gt;
So we developed some alternative solutions to upload/download files.&lt;br /&gt;
&lt;br /&gt;
== File upload ==&lt;br /&gt;
&lt;br /&gt;
The entry point is &#039;&#039;/webservice/upload.php&#039;&#039;, simply use HTTP POST method to upload files, it requires a web service token for authentication. If the upload is successfully, the files will be saved, prior to at least Moodle 2.9 in the user private file area but since in the user draft area.  Previously you could force the files to be saved in the draft area by specifying the then  optional parameter: &#039;&#039;filearea=draft&#039;&#039;.  Since at least Moodle 2.9, only two optional parameters are used, &#039;&#039;itemid&#039;&#039; to specify the draft area id – default 0 which is a new draft area - and &#039;&#039;filepath&#039;&#039; default  \‘/\’ to specify the file’s path.&lt;br /&gt;
&lt;br /&gt;
It is envisaged the &#039;&#039;itemid&#039;&#039; parameter will be used when the files are uploaded singularly in separate HTTP calls and the files are required to be in the same draft file area.  The client retrieves the &#039;&#039;itemid&#039;&#039; of the first uploaded file and uses it in subsequent uploads to specify the files must be saved in the same draft file area.&lt;br /&gt;
&lt;br /&gt;
On every successful upload, the file/s information are returned in JSON format. If an error occurs, an error message will be sent back in JSON format too.&lt;br /&gt;
&lt;br /&gt;
Once all the files are uploaded, you can call the webservice that accepts files and pass it the &#039;&#039;itemid&#039;&#039; of the draft area containing the list of files for the request. The service can identify the uploads and manipulate them as necessary.  An example of a webservice that accepts files is: &#039;&#039;mod_assign_save_submission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There is an oldish code example on [https://github.com/moodlehq/sample-ws-clients/tree/master/PHP-HTTP-filehandling code example on Github].&lt;br /&gt;
&lt;br /&gt;
== File download ==&lt;br /&gt;
We serve the files through &#039;&#039;/webservice/pluginfile.php&#039;&#039;. This script requires a web service token for authentication. &lt;br /&gt;
&lt;br /&gt;
Look at the [https://github.com/moodlehq/sample-ws-clients/tree/master/PHP-HTTP-filehandling code example on Github].&lt;br /&gt;
&lt;br /&gt;
In case of issue, think to check that:&lt;br /&gt;
# the service associated with the token allow &amp;quot;&#039;&#039;files download&#039;&#039;&amp;quot;  (&#039;&#039;Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; Manage services &amp;gt; Edit service &amp;gt; Advanced button&#039;&#039;)&lt;br /&gt;
# the web service is valid&lt;br /&gt;
&lt;br /&gt;
Note: you could notice that &#039;&#039;/webservice/pluginfile.php&#039;&#039; has the exact same stucture than &#039;&#039;/pluginfile.php&#039;&#039;. We don&#039;t serve the files through &#039;&#039;/pluginfile.php&#039;&#039; for web service clients because this script requires user&#039;s login session to work. It&#039;s why it might not be an option for web service client.&lt;br /&gt;
&lt;br /&gt;
== Returning files in Web Services ==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.2, you can return a complete file area list via Web Services using the static get_area_files method in external_util.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $forum-&amp;gt;introfiles = external_util::get_area_files($context-&amp;gt;id, &#039;mod_forum&#039;, &#039;intro&#039;, false, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also use the external_files structure definition in combination with the method to return the most common file fields required by WS clientes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     public static function get_forums_by_courses_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;Forum id&#039;),&lt;br /&gt;
                     ....&lt;br /&gt;
                    &#039;introfiles&#039; =&amp;gt; new external_files(&#039;Files in the introduction text&#039;, VALUE_OPTIONAL),&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Web_services|Web services developer documentation]]&lt;br /&gt;
* [[:en:Web_services|Web services user documentation]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Web_Services]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=AJAX&amp;diff=57463</id>
		<title>AJAX</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=AJAX&amp;diff=57463"/>
		<updated>2020-05-12T10:24:45Z</updated>

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

		<summary type="html">&lt;p&gt;Bencellis: /* supported_filetypes() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
====Course and User Repository Instances.====&lt;br /&gt;
By default, a system wide instance of a repository is created when the repository is enabled.  If you want to allow either or both course specific and user repository instances, you have to specify both &#039;&#039;&#039;enablecourseinstances&#039;&#039;&#039; and &#039;&#039;&#039;enableuserinstances&#039;&#039;&#039; as options.  There are three ways this can be done:  &lt;br /&gt;
&lt;br /&gt;
# Define &#039;&#039;$string[&#039; enablecourseinstances&#039;]&#039;&#039; and &#039;&#039;$string[&#039;enableuserinstances&#039;]&#039;&#039; in your plugin&#039;s language file.&lt;br /&gt;
# Add them as items in &#039;&#039;get_type_option_names()&#039;&#039; function&#039;s return array.  Note, you must not define the form fields for these options in the &#039;&#039;type_config_form()&#039;&#039; function.&lt;br /&gt;
# Several &#039;core&#039; repositories use the &#039;&#039;&#039;&#039;&#039;db/install.php&#039;&#039;&#039;&#039;&#039; to create the original repository instance by constructing an instance of the &#039;&#039;repository_type&#039;&#039; class.  The options can be defined in the array passed as the second parameter to the constructor.&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform, $classname=&#039;repository&#039;) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;:  You cannot call &#039;&#039;$this&#039;&#039; from static methods.  If you need access the non static variables, you may have to store the values in the &#039;&#039;__construct()&#039;&#039; method into private static variables.&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57291</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57291"/>
		<updated>2020-04-14T08:49:15Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Getting / updating settings */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform, $classname=&#039;repository&#039;) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;:  You cannot call &#039;&#039;$this&#039;&#039; from static methods.  If you need access the non static variables, you may have to store the values in the &#039;&#039;__construct()&#039;&#039; method into private static variables.&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57290</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57290"/>
		<updated>2020-04-14T08:39:23Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* instance_form_validation($mform, $data, $errors) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform, $classname=&#039;repository&#039;) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57289</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57289"/>
		<updated>2020-04-13T10:42:42Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* type_config_form($mform, $classname=&amp;#039;repository&amp;#039;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform, $classname=&#039;repository&#039;) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57288</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57288"/>
		<updated>2020-04-13T10:42:29Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* type_config_form($mform, $classname=&amp;#039;repository&amp;#039;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform, , $classname=&#039;repository&#039;) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57287</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57287"/>
		<updated>2020-04-13T10:35:03Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* type_form_validation($mform, $data, $errors) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57286</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57286"/>
		<updated>2020-04-13T10:33:27Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* type_config_form($mform, $classname=&amp;#039;repository&amp;#039;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_config_form($mform) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57285</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=57285"/>
		<updated>2020-04-13T10:26:25Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* get_type_option_names() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Repository plugins}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
==Creating new repository plugin==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;/repository/&#039;&#039; e.g. &#039;&#039;/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files in your plugin folder:&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lib.php&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/pix/icon.png&#039;&#039; - the icon displayed in the file picker (16x16)&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/[[version.php]]&#039;&#039;&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; - language file&lt;br /&gt;
#* &#039;&#039;/repository/myplugin/db/access.php&#039;&#039;&lt;br /&gt;
# Declare class &#039;&#039;&#039;repository_myplugin extends repository&#039;&#039;&#039; in your lib.php&lt;br /&gt;
# In your repository_myplugin class overwrite function get_listing() to &#039;&#039;&#039;return array(&#039;list&#039; =&amp;gt; array());&#039;&#039;&#039;&lt;br /&gt;
# Add at least strings &#039;&#039;&#039;$string[&#039;pluginname&#039;]&#039;&#039;&#039; and &#039;&#039;&#039;$string[&#039;configplugin&#039;]&#039;&#039;&#039; to your language file&lt;br /&gt;
# Add capability &#039;repository/myplugin:view&#039; to your access.php file&lt;br /&gt;
# Create install and upgrade scripts (optional or you can do it later)&lt;br /&gt;
# Login as admin on your website and run upgrade&lt;br /&gt;
# Open Site Administration-&amp;gt;Plugins-&amp;gt;Repositories-&amp;gt;Manage Repositories and make your repository &#039;Enabled and visible&#039;&lt;br /&gt;
&lt;br /&gt;
For a more detailed explanation of each of each of the files that have been created here, along with code examples, see [[Repository plugin files]].&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
===Fixed settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are hard-coded into your repository plugin and can only be updated by changing the plugin code.&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
Return any combination of the following values:&lt;br /&gt;
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.&lt;br /&gt;
* FILE_EXTERNAL - the file stays in the external repository and is accessed from there directly.&lt;br /&gt;
* FILE_REFERENCE - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.&lt;br /&gt;
* FILE_CONTROLLED_LINK - the file remains in the external repository. By &amp;quot;uploading&amp;quot; it, ownership of the file (in the remote system) is changed so that the [[:en:OAuth_2_services#Connecting_a_system_account|system account in the external repository]] becomes the new owner of the file. Later, if the file is accessed, the system account is responsible for granting access to users.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_returntypes() {&lt;br /&gt;
    return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:File options.png|thumb|Choices offered resulting from the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.]]&lt;br /&gt;
The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot excerpt on the right, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED_LINK being present. FILE_REFERENCE corresponds to the &amp;quot;alias/shortcut&amp;quot; option. &lt;br /&gt;
The option FILE_EXTERNAL is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, FILE_EXTERNAL is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support FILE_EXTERNAL.&lt;br /&gt;
&lt;br /&gt;
This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows FILE_EXTERNAL and FILE_REFERENCE.&lt;br /&gt;
&lt;br /&gt;
In the end, which type is&lt;br /&gt;
used by Moodle depends on the choices made by the end user (e.g. inserting a link, will result in &#039;FILE_EXTERNAL&#039;-related functions being used, using a &#039;shortcut/alias&#039; will result in the &#039;FILE_REFERENCE&#039;-related functions being used).&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
Optional. Returns &#039;*&#039; for all file types (default implementation), or an array of types or groups (e.g. array(&#039;text/plain&#039;, &#039;image/gif&#039;, &#039;web_image&#039;) )&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function supported_filetypes() {&lt;br /&gt;
    //return &#039;*&#039;;&lt;br /&gt;
    //return array(&#039;image/gif&#039;, &#039;image/jpeg&#039;, &#039;image/png&#039;);&lt;br /&gt;
    return array(&#039;web_image&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
For a full list of possible types and groups, look in lib/filelib.php, function get_mimetypes_array().&lt;br /&gt;
&lt;br /&gt;
===Global settings===&lt;br /&gt;
&lt;br /&gt;
These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration &amp;gt; Plugins &amp;gt; Repositories &amp;gt; Myplugin page.&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function returns an array with a single item - pluginname.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
   return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form($mform, $classname=&#039;repository&#039;)====&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the plugin settings. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function type_config_form($mform) {&lt;br /&gt;
    parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
    $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
    $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided (&#039;settingname&#039; =&amp;gt; value) for any errors. Then push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;human readable error message&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
        $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Instance settings===&lt;br /&gt;
These functions relate to a specific instance of your plugin (e.g. the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single &#039;name&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array. This is equivalent to &#039;&#039;get_type_option_names()&#039;&#039;, but for a specific instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_instance_option_names() {&lt;br /&gt;
    return array(&#039;fs_path&#039;); // From repository_filesystem&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====instance_config_form($mform)====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to &#039;&#039;type_config_form($mform, $classname)&#039;&#039; but for instances. [[lib/formslib.php Form Definition]] has details of all the types of elements you can add to the settings form.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to &#039;&#039;type_form_validation($mform, $data, $errors), but for instances. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
    if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
        $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    return $errors;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Getting / updating settings====&lt;br /&gt;
&lt;br /&gt;
Both global and instance settings can be retrieved, from within the plugin, via $this-&amp;gt;get_option(&#039;settingname&#039;) and updated via $this-&amp;gt;set_option(array(&#039;settingname&#039; =&amp;gt; &#039;value&#039;)).&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
===Example of using the settings===&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * repository_flickr_public class&lt;br /&gt;
 * Moodle user can access public flickr account&lt;br /&gt;
 *&lt;br /&gt;
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
*/&lt;br /&gt;
class repository_flickr_public extends repository {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public static function instance_config_form($mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form($mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository::static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install [[FirePHP]] (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
&lt;br /&gt;
These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.&lt;br /&gt;
&lt;br /&gt;
====__construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)====&lt;br /&gt;
Should be overridden to do any initialisation required by the repository, including:&lt;br /&gt;
* logging in via optional_param, if required - see &#039;print_login&#039;, below&lt;br /&gt;
* getting any options from the database&lt;br /&gt;
&lt;br /&gt;
The possible items in the $options array are:&lt;br /&gt;
* &#039;ajax&#039; - bool, true if the user is using the AJAX filepicker&lt;br /&gt;
* &#039;mimetypes&#039; - array of accepted mime types, or &#039;*&#039; for all types&lt;br /&gt;
&lt;br /&gt;
Calling parent::__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:&lt;br /&gt;
* $this-&amp;gt;id - the repository instance id (the ID of the entry in mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;context - the context in which the repository instance can be found&lt;br /&gt;
* $this-&amp;gt;instance - the repository instance record (from mdl_repository_instances)&lt;br /&gt;
* $this-&amp;gt;readonly - whether or not the settings can be changed&lt;br /&gt;
* $this-&amp;gt;options - the above options, combined with the settings saved in the database&lt;br /&gt;
* $this-&amp;gt;name - as specified by $this-&amp;gt;get_name()&lt;br /&gt;
* $this-&amp;gt;returntypes - as specified by $this-&amp;gt;supported_returntypes()&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files to be displayed to the user, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;10451213&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;1340002147&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Amongst other details, this returns a &#039;&#039;&#039;title&#039;&#039;&#039; for each file (to be displayed in the filepicker) and the &#039;&#039;&#039;source&#039;&#039;&#039; for the file (which will be included in the request to &#039;download&#039; the file into Moodle or to generate a link to the file). Directories return a &#039;&#039;&#039;children&#039;&#039;&#039; value, which is either an empty array (if &#039;dynload&#039; is specified) or an array of the files and directories contained within it.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // &#039;path&#039; is used to build navigation bar to show the current folder, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   // This will result in: /root/subfolder as current directory&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // &#039;dynload&#039; tells file picker to fetch list dynamically.&lt;br /&gt;
   // When user clicks the folder, it will send a ajax request to server side.&lt;br /&gt;
   // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be set.&lt;br /&gt;
   // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (int) how many pages. If number of pages is unknown but we know that the next page exists repository may return -1&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url to file manager for the external repository, if specified will display link in file picker&lt;br /&gt;
   &#039;help&#039; =&amp;gt; (string) url to the help window, if specified will display link in file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login, default false, if set to true the login link will be removed from file picker&lt;br /&gt;
   &#039;norefresh&#039; =&amp;gt; (bool) no refresh button, default false&lt;br /&gt;
   &#039;logouttext&#039; =&amp;gt; (string) in case of nologin=false can substitute the text &#039;Logout&#039; for logout link in file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link, default false, if set to true the search link will be removed from file picker&lt;br /&gt;
   &#039;issearchresult&#039; =&amp;gt; (bool) tells that this listing is the result of search&lt;br /&gt;
   // for repositories that actually upload a file: set &#039;upload&#039; option to display an upload form in file picker&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // &#039;list&#039; is used by file picker to build a file/folder tree&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (int) UNIX timestamp, default value for datemodified and datecreated,&lt;br /&gt;
       &#039;datemodified&#039; =&amp;gt; (int) UNIX timestamp when the file was last modified [2.3+],&lt;br /&gt;
       &#039;datecreated&#039; =&amp;gt; (int) UNIX timestamp when the file was last created [2.3+],&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size in bytes,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;thumbnail_height&#039; =&amp;gt; (int) the height of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039; =&amp;gt; the accessible url of file,&lt;br /&gt;
       &#039;icon&#039; =&amp;gt; (string) url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [2.3+],&lt;br /&gt;
       &#039;realthumbnail&#039; =&amp;gt; (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as &#039;thumbnail&#039;) [2.3+],&lt;br /&gt;
       &#039;realicon&#039; =&amp;gt; (string) url to image preview in icon size (24x24) [2.3+],&lt;br /&gt;
       &#039;author&#039; =&amp;gt; (string) default value for file author,&lt;br /&gt;
       &#039;license&#039; =&amp;gt; (string) default value for license (short name, see class license_manager),&lt;br /&gt;
       &#039;image_height&#039; =&amp;gt; (int) if the file is an image, image height in pixels, null otherwise [2.3+],&lt;br /&gt;
       &#039;image_width&#039; =&amp;gt;  (int) if the file is an image, image width in pixels, null otherwise [2.3+]&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - similar to file, has also &#039;path&#039; and &#039;children&#039; but no &#039;source&#039; or &#039;url&#039;&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children&lt;br /&gt;
       &#039;date&#039;, &#039;datemodified&#039;, &#039;datecreated&#039;, &#039;thumbnail&#039;, &#039;icon&#039; =&amp;gt; see above,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( &lt;br /&gt;
         // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array&lt;br /&gt;
         // otherwise it is a nested list of contained files and folders&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
// The &#039;object&#039; tag can be used to embed an external web page or application within the filepicker&lt;br /&gt;
   &#039;object&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;type&#039; =&amp;gt; (string) e.g. &#039;text/html&#039;, &#039;application/x-shockwave-flash&#039;&lt;br /&gt;
      &#039;src&#039; =&amp;gt; (string) the website address to embed in the object&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
The use of the &#039;&#039;&#039;object&#039;&#039;&#039; tag, instead of returning a &#039;&#039;list&#039;&#039; of files, allows you to embed an external file chooser within the repository panel. See [[Repository plugins embedding external file chooser]] for details about how to do this.&lt;br /&gt;
&lt;br /&gt;
===User login (optional)===&lt;br /&gt;
If the plugin requires login from the user at the time when they use it, then these functions can be used.&lt;br /&gt;
&lt;br /&gt;
====print_login====&lt;br /&gt;
Returns an array of the elements required in the login form. If no login form is required, then the default implementation of this will redirect to the files list. If $this-&amp;gt;options[&#039;ajax&#039;] is not set, then an HTML-snippet with the login fields (but not the form tags) should be output, instead of returning the form details.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login() { // From repository_alfresco&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $user_field = new stdClass();&lt;br /&gt;
        $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $user_field-&amp;gt;id    = &#039;alfresco_username&#039;;&lt;br /&gt;
        $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
        $user_field-&amp;gt;name  = &#039;al_username&#039;;&lt;br /&gt;
&lt;br /&gt;
        $passwd_field = new stdClass();&lt;br /&gt;
        $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;: &#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;id    = &#039;alfresco_password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
        $passwd_field-&amp;gt;name  = &#039;al_password&#039;;&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else { // Non-AJAX login form - directly output the form elements&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;al_username&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_alfresco&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;al_password&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Enter&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your login form is static and never changes, you can add &#039;&#039;$ret[&#039;allowcaching&#039;] = true;&#039;&#039; and filepicker will not send the request to the server every time user opens the login/search form.&lt;br /&gt;
&lt;br /&gt;
For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the &#039;__construct&#039; function, via $submitted = optional_param(&#039;fieldname&#039;, [defaultvalue], PARAM_INT/PARAM_TEXT).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {&lt;br /&gt;
// Taken from repository_alfresco&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;alfresco = new Alfresco_Repository($this-&amp;gt;options[&#039;alfresco_url&#039;]);        &lt;br /&gt;
        $this-&amp;gt;username = optional_param(&#039;al_username&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        $this-&amp;gt;password = optional_param(&#039;al_password&#039;, &#039;&#039;, PARAM_RAW);&lt;br /&gt;
        try{&lt;br /&gt;
            // deal with user logging in&lt;br /&gt;
            if (empty($SESSION-&amp;gt;{$this-&amp;gt;sessname}) &amp;amp;&amp;amp; !empty($this-&amp;gt;username) &amp;amp;&amp;amp; !empty($this-&amp;gt;password)) {&lt;br /&gt;
                $this-&amp;gt;ticket = $this-&amp;gt;alfresco-&amp;gt;authenticate($this-&amp;gt;username, $this-&amp;gt;password);&lt;br /&gt;
                $SESSION-&amp;gt;{$this-&amp;gt;sessname} = $this-&amp;gt;ticket;&lt;br /&gt;
            } else {&lt;br /&gt;
                if (!empty($SESSION-&amp;gt;{$this-&amp;gt;sessname})) {&lt;br /&gt;
                    $this-&amp;gt;ticket = $SESSION-&amp;gt;{$this-&amp;gt;sessname};&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            $this-&amp;gt;user_session = $this-&amp;gt;alfresco-&amp;gt;createSession($this-&amp;gt;ticket);&lt;br /&gt;
            $this-&amp;gt;store = new SpacesStore($this-&amp;gt;user_session);&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $this-&amp;gt;logout();&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;current_node = null;&lt;br /&gt;
&lt;br /&gt;
/* Skipping code that is not relevant to user login */&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many types include a single element of type &#039;popup&#039; with the param &#039;url&#039; pointing at the URL used to authenticate the repo instance.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_login(){ // Code taken from repository_boxnet&lt;br /&gt;
    $t = $this-&amp;gt;boxclient-&amp;gt;getTicket();&lt;br /&gt;
    if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
        $popup_btn = new stdClass();&lt;br /&gt;
        $popup_btn-&amp;gt;type = &#039;popup&#039;;&lt;br /&gt;
        $popup_btn-&amp;gt;url = &#039; https://www.box.com/api/1.0/auth/&#039; . $t[&#039;ticket&#039;];&lt;br /&gt;
&lt;br /&gt;
        $ret = array();&lt;br /&gt;
        $ret[&#039;login&#039;] = array($popup_btn);&lt;br /&gt;
        return $ret;&lt;br /&gt;
    } else {&lt;br /&gt;
        echo &#039;&amp;lt;table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;boxusername&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;label&amp;gt;&#039;.get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;&amp;lt;/label&amp;gt;&amp;lt;/td&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;boxpassword&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;ticket&amp;quot; value=&amp;quot;&#039;.$t[&#039;ticket&#039;].&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
        echo &#039;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&#039;.get_string(&#039;enter&#039;, &#039;repository&#039;).&#039;&amp;quot; /&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_login() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    return !empty($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling $this-&amp;gt;print_login() ):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function logout() { // Taken from repository_alfresco&lt;br /&gt;
    global $SESSION;&lt;br /&gt;
    unset($SESSION-&amp;gt;{$this-&amp;gt;sessname});&lt;br /&gt;
    return $this-&amp;gt;print_login();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Transferring files to Moodle (optional)===&lt;br /&gt;
These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.&lt;br /&gt;
&lt;br /&gt;
====get_file_reference($source)====&lt;br /&gt;
This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $filename = &amp;quot;&amp;quot;)====&lt;br /&gt;
For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on &#039;select this file&#039; to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the &#039;source&#039; returned by &#039;get_listing&#039;). The $filename should usually be processed by $path = $this-&amp;gt;prepare_file($filename), giving the full &#039;path&#039; where the file should be saved locally. This function then returns an array, containing:&lt;br /&gt;
* path - the local path where the file was saved&lt;br /&gt;
* url - the $url param passed into the function&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($url, $filename = &#039;&#039;) {&lt;br /&gt;
// Default implementation from the base &#039;repository&#039; class&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename); // Generate a unique temporary filename&lt;br /&gt;
    $c = new curl;&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file($reference, $filename = &#039;&#039;) {&lt;br /&gt;
// Slightly extended version taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
// Extract the details saved in the &#039;source&#039; param by &lt;br /&gt;
// repository/equella/callback.php (now in the $reference paramater)&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    $path = $this-&amp;gt;prepare_file($filename);&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039;=&amp;gt;$cookiepathname));&lt;br /&gt;
    $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::GETFILE_TIMEOUT));&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
    if ($result !== true) {&lt;br /&gt;
        throw new moodle_exception(&#039;errorwhiledownload&#039;, &#039;repository&#039;, &#039;&#039;, $result);&lt;br /&gt;
    }&lt;br /&gt;
    return array(&#039;path&#039;=&amp;gt;$path, &#039;url&#039;=&amp;gt;$url);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_link($url)====&lt;br /&gt;
Used with FILE_EXTERNAL to convert a reference (from &#039;get_file_reference&#039;, but ultimately from the output of &#039;get_listing&#039;) into a URL that can be used directly by the end-user&#039;s browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}====get_file_source_info($source)====&lt;br /&gt;
Takes the &#039;source&#039; field from &#039;get_listing&#039; (as returned by the user&#039;s browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.&lt;br /&gt;
Examples: &#039;Dropbox: /filename.jpg&#039;, &#039;http://fullurl.com/path/file&#039;, etc.&lt;br /&gt;
This value will be used to display warning message if reference can not be restored from backup.  Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.&lt;br /&gt;
&lt;br /&gt;
===Search functions (optional)===&lt;br /&gt;
&lt;br /&gt;
These functions allow you to implement search functionality within your repository.&lt;br /&gt;
&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
&lt;br /&gt;
A custom search form must include the following:&lt;br /&gt;
* A text field element named &#039;&#039;&#039;s&#039;&#039;&#039;, this is where users will type in their search criteria&lt;br /&gt;
&lt;br /&gt;
The following fields are automatically inserted in Moodle 2.3+ (but may need to be manually included in earlier versions):&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;repo_id&#039;&#039;&#039; and the value must be the id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;ctx_id&#039;&#039;&#039; and the value must be the context id of the repository instance&lt;br /&gt;
* A hidden element named &#039;&#039;&#039;sesskey&#039;&#039;&#039; and the value must be the session key&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function print_search() {&lt;br /&gt;
    // The default implementation in class &#039;repository&#039;&lt;br /&gt;
    global $PAGE;&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;core&#039;, &#039;files&#039;);&lt;br /&gt;
    return $renderer-&amp;gt;repository_default_searchform();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// From core_files_renderer (repository/renderer.php)&lt;br /&gt;
public function repository_default_searchform() {&lt;br /&gt;
    $str = &#039;&amp;lt;div class=&amp;quot;fp-def-search&amp;quot;&amp;gt;&amp;lt;input name=&amp;quot;s&amp;quot; value=&#039;.get_string(&#039;search&#039;, &#039;repository&#039;).&#039; /&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    return $str;&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;
public function print_search() {&lt;br /&gt;
&lt;br /&gt;
    // label search name&lt;br /&gt;
    $param = array(&#039;for&#039; =&amp;gt; &#039;label_search_name&#039;);&lt;br /&gt;
    $title = get_string(&#039;search_name&#039;, &#039;myrepo_search_name&#039;);&lt;br /&gt;
    $html .= html_writer::tag(&#039;label&#039;, $title, $param);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
&lt;br /&gt;
    // text field search name&lt;br /&gt;
    $attributes[&#039;type&#039;] = &#039;text&#039;;&lt;br /&gt;
    $attributes[&#039;name&#039;] = &#039;s&#039;;&lt;br /&gt;
    $attributes[&#039;value&#039;] = &#039;&#039;;&lt;br /&gt;
    $attributes[&#039;title&#039;] = $title;&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;input&#039;, $attributes);&lt;br /&gt;
    $html .= html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
      &lt;br /&gt;
    return $html;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====search($search_text, $page = 0)====&lt;br /&gt;
Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param(&#039;paramname&#039;, [defaultvalue], PARAM_INT / PARAM_TEXT);. The return should return an array containing:&lt;br /&gt;
* list - with the same layout as the &#039;list&#039; element in &#039;get_listing&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function search($search_text, $page = 0) { &lt;br /&gt;
// Example from repoistory_googledocs&lt;br /&gt;
    $gdocs = new google_docs($this-&amp;gt;googleoauth);&lt;br /&gt;
&lt;br /&gt;
    $ret = array();&lt;br /&gt;
    $ret[&#039;dynload&#039;] = true;&lt;br /&gt;
    $ret[&#039;list&#039;] = $gdocs-&amp;gt;get_file_list($search_text);&lt;br /&gt;
    return $ret;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====global_search()====&lt;br /&gt;
Return true if should be included in a search throughout all repositories (currently not available via the UI)&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.3}}===Repository support for returning file as alias/shortcut=== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.3 it became possible to link to the file from external (or internal) repository by reference. In UI it is called “create alias/shortcut”. This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.&lt;br /&gt;
&lt;br /&gt;
Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.&lt;br /&gt;
&lt;br /&gt;
Note that external file is synchronised by moodle when UI wants to show the file size.&lt;br /&gt;
&lt;br /&gt;
====get_reference_file_lifetime()====&lt;br /&gt;
Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_file_lifetime($ref) {&lt;br /&gt;
    return 60 * 60 * 24; // One day&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====sync_individual_file(stored_file $storedfile)====&lt;br /&gt;
Called after the file has reached the &#039;lifetime&#039; specified above to see if it should now be synchronised (default implementation is to return true)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function sync_individual_file(stored_file $storedfile) {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_reference_details($reference, $filestatus = 0)====&lt;br /&gt;
Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (e.g. &#039;Myrepository: http://url.to.file&#039;). $reference is the &#039;source&#039; output by &#039;get_listing&#039;. $filestatus can be either 0 (OK - default) or 666 (source file missing).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_reference_details($reference, $filestatus = 0) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    if (!$filestatus) {&lt;br /&gt;
        $ref = unserialize(base64_decode($reference));&lt;br /&gt;
        return $this-&amp;gt;get_name(). &#039;: &#039;. $ref-&amp;gt;filename;&lt;br /&gt;
    } else {&lt;br /&gt;
        return get_string(&#039;lostsource&#039;, &#039;repository&#039;, &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_file_by_reference($reference)====&lt;br /&gt;
Returns up-to-date information about the original file, only called when the &#039;lifetime&#039; is reached and &#039;sync_individual_file&#039; returns true.&lt;br /&gt;
* for image files - download the file and return either $ret-&amp;gt;filepath (full path on the server), $ret-&amp;gt;handle (open handle to the file) or $ret-&amp;gt;content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated&lt;br /&gt;
* for non-image files - avoid downloading the file (if possible) and just return $ret-&amp;gt;filesize to update that information&lt;br /&gt;
* for missing / inaccessible files - return null&lt;br /&gt;
Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_file_by_reference($reference) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    global $USER;&lt;br /&gt;
    // Extract the remote file identifier&lt;br /&gt;
    $ref = @unserialize(base64_decode($reference-&amp;gt;reference));&lt;br /&gt;
    if (!isset($ref-&amp;gt;url) || !($url = $this-&amp;gt;appendtoken($ref-&amp;gt;url))) {&lt;br /&gt;
        // Occurs when the user isn&#039;t known..&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Download the file details&lt;br /&gt;
    $return = null;&lt;br /&gt;
    $cookiepathname = $this-&amp;gt;prepare_file($USER-&amp;gt;id. &#039;_&#039;. uniqid(&#039;&#039;, true). &#039;.cookie&#039;);&lt;br /&gt;
    $c = new curl(array(&#039;cookie&#039; =&amp;gt; $cookiepathname));&lt;br /&gt;
    if (file_extension_in_typegroup($ref-&amp;gt;filename, &#039;web_image&#039;)) {&lt;br /&gt;
        // The file is an image - download and return the file path&lt;br /&gt;
        $path = $this-&amp;gt;prepare_file(&#039;&#039;);&lt;br /&gt;
        $result = $c-&amp;gt;download_one($url, null, array(&#039;filepath&#039; =&amp;gt; $path, &#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCIMAGE_TIMEOUT));&lt;br /&gt;
        if ($result === true) {&lt;br /&gt;
            $return = (object)array(&#039;filepath&#039; =&amp;gt; $path);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // The file is not an image - just get the file details&lt;br /&gt;
        $result = $c-&amp;gt;head($url, array(&#039;followlocation&#039; =&amp;gt; true, &#039;timeout&#039; =&amp;gt; self::SYNCFILE_TIMEOUT));&lt;br /&gt;
    }&lt;br /&gt;
    // Delete cookie jar.&lt;br /&gt;
    if (file_exists($cookiepathname)) {&lt;br /&gt;
        unlink($cookiepathname);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;connection_result($c-&amp;gt;get_errno());&lt;br /&gt;
    $curlinfo = $c-&amp;gt;get_info();&lt;br /&gt;
    if ($return === null &amp;amp;&amp;amp; isset($curlinfo[&#039;http_code&#039;]) &amp;amp;&amp;amp; $curlinfo[&#039;http_code&#039;] == 200&lt;br /&gt;
            &amp;amp;&amp;amp; array_key_exists(&#039;download_content_length&#039;, $curlinfo)&lt;br /&gt;
            &amp;amp;&amp;amp; $curlinfo[&#039;download_content_length&#039;] &amp;gt;= 0) {&lt;br /&gt;
        // we received a correct header and at least can tell the file size&lt;br /&gt;
        $return = (object)array(&#039;filesize&#039; =&amp;gt; $curlinfo[&#039;download_content_length&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return $return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)====&lt;br /&gt;
Send the requested file back to the user&#039;s browser. The &#039;reference&#039; for the file can be found via $storedfile-&amp;gt;get_reference(). If the file is not found / no longer exists, the function &#039;send_file_not_found()&#039; should be used. Otherwise the file should be output directly, via the most appropriate method - e.g. use a &#039;Location: &#039; header to redirect to the external URL; or download the file and cache within the Moodle filesystem (possibly using &#039;$this-&amp;gt;import_external_file_contents()&#039;), then call &#039;send_stored_file&#039;. Note, it is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {&lt;br /&gt;
// Example taken from repository_equella&lt;br /&gt;
    $reference  = unserialize(base64_decode($stored_file-&amp;gt;get_reference()));&lt;br /&gt;
    $url = $this-&amp;gt;appendtoken($reference-&amp;gt;url);&lt;br /&gt;
    if ($url) {&lt;br /&gt;
        header(&#039;Location: &#039; . $url);&lt;br /&gt;
    } else {&lt;br /&gt;
        send_file_not_found();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
An example of caching files within the Moodle filesystem can be found in repository_dropbox.&lt;br /&gt;
&lt;br /&gt;
===Misc functions===&lt;br /&gt;
&lt;br /&gt;
A couple of other useful functions to be aware of.&lt;br /&gt;
&lt;br /&gt;
====get_name()====&lt;br /&gt;
Returns the human-readable name for this instance of the plugin (the default implementation should usually be fine and this function can be useful when doing any output to the user).&lt;br /&gt;
&lt;br /&gt;
====cron()====&lt;br /&gt;
For any background tasks that need to be scheduled (rarely needed). The minimum time between calls is specified in the version.php file (but the maximum time depends on the server settings for the Moodle install).&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Plugins]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=57106</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=57106"/>
		<updated>2020-03-28T13:25:46Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Chrome specific */&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/] (Forget trying to use Firefox, the latest version is not compatible).  Ensure you have the right version of the Chrome driver - see [[#Trouble_shooting| Trouble shooting]].&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>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=57105</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=57105"/>
		<updated>2020-03-28T13:02:29Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* 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/] (Forget trying to use Firefox, the latest version is not compatible).  Ensure you have the right version of the Chrome driver - see [[#Trouble_shooting| Trouble shooting]].&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>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=57104</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=57104"/>
		<updated>2020-03-28T12:54:56Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* 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/] (Forget trying to use Firefox, the latest version is not compatible)&lt;br /&gt;
# Unzip the driver (it&#039;s a single file) and copy to /usr/local/bin (should work anywhere on the path)&lt;br /&gt;
# If not installed already, &#039;&amp;lt;tt&amp;gt;sudo apt install default-jre&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Start Selenium - &#039;&amp;lt;tt&amp;gt;java -jar /path/to/your/selenium/server/selenium-server-standalone-N.NN.N.jar -port 4444&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# check it works, access &#039;localhost:4444/wd/hub/&#039; in your browser and check you can create a new Chrome session.&lt;br /&gt;
&lt;br /&gt;
If running headless or the above doesn&#039;t work (&amp;quot;Selenium server is not running&amp;quot; when running the behat tests). Try the following&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;Xvfb -ac :99 -screen 0 1280x1024x16 &amp;amp;&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# Then immediately, &#039;&amp;lt;tt&amp;gt;export DISPLAY=:99&amp;lt;/tt&amp;gt;&#039;&lt;br /&gt;
# The run the Selenium command as above&lt;br /&gt;
&lt;br /&gt;
==== 3. Set up Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Create a new &#039;dataroot&#039; area for files especially for behat adjusting permissions accordingly. &lt;br /&gt;
# If not there already, add Section 11 from config-dist.php to your config.php file and review the settings. &lt;br /&gt;
# $CFG-&amp;gt;behat_wwwroot needs to point to your Moodle site yet be different from the &#039;normal&#039; wwwroot (e.g. if you used localhost for wwwroot use 127.0.0.1 for the behat_wwwroot). Whatever you choose, make sure it works. &lt;br /&gt;
# $CFG-&amp;gt;behat_dataroot should point to the directory you created above&lt;br /&gt;
# $CFG-&amp;gt;behat_prefix should be fine. &lt;br /&gt;
# Set up $CFG-&amp;gt;behat_profiles to select Chrome as the browser...&lt;br /&gt;
&lt;br /&gt;
     $CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
         &#039;default&#039; =&amp;gt; [&lt;br /&gt;
             &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
             &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
                 &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                     &#039;selenium2&#039; =&amp;gt; [&lt;br /&gt;
                         &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                     ]&lt;br /&gt;
                 ]&lt;br /&gt;
             ]&lt;br /&gt;
         ]&lt;br /&gt;
     ];&lt;br /&gt;
&lt;br /&gt;
==== 4. Configure Behat for Moodle ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;php admin/tool/behat/cli/init.php&amp;lt;/tt&amp;gt;&#039; (from the root of your Moodle install). This installs all the required software and creates the test version of Moodle. &lt;br /&gt;
&lt;br /&gt;
==== 5. Run Behat tests ====&lt;br /&gt;
&lt;br /&gt;
# Run &#039;&amp;lt;tt&amp;gt;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>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=57103</id>
		<title>Acceptance testing/Browsers/Working combinations of OS+Browser+selenium</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=57103"/>
		<updated>2020-03-28T12:28:48Z</updated>

		<summary type="html">&lt;p&gt;Bencellis: /* Moodle 3.5 and up */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Working combinations of OS+Browser+selenium =&lt;br /&gt;
As OS, Browsers and Selenium keeps updating, some combination might fail on different Moodle releases.&lt;br /&gt;
&lt;br /&gt;
Following combinations have been tested at the time of release of Moodle version and will be supported for that combination.&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.5 and up ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|Notes&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;white-space: nowrap;&amp;quot;|Linux - Debian Stretch/Buster&lt;br /&gt;
|style=&amp;quot;white-space: nowrap;&amp;quot;|Firefox 47.0.1&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
| Requires special behat config ([[Actual Selenium with old Firefox 47.0.1|more info here]])&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Debian Stretch/Buster&lt;br /&gt;
| Chrome 66&lt;br /&gt;
Chrome 76&amp;lt;br/&amp;gt;&lt;br /&gt;
Chrome 80&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.11/selenium-server-standalone-3.11.0.jar 3.11.0]&lt;br /&gt;
[https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&amp;lt;br/&amp;gt;&lt;br /&gt;
[https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.38/ 2.38]&lt;br /&gt;
[https://chromedriver.storage.googleapis.com/index.html?path=76.0.3809.126/ 76.0.3809.126]&amp;lt;br/&amp;gt;&lt;br /&gt;
[https://chromedriver.storage.googleapis.com/index.html?path=80.0.3987.106/ 80.0.3987.106]&lt;br /&gt;
| N/A&lt;br /&gt;
| Any other valid combination should work ok, normally.&amp;lt;br/&amp;gt;(Here there is a [https://github.com/SeleniumHQ/docker-selenium/releases good list] of them, as reference)&lt;br /&gt;
Chrome &amp;gt;=76 can be used since Moodle 20190909 builds. &lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
| Requires special behat config ([[Actual Selenium with old Firefox 47.0.1|more info here]])&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome 72&lt;br /&gt;
Chrome 77&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
[https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.46/ 2.46]&lt;br /&gt;
[https://chromedriver.storage.googleapis.com/index.html?path=77.0.3865.40/ 77.0.3865.40]&lt;br /&gt;
| N/A&lt;br /&gt;
| Any other valid combination should work ok, normally.&amp;lt;br/&amp;gt;(Here there is a [https://github.com/SeleniumHQ/docker-selenium/releases good list] of them, as reference)&lt;br /&gt;
Chrome &amp;gt;=76 can be used since Moodle 20190909 builds. &lt;br /&gt;
|-&lt;br /&gt;
| Windows&lt;br /&gt;
| Chrome 72&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| [https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/ 72.0.3626.69]&lt;br /&gt;
| N/A&lt;br /&gt;
| Any other valid combination should work ok, normally.&amp;lt;br/&amp;gt;(Here there is a [https://github.com/SeleniumHQ/docker-selenium/releases good list] of them, as reference)&lt;br /&gt;
|}&lt;br /&gt;
General note: Many of the combinations below, for Moodle 3.1 and up, should continue working acceptably well for Moodle 3.5 and up. Just the those listed above are actively being used now (CI infrastructure, developers...), hence, verified to be running ok. Feel free to add more relevant working combinations!&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.4==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|Notes&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;white-space: nowrap;&amp;quot;|Linux - Debian Stretch&lt;br /&gt;
|style=&amp;quot;white-space: nowrap;&amp;quot;|Firefox 47.0.1&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
| Requires special behat config ([[Actual Selenium with old Firefox 47.0.1|more info here]])&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Debian Stretch&lt;br /&gt;
| Chrome 66&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.11/selenium-server-standalone-3.11.0.jar 3.11.0]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.38/ 2.38]&lt;br /&gt;
| N/A&lt;br /&gt;
| Any other valid combination should work ok, normally.&amp;lt;br/&amp;gt;(Here there is a [https://github.com/SeleniumHQ/docker-selenium/releases good list] of them, as reference)&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
| Requires special behat config ([[Actual Selenium with old Firefox 47.0.1|more info here]])&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome 72&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.46/ 2.46]&lt;br /&gt;
| N/A&lt;br /&gt;
| Any other valid combination should work ok, normally.&amp;lt;br/&amp;gt;(Here there is a [https://github.com/SeleniumHQ/docker-selenium/releases good list] of them, as reference)&lt;br /&gt;
|-&lt;br /&gt;
| Windows&lt;br /&gt;
| Chrome 72&lt;br /&gt;
| [https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar 3.141.59]&lt;br /&gt;
| [https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/ 72.0.3626.69]&lt;br /&gt;
| N/A&lt;br /&gt;
| Any other valid combination should work ok, normally.&amp;lt;br/&amp;gt;(Here there is a [https://github.com/SeleniumHQ/docker-selenium/releases good list] of them, as reference)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.2 and 3.3 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F403380%2Fchrome-linux.zip?generation=1467337264475000&amp;amp;alt=media Chrome 53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=3.0/ 3.0.1]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.25/ 2.25]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v53.0&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=3.0/ 3.0.1]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.25/ 2.25]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| [http://www.slimjet.com/chrome/google-chrome-old-version.php Chrome v53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=3.0/ 3.0.1]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.25/ 2.25]&lt;br /&gt;
| N/A&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.1 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| [https://download.mozilla.org/?product=firefox-47.0.1-SSL&amp;amp;os=linux64&amp;amp;lang=en-GB Firefox 47.0.1]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 51.0]&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.0 and lower ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Firefox 42.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.19.346067&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Phantomjs 2.0.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Chrome 47.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 48.0&lt;br /&gt;
|2.51.0&lt;br /&gt;
| 2.21&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|PhantomJS - 2.0.0 &amp;amp; 2.1.1&lt;br /&gt;
|2.48.2&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Bencellis</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Old_Events_API&amp;diff=36846</id>
		<title>Old Events API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Old_Events_API&amp;diff=36846"/>
		<updated>2012-12-11T15:22:26Z</updated>

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