<?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=Jan.dagefoerde</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=Jan.dagefoerde"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Jan.dagefoerde"/>
	<updated>2026-08-07T01:34:14Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Output_callbacks&amp;diff=56975</id>
		<title>Output callbacks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Output_callbacks&amp;diff=56975"/>
		<updated>2020-03-04T08:20:55Z</updated>

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

		<summary type="html">&lt;p&gt;Jan.dagefoerde: /* List of Moodle plugin types */ Add Custom fields type&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Plugins development}}&lt;br /&gt;
&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;
&lt;br /&gt;
== List of Moodle plugin types ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&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;
| [[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;
| [[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;
&lt;br /&gt;
== Obtaining the list of plugin types known to your Moodle ==&lt;br /&gt;
&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;
&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Things you can find in all plugins==&lt;br /&gt;
&lt;br /&gt;
Although there are many different types of plugin, there are some things that work the same way in all plugin types, and we have [[Things that work the same in all plugin types|a page that describes them]].&lt;br /&gt;
&lt;br /&gt;
Additionally you probably want to look at the page [[Plugin files]].&lt;br /&gt;
&lt;br /&gt;
== Naming conventions ==&lt;br /&gt;
&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;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&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;
&lt;br /&gt;
[[Category:Coding guidelines|Plugins]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=55061</id>
		<title>Privacy API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Privacy_API&amp;diff=55061"/>
		<updated>2018-11-26T08:25:16Z</updated>

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

		<summary type="html">&lt;p&gt;Jan.dagefoerde: /* supported_returntypes() */&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 empty array.&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;
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 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>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=54618</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=54618"/>
		<updated>2018-08-10T14:31:48Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: supported_returntypes(): Add FILE_CONTROLLED_LINK and clarify different kinds of return types using an example&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 [[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 empty array.&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;
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 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>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File:File_options.png&amp;diff=54617</id>
		<title>File:File options.png</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File:File_options.png&amp;diff=54617"/>
		<updated>2018-08-10T14:17:02Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: Choices offered as a result of the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Choices offered as a result of the values FILE_INTERNAL, FILE_REFERENCE, and FILE_CONTROLLED LINK, respectively. Whether FILE_EXTERNAL is present is never reflected in this list.&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=XMLDB_defining_an_XML_structure&amp;diff=53656</id>
		<title>XMLDB defining an XML structure</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=XMLDB_defining_an_XML_structure&amp;diff=53656"/>
		<updated>2018-01-30T12:32:53Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: add hint that fields are a comma-separated string&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[XMLDB Documentation|XMLDB Documentation]] &amp;gt; [[XMLDB roadmap|Roadmap]] &amp;gt; Defining one XML structure&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Justification ==&lt;br /&gt;
&lt;br /&gt;
Before Moodle 1.7, all the DB install and upgrade was developed twice (once to handle MySQL installations and another to handle PostgreSQL installations). This approach, although working, has caused some headaches in the past, mainly because it was really difficult to keep both lines of development 100% on sync. Some developers do they work against one RDBMS and it was complex to develop to the other one (two test environments, skills on both databases, slower development cycle...). And all this was happening with &#039;&#039;only&#039;&#039; two supported RDBMS!&lt;br /&gt;
&lt;br /&gt;
One of the main objectives of Moodle 1.7 is to extend the the number of supported RDBMS to other flavours (more exactly, to Oracle and MSSQL). And the old approach (one line of development for each DB) could become an absolute nightmare. &lt;br /&gt;
&lt;br /&gt;
Because of this we have planned to build one structure to define all the DB objects used by Moodle. This structure will provide the necessary level of abstraction to be shared by all the RDBMS systems, so the &amp;quot;multiple lines of development&amp;quot; explained in the previous paragraph will be out forever, giving us one robust and well defined way to handle DB objects independently of the underlying RDBMS being used.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
Initially all our best wishes were to use the [http://phplens.com/lens/adodb/docs-datadict.htm#xmlschema AdoDB XML Schema]. As Moodle is using ADOdb libraries to communicate with databases it sounded like the natural approach to solve the problem. But, finally, two reasons prevented us to use it:&lt;br /&gt;
&lt;br /&gt;
# Although working, it seems to be one feature in progress, with important changes/evolutions arriving at the time of write this document.&lt;br /&gt;
# Its lack of support for &amp;quot;prefixes&amp;quot; (one Moodle key feature, to allow multiple instances to run in the same server), would force us to create some awful tricks to generate the objects.&lt;br /&gt;
&lt;br /&gt;
So, finally, we decided to build our own XML files, with everything we need to define every object present in the DB.&lt;br /&gt;
&lt;br /&gt;
== The XMLDB editor ==&lt;br /&gt;
[[XMLDB_editor | Main article]]&lt;br /&gt;
&lt;br /&gt;
Although the XML is pretty simple to read (and to write), one of the major drawbacks was its easy and error-prone adoption by the developers. Also some problems with versioning systems getting crazy with XML files (thanks ML!) pointed us to the requirement to use one high-density format (it means, physically &#039;&#039;&#039;long lines&#039;&#039;&#039;) in our XML files. &lt;br /&gt;
&lt;br /&gt;
After some intense thoughts we decided to build one specialised editor for our XML format. This editor should be easy to use and provide support for all the objects present one Moodle DB. And it&#039;s done (and will support future enhancements easily, we hope).&lt;br /&gt;
&lt;br /&gt;
The XMLDB Editor makes the addition of tables/fields/keys/indexes practically a trivial task, allowing the developer to spend  the time coding and improving things instead of fighting against XML files and the errors caused by manual editing (of course, the developer is free to use such extra-time as desired, beers, dance, books, music...) ;-)&lt;br /&gt;
&lt;br /&gt;
All the new &#039;&#039;&#039;install.xml&#039;&#039;&#039; files, present under each &#039;&#039;&#039;db&#039;&#039;&#039; directory in Moodle can be edited (and we recommend it) with just some clicks and keystrokes. Those &#039;&#039;&#039;install.xml&#039;&#039;&#039; will contain all the info needed to generate the specific objects needed for each RDBMS supported. Obviously, such files, are the neutral replacement for all the *.sql files used until now.&lt;br /&gt;
&lt;br /&gt;
=== Launching ===&lt;br /&gt;
&lt;br /&gt;
Just login to your server as an administrator and, under the Miscellaneous tab of the Administration Block, you&#039;ll see a new link pointing to the &amp;quot;XMLDB Editor&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
One &#039;&#039;&#039;important note&#039;&#039;&#039; is that, to be able to handle files properly, the web server needs write access to all those &amp;quot;db&amp;quot; directories where the &amp;quot;install.xml&amp;quot; files reside (and to the files themselves, of course). ;-)&lt;br /&gt;
&lt;br /&gt;
That&#039;s all!&lt;br /&gt;
&lt;br /&gt;
=== Use===&lt;br /&gt;
&lt;br /&gt;
We really think the XMLDB Editor is pretty easy to use, so here you won&#039;t see a complete guide to use it. We highly recommend you to play with it for a while, viewing how it works and how it modifies the &#039;&#039;&#039;install.xml&#039;&#039;&#039; files.&lt;br /&gt;
&lt;br /&gt;
It&#039;s organised in a top-botton structure, where you start &#039;&#039;&#039;loading&#039;&#039;&#039; (or &#039;&#039;&#039;creating&#039;&#039;&#039;) a new XMLDB file. Then, you can &#039;&#039;&#039;edit&#039;&#039;&#039; such file and its &#039;&#039;&#039;general structure&#039;&#039;&#039; will be showed. This structure have two type of elements, &#039;&#039;&#039;tables&#039;&#039;&#039; and &#039;&#039;&#039;statements&#039;&#039;&#039; and the XMLDB Editor allows you to &#039;&#039;&#039;add&#039;&#039;&#039;, &#039;&#039;&#039;edit&#039;&#039;&#039;, &#039;&#039;&#039;delete&#039;&#039;&#039;, and &#039;&#039;&#039;move&#039;&#039;&#039; them easily. Also, for initial creation of tables, one small but effective &#039;&#039;&#039;reverse-enginery&#039;&#039;&#039; tool has been developed (only under MySQL) allowing you to retrofit any table from the DB to the XMLDB Editor.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note: If you can&#039;t click on the create links....&#039;&#039;&#039; you must first create the /db folder (as shown in the list, but it may not really exist) and then make sure it is writeable by the webserver&lt;br /&gt;
&lt;br /&gt;
While editing tables you will see their &#039;&#039;&#039;fields&#039;&#039;&#039;, &#039;&#039;&#039;keys&#039;&#039;&#039; and &#039;&#039;&#039;indexes&#039;&#039;&#039; and you&#039;ll be able to handle all them easily. Note that some fields can be no-editable. It uses to be because they are being used in some way (part of one key or index) and the idea is to warn you about that.&lt;br /&gt;
&lt;br /&gt;
Fields can be edited and you can specify their &#039;&#039;&#039;name&#039;&#039;&#039;, &#039;&#039;&#039;type&#039;&#039;&#039;, &#039;&#039;&#039;length&#039;&#039;&#039;, &#039;&#039;&#039;decimals&#039;&#039;&#039;, &#039;&#039;&#039;null-ability&#039;&#039;&#039;, &#039;&#039;&#039;defaults&#039;&#039;&#039; and so one. Exactly the same for both &#039;&#039;&#039;keys&#039;&#039;&#039; and &#039;&#039;&#039;indexes&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
While editing statements, you must think about them like &amp;quot;collections of sentences&amp;quot;. Once you select the &#039;&#039;&#039;type&#039;&#039;&#039; (only inserts are allowed for now) and &#039;&#039;&#039;table&#039;&#039;&#039; you are interested you&#039;ll be able to introduce the exact values easily, being able to &#039;&#039;&#039;duplicate&#039;&#039;&#039; them easily to gain some speed if you have a lot of sentences in your development. Sentences can be &#039;&#039;&#039;edited&#039;&#039;&#039; and &#039;&#039;&#039;deleted&#039;&#039;&#039; easily too.&lt;br /&gt;
&lt;br /&gt;
One interesting feature is that all the XMLDB Editor pages allow you to enter one &#039;&#039;&#039;comment&#039;&#039;&#039; about the item being modified (table, index, key, field, statement...). Use it at your entire needs, sure it helps other developers to understand a bit more the DB model.&lt;br /&gt;
&lt;br /&gt;
Please, don&#039;t forget to read and understand the next section, where we talk about &#039;&#039;&#039;some important guidelines&#039;&#039;&#039; to create and handle XMLDB files.&lt;br /&gt;
&lt;br /&gt;
== Conventions ==&lt;br /&gt;
&lt;br /&gt;
Apart of the [[Database| Database Structures guidelines]], some more conventions should be followed:&lt;br /&gt;
&lt;br /&gt;
# About names:&lt;br /&gt;
## All lowercase names (tables, indexes, keys and fields).&lt;br /&gt;
## Table names and field names must use only a-z, 0-9 and _ chars. Table names can be at most 28 characters long; column names at most 30 characters.&lt;br /&gt;
## Key and index names under the XMLDB Files must be formed by concatenating the name of the fields present in the key/index with the &#039;&amp;quot;-&amp;quot; (minus) character.&lt;br /&gt;
## Primary key always must be named &amp;quot;primary&amp;quot; (this is one exception to the previous convention).&lt;br /&gt;
## It&#039;s highly recommended to avoid [[XMLDB_reserved_words|reserved words]] completely. We know we have some of them now but they should be completely out for next releases.&lt;br /&gt;
# About NULLS&lt;br /&gt;
## Avoid to create all the fields as NOT NULL with the &#039;&#039;silly&#039;&#039; default value &amp;lt;nowiki&amp;gt;&#039;&#039;&amp;lt;/nowiki&amp;gt; (empty string). The underlying code used to create tables will handle it properly but the XMLDB structure must be REAL. Read more in the [[XMLDB_Problems#NOT_NULL_fields_using_a_DEFAULT_.27.27_clause|Problems Page]].&lt;br /&gt;
# About FOREIGN KEYS&lt;br /&gt;
## Under the tables of every XMLDB file, you must define the existing &#039;&#039;&#039;Foreign Keys&#039;&#039;&#039; (FK) properly. This will allow everybody to know a bit better the structure, allow to evolve to a better constrained system in the future and will provide the underlying code with the needed info to create the proper indexes. &lt;br /&gt;
## Note that, if you define any field combination as FK you won&#039;t have to create any index on that fields, the code will do it automatically! &lt;br /&gt;
## Respect Convention 1.3&lt;br /&gt;
# About UNIQUE KEYS&lt;br /&gt;
## Declare any fields as UNIQUE KEY (UK) only if they are going to be used as target for one FK. Create unique indexes instead.&lt;br /&gt;
## Respect Convention 1.3&lt;br /&gt;
&lt;br /&gt;
== One example: the assignment module ==&lt;br /&gt;
&lt;br /&gt;
Here we are going to examine the [http://cvs.moodle.org/moodle/mod/assignment/db/install.xml?view=markup current implementation of the XMLDB Schema for the assignment module] (a simple one). It has been completely generated with the XMLDB Editor but it&#039;s nice to know a bit more about the XML internals.&lt;br /&gt;
&lt;br /&gt;
As you can see the structure is pretty simple:&lt;br /&gt;
&lt;br /&gt;
* XMLDB&lt;br /&gt;
** TABLES, one or more, each one with&lt;br /&gt;
*** FIELDS&lt;br /&gt;
*** KEYS&lt;br /&gt;
*** INDEXES&lt;br /&gt;
** STATEMENTS, none or more, each one with&lt;br /&gt;
*** SENTENCES&lt;br /&gt;
&lt;br /&gt;
First of all you should note that all the elements contain the &#039;&#039;&#039;PREVIOUS&#039;&#039;&#039; and &#039;&#039;&#039;NEXT&#039;&#039;&#039; attributes. They allow us to keep everything ordered although it isn&#039;t meaningful at all from the RDBMS perspective. Also the &#039;&#039;&#039;COMMENT&#039;&#039;&#039; field is present everywhere to be used as desired.&lt;br /&gt;
&lt;br /&gt;
=== The TABLE element ===&lt;br /&gt;
&lt;br /&gt;
We can ignore the TABLE element, as it&#039;s simply one container for the internals (FIELDS, KEYS and INDEXES). Let&#039;s go to examine them a bit more:&lt;br /&gt;
&lt;br /&gt;
==== The FIELD element ====&lt;br /&gt;
&lt;br /&gt;
It maps with one field in the DB (obviously). For each field you can define its &#039;&#039;&#039;name&#039;&#039;&#039;, &#039;&#039;&#039;type&#039;&#039;&#039; (from a list of [[XMLDB column types|neutral types]]), &#039;&#039;&#039;length&#039;&#039;&#039;, &#039;&#039;&#039;decimals&#039;&#039;&#039; (for some types), &#039;&#039;&#039;notnull&#039;&#039;&#039; (true/false), &#039;&#039;&#039;unsigned&#039;&#039;&#039; (true/false), &#039;&#039;&#039;sequence&#039;&#039;&#039; (if it&#039;s autonumeric or serial, true/false), &#039;&#039;&#039;enum&#039;&#039;&#039; (true/false), &#039;&#039;&#039;enumvalues&#039;&#039;&#039; (the list of values if the field has been declared as enum, for example &amp;lt;tt&amp;gt;&#039;frog&#039;,&#039;toad&#039;,&#039;newt&#039;&amp;lt;/tt&amp;gt;) and &#039;&#039;&#039;default&#039;&#039;&#039; (to assign a default value).&lt;br /&gt;
&lt;br /&gt;
So, in our example, we have two tables, assignment and assignment_submissions, each one with its own fields, defining all the information related above. Please note that naming conventions are followed.&lt;br /&gt;
&lt;br /&gt;
==== The KEY element ====&lt;br /&gt;
&lt;br /&gt;
Here is where all the PRIMARY KEYS (PK), UNIQUE KEYS (UK) and FOREIGN KEYS (FK) will be defined. For each key we define its &#039;&#039;&#039;name&#039;&#039;&#039;, &#039;&#039;&#039;type&#039;&#039;&#039;, &#039;&#039;&#039;fields&#039;&#039;&#039; (that belongs to it) and optionally (if the key is one FK) the target &#039;&#039;&#039;reftable&#039;&#039;&#039; and &#039;&#039;&#039;reffields&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
In our example, the assignment table has one (mandatory!) PK (called, &amp;quot;primary&amp;quot;, rules are rules) built with the &amp;quot;id&amp;quot; field. &lt;br /&gt;
&lt;br /&gt;
The other table, the &amp;quot;assignment_submissions&amp;quot; one, also has its PK (called &amp;quot;primary&amp;quot; once more) and one FK, with the field &amp;quot;assignment&amp;quot; pointing to the field &amp;quot;id&amp;quot; of the table &amp;quot;assignment&amp;quot;. Note that the FK follows the name conventions and its name is, simply, the name of the fields being part of it (&amp;quot;assignment&amp;quot;). Also, the FK has as target to one PK of the same module.&lt;br /&gt;
&lt;br /&gt;
Finally, note that there isn&#039;t any index created for all these keys. Moodle will generate them automatically when the table is created. All the keys will have their corresponding index. Point. ;-)&lt;br /&gt;
&lt;br /&gt;
==== The INDEX element ====&lt;br /&gt;
&lt;br /&gt;
Where all the indexes will be defined. For each index you can define its &#039;&#039;&#039;name&#039;&#039;&#039;, &#039;&#039;&#039;unique&#039;&#039;&#039; (true/false) and the &#039;&#039;&#039;fields&#039;&#039;&#039; (as a comma-separated string) that it comprises. Please note that naming conventions are followed.&lt;br /&gt;
&lt;br /&gt;
Also, some &amp;quot;obvious index&amp;quot;, like the one based in the &amp;quot;assignment&amp;quot; field of the &amp;quot;assignment_submissions&amp;quot; table doesn&#039;t exist. Yes, you know why: Because such column has been defined as a FK and the index will be automatically created (see previous section).&lt;br /&gt;
&lt;br /&gt;
=== The STATEMENT element ===&lt;br /&gt;
&lt;br /&gt;
This is the other &#039;&#039;&#039;big container&#039;&#039;&#039; in the XMLDB Schema (at the same level as the &#039;&#039;&#039;TABLES&#039;&#039;&#039; one) and we can define its &#039;&#039;&#039;name&#039;&#039;&#039;, &#039;&#039;&#039;type&#039;&#039;&#039; (only insert allowed for now) and &#039;&#039;&#039;table&#039;&#039;&#039; (against the sentences will be executed).&lt;br /&gt;
&lt;br /&gt;
Every statement is a collection of &#039;&#039;&#039;sentences&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
==== The SENTENCE element ====&lt;br /&gt;
&lt;br /&gt;
Each sentence implies one simple action to be performed against the DB and it can be defined as the &amp;quot;missing part of the SQL statement&amp;quot;. In our example, we have one statement, of type &amp;quot;insert&amp;quot; on table &amp;quot;log_display&amp;quot;. With this Moodle knows the initial part of the sentence, i.e:&lt;br /&gt;
&lt;br /&gt;
 INSERT INTO log_display &lt;br /&gt;
&lt;br /&gt;
and then the text will be added to create this:&lt;br /&gt;
&lt;br /&gt;
 INSERT INTO log_display &lt;br /&gt;
   (module, action, mtable, field) &lt;br /&gt;
 VALUES &lt;br /&gt;
   (&#039;assignment&#039;, &#039;view&#039;, &#039;assignment&#039;, &#039;name&#039;)&lt;br /&gt;
&lt;br /&gt;
There is one important trick when handling sentences, although they aren&#039;t in the assignment example. Take a look to the [http://cvs.moodle.org/moodle/lib/db/install.xml?view=co Core Tables XML Schema] (it&#039;s a huge one!). If you go near the end, to the statements section, you will see some sentences like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;SENTENCE TEXT=&amp;quot;....VALUES (&#039;user&#039;, &#039;view&#039;, &#039;user&#039;, &#039;CONCAT(firstname,&amp;quot; &amp;quot;,lastname)&#039;)&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Such &amp;quot;CONCAT&amp;quot; function isn&#039;t standard at all (only MySQL supports it), but don&#039;t worry, we&#039;ll transform it to the correct concatenation operators for other RDBMS. Just be sure to use the syntax showed above.&lt;br /&gt;
&lt;br /&gt;
== DTD and XML schema ==&lt;br /&gt;
&lt;br /&gt;
Not sure if this will be usable for somebody but here you can find one [http://cvs.moodle.org/moodle/lib/xmldb/xmldb.dtd?view=co automatically generated DTD] for the XMLDB files. Also one [http://cvs.moodle.org/moodle/lib/xmldb/xmldb.xsd?view=co automatically generated XML Schema] is available. Any improvement/fix to them will be welcome!&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[XMLB List of files to create|List of files to create]]: The list of files to be created from scratch. Used to follow the progress.&lt;br /&gt;
* http://www.hitsw.com/xml_utilites/: One online XML-DTD-Schema converter.&lt;br /&gt;
&lt;br /&gt;
[[Category:XMLDB]]&lt;br /&gt;
[[Category:DB]]&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Lock_API&amp;diff=52834</id>
		<title>Lock API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Lock_API&amp;diff=52834"/>
		<updated>2017-08-17T14:33:10Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: /* Usage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.7}}&lt;br /&gt;
&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = Lock&lt;br /&gt;
|state = Done&lt;br /&gt;
|tracker = MDL-25500&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=229139&lt;br /&gt;
|assignee = Damyon&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= Locks =&lt;br /&gt;
&lt;br /&gt;
Locking is required whenever you need to prevent 2 processing accessing the same resource at the same time. The prime candidate for locking in Moodle is cron. This will allow multiple cron processes to work on different parts of cron at the same time with no risk that they will conflict (work on the same job at the same time).&lt;br /&gt;
&lt;br /&gt;
This is a new feature for Moodle 2.7 and will not be available in older versions.&lt;br /&gt;
&lt;br /&gt;
= When to use locking =&lt;br /&gt;
&lt;br /&gt;
When you want to prevent multiple requests from accessing the same resource at the same time. Accessing a resource is a vague description, but it could be e.g. running a slow running task in the background, running different parts of cron etc.&lt;br /&gt;
&lt;br /&gt;
= Performance =&lt;br /&gt;
Locking is not meant to be fast. Do not use it in code that will be triggered many times in a single request (e.g. MUC). It is meant to be always correct - even for multiple nodes in a cluster. This implies that the locks are communicated among all the nodes in the cluster, and hence it will never be super quick.&lt;br /&gt;
&lt;br /&gt;
= Usage =&lt;br /&gt;
&lt;br /&gt;
The locking API is used by getting an instance of a lock_factory, and then using it to retrieve locks, and eventually releasing them. You are required to release all your locks, even on the event of failures.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// 5 seconds.&lt;br /&gt;
$timeout = 5;&lt;br /&gt;
// A namespace for the locks. Must be prefixed with the component name to prevent conflicts.&lt;br /&gt;
$locktype = &#039;mod_assign_download_submissions&#039;; &lt;br /&gt;
// Resource key - needs to uniquely identify the resource that is to be locked. E.g. If you&lt;br /&gt;
// want to prevent a user from running multiple course backups - include the userid in the key.&lt;br /&gt;
$resource = &#039;user:&#039; . $USER-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
// Get an instance of the currently configured lock_factory.&lt;br /&gt;
$lockfactory = \core\lock\lock_config::get_lock_factory($locktype);&lt;br /&gt;
&lt;br /&gt;
// Get a new lock for the resource, wait for it if needed.&lt;br /&gt;
if ($lock = $lockfactory-&amp;gt;get_lock($resource, $timeout)) {&lt;br /&gt;
    // We have exclusive access to the resource, do the slow zip file generation...&lt;br /&gt;
&lt;br /&gt;
    if ($someerror) {&lt;br /&gt;
        // Always release locks on failure.&lt;br /&gt;
        $lock-&amp;gt;release();&lt;br /&gt;
        print_error(&#039;blah&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Release the lock once finished.&lt;br /&gt;
    $lock-&amp;gt;release();&lt;br /&gt;
&lt;br /&gt;
} else {&lt;br /&gt;
    // We did not get access to the resource in time, give up.&lt;br /&gt;
    throw new moodle_exception(&#039;locktimeout&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Use a different lock type from the default =&lt;br /&gt;
Change the $CFG-&amp;gt;lock_factory setting to one of the other lock types included with core. These are all documented in config-dist.php.&lt;br /&gt;
&lt;br /&gt;
= Implementing new lock types =&lt;br /&gt;
If you really want to do this you can. I probably wouldn&#039;t recommend it - because the core lock types should be very reliable - and the performance is not really a concern.&lt;br /&gt;
&lt;br /&gt;
Add a new local_XXX plugin with an autoloaded class that implements \core\lock\lock_factory.&lt;br /&gt;
Set $config-&amp;gt;lock_factory to the full namespaced path to your class e.g. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$config-&amp;gt;lock_factory = &#039;\\local_redis\\lock\\redis_lock_factory&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note: the extra backslashes are just required because it&#039;s a string and php insists on moronic syntax for namespaces.&lt;br /&gt;
&lt;br /&gt;
Note: lib/tests/lock_test.php has unit tests that can be run on a custom lock instance to verify it for correctness (run_on_lock_factory).&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Composer&amp;diff=52662</id>
		<title>Composer</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Composer&amp;diff=52662"/>
		<updated>2017-07-06T14:03:41Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: Use stable Moodle repository in order to avoid that people download Moodle from the integration server&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Use composer to download moodle code =&lt;br /&gt;
Composer.json now includes meta information and hence composer can be used to download the moodle code base.&lt;br /&gt;
You can do it by creating composer.json file with following information&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;repositories&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;type&amp;quot;: &amp;quot;vcs&amp;quot;,&lt;br /&gt;
            &amp;quot;url&amp;quot;: &amp;quot;https://github.com/moodle/moodle.git&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    ],&lt;br /&gt;
    &amp;quot;require&amp;quot;: {&lt;br /&gt;
        &amp;quot;moodle/moodle&amp;quot;: &amp;quot;v3.2.0&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And then execute&lt;br /&gt;
&amp;lt;code&amp;gt;php composer.phar install&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_a_theme_settings_page&amp;diff=49656</id>
		<title>Creating a theme settings page</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_a_theme_settings_page&amp;diff=49656"/>
		<updated>2016-03-10T13:46:05Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: More accurate description how a $settings object is created.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Themes}}This document looks at how to create a settings page for your Moodle 2.x.x theme and how to make use of those settings within the CSS and layout files for your theme.&lt;br /&gt;
&lt;br /&gt;
This is a pretty advanced topic and will require that you have at least an intermediate knowledge of PHP, CSS, and development in general.&lt;br /&gt;
&lt;br /&gt;
==Before we begin==&lt;br /&gt;
[[Image:Theme.settings.page.03.png|350px|thumb|Our end goal. The settings page.]]&lt;br /&gt;
[[Image:Theme.settings.page.10.png|350px|thumb|And what it can do.]]&lt;br /&gt;
There is a huge body of knowledge that we must cover in following through this document and as such I think the best way to write this is as a tutorial.&lt;br /&gt;
&lt;br /&gt;
My intentions for this tutorial are to replicate the standard theme but with a settings page that allows the administrator to set a background colour, set a logo to use with the page, and probably several other minor settings to change the way in which the theme is displayed.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I will start this tutorial by creating a new theme which will be largely a copy/paste of the current standard theme. I expect that anyone working through this tutorial has previously read the tutorial I wrote on [[Themes 2.0 creating your first theme|creating your first theme]]. If you haven&#039;t go read it now because I&#039;m not going to go into much detail until we get to the actual process of customising the theme and introducing the settings page.&lt;br /&gt;
&lt;br /&gt;
So before we finally get this started please ensure you can check off everything on the following requirements list.&lt;br /&gt;
* Have a Moodle installation that has already been installed and configured and is ready to use.&lt;br /&gt;
* Have full read/write access to that installation.&lt;br /&gt;
* Be prepared to delete that installation at the end of this... we will destroy it!&lt;br /&gt;
* Have a development environment prepared and ready to use. This includes:&lt;br /&gt;
** Your favourite editor installed, running, and pointed at the themes directory of your installation.&lt;br /&gt;
** Your browser open and your site visible.&lt;br /&gt;
** A bottomless coffee pot... decaf won&#039;t help you with this one.&lt;br /&gt;
* Have set the following settings:&lt;br /&gt;
** &#039;&#039;&#039;themedesignermode&#039;&#039;&#039; if you don&#039;t know what this is please read the [[Themes 2.0 creating your first theme|creating your first theme]] tutorial.&lt;br /&gt;
** &#039;&#039;&#039;allowthemechangeonurl&#039;&#039;&#039; turn this on, it allows you to change themes on the URL and is very handy when developing themes. &#039;&#039;Site Administration &amp;gt; Appearance &amp;gt; Themes &amp;gt; Theme settings&#039;&#039;&lt;br /&gt;
** &#039;&#039;&#039;langstringcache&#039;&#039;&#039; if you don&#039;t turn this off you won&#039;t see your strings when they are added. &#039;&#039;Site Administration &amp;gt; Language &amp;gt; Language settings&#039;&#039;&lt;br /&gt;
* And finally an insane ambition to create a customisable theme.&lt;br /&gt;
&lt;br /&gt;
For those interested the theme that I create throughout this tutorial can be downloaded from the forum post in which I announce this document: http://moodle.org/mod/forum/discuss.php?d=152053&lt;br /&gt;
&amp;lt;br style=&amp;quot;clear:right;&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Our goals for this tutorial==&lt;br /&gt;
The following is just a list goals that I hope to achieve during this tutorial. They are laid out here so that I can easily refer back to them and so that you can easily find them.&lt;br /&gt;
# Create a new theme called &#039;&#039;&#039;demystified&#039;&#039;&#039; based upon the standard theme within Moodle 2.0.&lt;br /&gt;
# Make some minor changes to that theme to allow us to more easily see what is going on.&lt;br /&gt;
# Create a settings page for the demystified theme.&lt;br /&gt;
# Add several settings to our settings page.&lt;br /&gt;
# Use some of those settings to alter our CSS.&lt;br /&gt;
# Use the rest of those settings within our layout file..&lt;br /&gt;
# Discuss the good, the bad, and limits of what we have just created.&lt;br /&gt;
&lt;br /&gt;
So I can see you are all very excited about this point and that you would love to know what settings we are going to create; So here they are:&lt;br /&gt;
&lt;br /&gt;
A setting to ...&lt;br /&gt;
* change the background colour (CSS).&lt;br /&gt;
* set the path to an image that we will use as a logo on all pages (Layout files).&lt;br /&gt;
* override the width of the block regions (CSS).&lt;br /&gt;
* allow a note to be added to the footer of all pages (Layout files).&lt;br /&gt;
* allow custom CSS to be written to do anything the user wants. (CSS)&lt;br /&gt;
&lt;br /&gt;
==Creating the demystified theme==&lt;br /&gt;
Before we start here I want to remind you that I am going to look at this only briefly as I am making the assumption that you have read the [[Themes 2.0 creating your first theme|creating your first theme]] tutorial.&lt;br /&gt;
&lt;br /&gt;
Well lets get into it....&lt;br /&gt;
&lt;br /&gt;
The first thing we need to do is create a directory for our theme which we will call demystified. &lt;br /&gt;
&lt;br /&gt;
So within your Moodle directory create the following folder &#039;&#039;&#039;moodle/theme/demystified&#039;&#039;&#039;. At the same time you can also create the following files and folders which we will get to soon.&lt;br /&gt;
* The file &#039;&#039;&#039;moodle/theme/demystified/config.php&#039;&#039;&#039; for our config information.&lt;br /&gt;
* The directory &#039;&#039;&#039;moodle/theme/demystified/layout&#039;&#039;&#039; for our layout files.&lt;br /&gt;
* The directory &#039;&#039;&#039;moodle/theme/demystified/style&#039;&#039;&#039; for our css files.&lt;br /&gt;
* The file &#039;&#039;&#039;moodle/theme/demystified/style/core.css&#039;&#039;&#039; which will contain our special CSS.&lt;br /&gt;
&lt;br /&gt;
Next we will copy the layout files from the base theme to our new theme demystified. We are basing the demystified theme on the standard theme however that doesn&#039;t use it&#039;s own layout files it uses the base theme&#039;s layout files so those are the ones we want. &lt;br /&gt;
&lt;br /&gt;
The reason that we are coping these layout files is that later on in this tutorial we will be modifying them to make use of our new settings... so copy all of the layout files from &#039;&#039;&#039;moodle/theme/base/layout&#039;&#039;&#039; to &#039;&#039;&#039;moodle/theme/demystified/layout&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There should be three files that you just copied:&lt;br /&gt;
# embedded.php&lt;br /&gt;
# frontpage.php&lt;br /&gt;
# general.php&lt;br /&gt;
&lt;br /&gt;
Now we need to populate demystified/config.php with the settings for our new theme. They are as follows:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$THEME-&amp;gt;name = &#039;demystified&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Simply sets the name of our theme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$THEME-&amp;gt;parents = array(&#039;standard&#039;,&#039;base&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This theme is extending both the standard theme and the base theme. Remember when extending a theme you also need to extend its parents or things might not work correctly.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$THEME-&amp;gt;sheets = array(&#039;core&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This tells our theme that we want to use the file &#039;&#039;&#039;demystified/style/core.css&#039;&#039;&#039; with this theme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;height:300px;overflow-y:scroll;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$THEME-&amp;gt;layouts = array(&lt;br /&gt;
    // Most backwards compatible layout without the blocks - this is the layout used by default&lt;br /&gt;
    &#039;base&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
    ),&lt;br /&gt;
    // Standard layout with blocks, this is recommended for most pages with general information&lt;br /&gt;
    &#039;standard&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // Main course page&lt;br /&gt;
    &#039;course&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;langmenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;coursecategory&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // part of course, typical for modules - default page layout if $cm specified in require_login()&lt;br /&gt;
    &#039;incourse&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // The site home page.&lt;br /&gt;
    &#039;frontpage&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;frontpage.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // Server administration scripts.&lt;br /&gt;
    &#039;admin&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-pre&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // My dashboard page&lt;br /&gt;
    &#039;mydashboard&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;langmenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // My public page&lt;br /&gt;
    &#039;mypublic&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;login&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;langmenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    // Pages that appear in pop-up windows - no navigation, no blocks, no header.&lt;br /&gt;
    &#039;popup&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;nofooter&#039;=&amp;gt;true, &#039;nonavbar&#039;=&amp;gt;true, &#039;nocustommenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // No blocks and minimal footer - used for legacy frame layouts only!&lt;br /&gt;
    &#039;frametop&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;nofooter&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible&lt;br /&gt;
    &#039;embedded&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;embedded.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;nofooter&#039;=&amp;gt;true, &#039;nonavbar&#039;=&amp;gt;true, &#039;nocustommenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // Used during upgrade and install, and for the &#039;This site is undergoing maintenance&#039; message.&lt;br /&gt;
    // This must not have any blocks, and it is good idea if it does not have links to&lt;br /&gt;
    // other places - for example there should not be a home link in the footer...&lt;br /&gt;
    &#039;maintenance&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;noblocks&#039;=&amp;gt;true, &#039;nofooter&#039;=&amp;gt;true, &#039;nonavbar&#039;=&amp;gt;true, &#039;nocustommenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
Now that all looks very complicated however its really not too bad as it is just copied from the base theme&#039;s config.php file. We can do this because we copied the layout files from the base theme to begin with and for the time being there are no changes that we wish to make. Simply open up &#039;&#039;&#039;theme/base/config.php&#039;&#039;&#039; and copy the layouts from there.&lt;br /&gt;
&lt;br /&gt;
And that is it. The config.php file for our demystified theme is complete. The full source is shown below:&lt;br /&gt;
&amp;lt;div style=&amp;quot;height:300px;overflow-y:scroll;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
// This file is part of Moodle - http://moodle.org/&lt;br /&gt;
//&lt;br /&gt;
// Moodle is free software: you can redistribute it and/or modify&lt;br /&gt;
// it under the terms of the GNU General Public License as published by&lt;br /&gt;
// the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
// (at your option) any later version.&lt;br /&gt;
//&lt;br /&gt;
// Moodle is distributed in the hope that it will be useful,&lt;br /&gt;
// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
// GNU General Public License for more details.&lt;br /&gt;
//&lt;br /&gt;
// You should have received a copy of the GNU General Public License&lt;br /&gt;
// along with Moodle.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * The demystified theme config file&lt;br /&gt;
 *&lt;br /&gt;
 * This theme was created to document the process of adding a settings page to a theme&lt;br /&gt;
 *&lt;br /&gt;
 * @copyright 2010 Sam Hemelryk&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;
// The name of our theme&lt;br /&gt;
$THEME-&amp;gt;name = &#039;demystified&#039;;&lt;br /&gt;
&lt;br /&gt;
// The other themes this theme extends&lt;br /&gt;
$THEME-&amp;gt;parents = array(&#039;standard&#039;,&#039;base&#039;);&lt;br /&gt;
&lt;br /&gt;
// The CSS files this theme uses (located in the style directory)&lt;br /&gt;
$THEME-&amp;gt;sheets = array(&#039;core&#039;);&lt;br /&gt;
&lt;br /&gt;
// The layout definitions for this theme&lt;br /&gt;
$THEME-&amp;gt;layouts = array(&lt;br /&gt;
    // Most backwards compatible layout without the blocks - this is the layout used by default&lt;br /&gt;
    &#039;base&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
    ),&lt;br /&gt;
    // Standard layout with blocks, this is recommended for most pages with general information&lt;br /&gt;
    &#039;standard&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // Main course page&lt;br /&gt;
    &#039;course&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;langmenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;coursecategory&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // part of course, typical for modules - default page layout if $cm specified in require_login()&lt;br /&gt;
    &#039;incourse&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // The site home page.&lt;br /&gt;
    &#039;frontpage&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;frontpage.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // Server administration scripts.&lt;br /&gt;
    &#039;admin&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-pre&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    // My dashboard page&lt;br /&gt;
    &#039;mydashboard&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;langmenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // My public page&lt;br /&gt;
    &#039;mypublic&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(&#039;side-pre&#039;, &#039;side-post&#039;),&lt;br /&gt;
        &#039;defaultregion&#039; =&amp;gt; &#039;side-post&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;login&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;langmenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    // Pages that appear in pop-up windows - no navigation, no blocks, no header.&lt;br /&gt;
    &#039;popup&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;nofooter&#039;=&amp;gt;true, &#039;nonavbar&#039;=&amp;gt;true, &#039;nocustommenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // No blocks and minimal footer - used for legacy frame layouts only!&lt;br /&gt;
    &#039;frametop&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;nofooter&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible&lt;br /&gt;
    &#039;embedded&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;embedded.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;nofooter&#039;=&amp;gt;true, &#039;nonavbar&#039;=&amp;gt;true, &#039;nocustommenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
    // Used during upgrade and install, and for the &#039;This site is undergoing maintenance&#039; message.&lt;br /&gt;
    // This must not have any blocks, and it is good idea if it does not have links to&lt;br /&gt;
    // other places - for example there should not be a home link in the footer...&lt;br /&gt;
    &#039;maintenance&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;file&#039; =&amp;gt; &#039;general.php&#039;,&lt;br /&gt;
        &#039;regions&#039; =&amp;gt; array(),&lt;br /&gt;
        &#039;options&#039; =&amp;gt; array(&#039;noblocks&#039;=&amp;gt;true, &#039;nofooter&#039;=&amp;gt;true, &#039;nonavbar&#039;=&amp;gt;true, &#039;nocustommenu&#039;=&amp;gt;true),&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The screenshot below shows both the directory structure we have now created and the theme presently.&lt;br /&gt;
&lt;br /&gt;
[[Image:Theme.settings.page.01.png]]&lt;br /&gt;
&lt;br /&gt;
To view the theme so far open you browser and enter the URL of your site followed by &#039;&#039;&#039;?theme=demystified&#039;&#039;&#039;. You should see the theme that we just created which will look exactly like the base standard theme.&lt;br /&gt;
&lt;br /&gt;
The final thing that we want to do is add a little bit of CSS to the demystified theme that will both visually set this theme apart from the standard theme and second build a the base which our settings can later extend.&lt;br /&gt;
&lt;br /&gt;
I added the following snippet of CSS to the file &#039;&#039;&#039;demystified/style/core.css&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;code css&amp;gt;&lt;br /&gt;
html {background-color:#DDD;}&lt;br /&gt;
body {margin:30px;padding:0;border:1px solid #333;border-width:0 10px 0 10px;background-color:#333;}&lt;br /&gt;
body #page {background-color:#FFF;position:relative;top:-10px;}&lt;br /&gt;
.block .header {background-image:none;background-color:#0C5CAC;border:1px solid #0C5CAC;color:#FFF;}&lt;br /&gt;
.block {border-color:#4BA7FF;background-color:#DDEEFF;}&lt;br /&gt;
.block .content {background-color:#F1F8FF;}&lt;br /&gt;
a:link,&lt;br /&gt;
a:visited {color:#0C5CAC;}&lt;br /&gt;
a:hover {color:#C77500;}&lt;br /&gt;
#page #page-header {background-color:#0C5CAC;margin:0;padding:0;width:100%;color:#fff;}&lt;br /&gt;
#page #page-header a:link, #page #page-header a:visited {color:#FFAC02}&lt;br /&gt;
#page #page-header .navbar, #page #page-header .navbar a:link, #page #page-header .navbar a:visited {color:#0C5CAC;}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The CSS that we have just added to our theme sets a couple of colours on the front page. Presently this is the only CSS I will add, I know it isn&#039;t complete by any means but it achieves it&#039;s purpose as the screenshot below illustrates.&lt;br /&gt;
&lt;br /&gt;
[[Image:Theme.settings.page.02.png|715px|thumb|left|The newly styles demystified theme]]&amp;lt;br style=&amp;quot;clear:both;&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And with that I will move on to the real purpose of this tutorial, creating the settings page&lt;br /&gt;
&lt;br /&gt;
==Setting up the settings page==&lt;br /&gt;
With the demystified theme set up it is time to create the settings page. This is where the real PHP fun begins.&lt;br /&gt;
&lt;br /&gt;
For those of you who happen to be familiar with development of modules, blocks or other plugin types you have probably encountered settings pages before and this is not going to be any different.&lt;br /&gt;
&lt;br /&gt;
However for those who haven&#039;t which I imagine is most of you this is going to be quite a challenge. I will try to walk through this step by step however if at any point you get stuck don&#039;t hesitate to ask in the forums as I imagine you will get a speedy response.&lt;br /&gt;
&lt;br /&gt;
===How settings pages work in Moodle===&lt;br /&gt;
Settings pages can be used by nearly every plugin type, of which themes is of course one. The way in which it all works isn&#039;t too tricky to understand. &lt;br /&gt;
&lt;br /&gt;
All of the settings for Moodle can be configured through the administrator interfaces when logged in. I am sure that everyone here has seen those pages and has changed a setting or two before so you will all know what I am talking about. Well the settings page for a theme is no different. It will be shown in the administration pages tree under &#039;&#039;&#039;Appearance &amp;gt; Themes&#039;&#039;&#039; and all we have to do is tell Moodle what settings there are.&lt;br /&gt;
&lt;br /&gt;
This is done by creating a settings.php file within our theme into which we will add code that tells Moodle about the settings we want to add/use.&lt;br /&gt;
&lt;br /&gt;
When telling Moodle about each setting we are simply creating a new &#039;&#039;admin_setting&#039;&#039; instance of the type we want and the properties we want and then adding it to our settings page.&lt;br /&gt;
&lt;br /&gt;
There is really not much more too it at this level. Things can get very complex very fast so the best thing we can do now is start creating our settings.php file for the demystified theme and see where it leads us.&lt;br /&gt;
&lt;br /&gt;
===Creating the settings page===&lt;br /&gt;
So as mentioned before we need a settings.php file which we will create now. To begin with create the file &#039;&#039;&#039;theme/demystified/settings.php&#039;&#039;&#039; and open it in your favourite editor so its ready to go.&lt;br /&gt;
&lt;br /&gt;
Before we start adding code however lets just remember the settings that we want to create:&lt;br /&gt;
* change the background colour (CSS).&lt;br /&gt;
* set the path to an image that we will use as a logo on all pages (Layout files).&lt;br /&gt;
* override the width of the block regions (CSS).&lt;br /&gt;
* allow a note to be added to the footer of all pages (Layout files).&lt;br /&gt;
* allow custom CSS to be written to do anything the user wants. (CSS)&lt;br /&gt;
&lt;br /&gt;
Alright.&lt;br /&gt;
&lt;br /&gt;
Now thinking about this the first setting is as basic as it gets, all we need is a text box that the user can type a colour into.&lt;br /&gt;
&lt;br /&gt;
The second is to allow a logo to be used in the header of each page. What we want here is a path but should it be a physical path e.g. C:/path/to/image.png or should it be a web path e.g. &amp;lt;nowiki&amp;gt;http://mysite.com/path/to/image.png&amp;lt;/nowiki&amp;gt;?&lt;br /&gt;
For the purpose of this tutorial I am going to go with a web path because it is going to be easier to code and will hopefully be a little easier to understand to begin with.&lt;br /&gt;
&lt;br /&gt;
The third setting is a little more complex. For this I want a drop down box with some specific widths that the administrator can select.&lt;br /&gt;
&lt;br /&gt;
The forth and the fifth settings are both pretty straight forward, there we want a textarea into which the user can enter what ever they want and we will do something useful with it.&lt;br /&gt;
&lt;br /&gt;
Now that we have an understanding about the settings we wish to define pull up your editor and lets start coding....&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Settings for the demystified theme&lt;br /&gt;
 */&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die;&lt;br /&gt;
&lt;br /&gt;
if ($ADMIN-&amp;gt;fulltree) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is the first bit of code we must enter, the first line is of course just the opening php tag, secondly we have a comment that describes this file. The surrounding part of Moodle has already created an &#039;&#039;&#039;admin_settingspage&#039;&#039;&#039; object which can be referenced as &#039;&#039;&#039;$settings&#039;&#039;&#039;. Therefore, it does not need to be created by us. It already represents an entry in the Moodle administration entry. We proceed with testing for &#039;&#039;&#039;$ADMIN-&amp;gt;fulltree&#039;&#039;&#039; in order to find out whether actual settings should be initialised.&lt;br /&gt;
&lt;br /&gt;
For more background information: Look at the class &#039;&#039;&#039;\admin_root&#039;&#039;&#039; in lib/adminlib.php to find out about &#039;&#039;&#039;$ADMIN-&amp;gt;fulltree&#039;&#039;&#039; and have a look at admin/settings/appearance.php to find out how &#039;&#039;&#039;&amp;quot;$themedir/settings.php&amp;quot;&#039;&#039;&#039; is included.&lt;br /&gt;
&lt;br /&gt;
====Background colour====&lt;br /&gt;
&lt;br /&gt;
With the page now created lets add our first setting: Background colour.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Background colour setting&lt;br /&gt;
$name = &#039;theme_demystified/backgroundcolor&#039;;&lt;br /&gt;
$title = get_string(&#039;backgroundcolor&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
$description = get_string(&#039;backgroundcolordesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
$default = &#039;#DDD&#039;;&lt;br /&gt;
$setting = new admin_setting_configtext($name, $title, $description, $default, PARAM_CLEAN, 12);&lt;br /&gt;
$setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
$settings-&amp;gt;add($setting);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Thankfully this isn&#039;t as difficult as it initially looks.&lt;br /&gt;
&lt;br /&gt;
The first line of code is creating a variable for the name of the background colour setting. In this case it is &#039;&#039;&#039;theme_demystified/backgroundcolor&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
The name is very important, for the setting to be usable we have to follow a strict naming convention. &#039;&#039;&#039;theme_&#039;&#039;themename&#039;&#039;/&#039;&#039;settingname&#039;&#039;&#039;&#039;&#039; where &#039;&#039;&#039;&#039;&#039;themename&#039;&#039;&#039;&#039;&#039; is the name of the theme the setting belongs to and &#039;&#039;&#039;&#039;&#039;settingname&#039;&#039;&#039;&#039;&#039; is the name for the setting by which we will use it.&lt;br /&gt;
&lt;br /&gt;
The second line of code creates a variable that contains the title of the setting. This is what the user sees to the right of the setting on the settings page and should be a short description of the setting. Here we are again using the &#039;&#039;get_string&#039;&#039; method so we will need to remember to add that string later on.&lt;br /&gt;
&lt;br /&gt;
The third line of code sets the description. This should describe what the setting does or how it works and again we will use the get_string method.&lt;br /&gt;
&lt;br /&gt;
The fourth line creates a variable that will be used as the default value for the setting. Because this setting is a colour we want an HTML colour to be the default value.&lt;br /&gt;
&lt;br /&gt;
The fifth line is where we put it all together. Here we create a new &#039;&#039;&#039;admin_setting_configtext&#039;&#039;&#039; object. This object will represent the background colour setting.&lt;br /&gt;
&lt;br /&gt;
When we create it we need to give it 6 different things.&lt;br /&gt;
# The name of the setting. In this case we have a variable &#039;&#039;&#039;$name&#039;&#039;&#039;.&lt;br /&gt;
# The title for this setting. We used the variable &#039;&#039;&#039;$title&#039;&#039;&#039;.&lt;br /&gt;
# The description of the setting &#039;&#039;&#039;$description&#039;&#039;&#039;.&lt;br /&gt;
# The default value for the setting. &#039;&#039;&#039;$default&#039;&#039;&#039; is the variable this.&lt;br /&gt;
# The type of value we want the user to enter. For this we have used PARAM_CLEAN which tells Moodle to get rid of any nasties from what the user enters.&lt;br /&gt;
# The size of the field. In our case 12 characters will be plenty.&lt;br /&gt;
&lt;br /&gt;
The sixth and final line of code adds our newly created setting to the administration page we created earlier.&lt;br /&gt;
&lt;br /&gt;
That is it we have successfully created and added our first setting, however there are several more to settings to do, and there are a couple of important things that you need to be aware of before we move on.&lt;br /&gt;
&lt;br /&gt;
First: There are several different types of settings that you can create and add to a page, and each one may differ in what they need you to give them. In this case it was name, title, description, default, type, and size. However other settings will likely require different things. Smart editors like Netbeans or Eclipse can tell you what is required, otherwise you will need to research it.&lt;br /&gt;
&lt;br /&gt;
Second: Normally settings are declared on one line as follows:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$setting-&amp;gt;add(new admin_setting_configtext(&#039;theme_demystified/backgroundcolor&#039;, get_string(&#039;backgroundcolor&#039;,&#039;theme_demystified&#039;), get_string(&#039;backgroundcolordesc&#039;, &#039;theme_demystified&#039;), &#039;#DDD&#039;, PARAM_CLEAN, 12));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
While this is structurally identical as all we have done is move everything onto one line and do away with the variables it is a little harder to read when you are learning all of this.&lt;br /&gt;
&lt;br /&gt;
====The logo file====&lt;br /&gt;
Time to create the second setting that will allow the user to enter a URL to an image they wish to use as the logo on their site.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Logo file setting&lt;br /&gt;
$name = &#039;theme_demystified/logo&#039;;&lt;br /&gt;
$title = get_string(&#039;logo&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
$description = get_string(&#039;logodesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
$setting = new admin_setting_configtext($name, $title, $description, &#039;&#039;, PARAM_URL);&lt;br /&gt;
$setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
$settings-&amp;gt;add($setting);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The first thing that you will notice about this setting that it is very similar to the first setting, in fact all we have changed is the name, title, description, and default value. We have however changed the value type from PARAM_CLEAN to PARAM_URL, this makes sure the user enters a URL. You will also notice that for this one we don&#039;t set a size for the field as we have no idea how long the URL will be.&lt;br /&gt;
&lt;br /&gt;
====Block region width====&lt;br /&gt;
The third setting should allow the user to set a width for the block regions that will be used as columns.&lt;br /&gt;
&lt;br /&gt;
For this setting I want to do something a little different from the previous two, here I want to use a select box so that the user selects a width for the column from a list I provide.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Block region width&lt;br /&gt;
$name = &#039;theme_demystified/regionwidth&#039;;&lt;br /&gt;
$title = get_string(&#039;regionwidth&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
$description = get_string(&#039;regionwidthdesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
$default = 200;&lt;br /&gt;
$choices = array(150=&amp;gt;&#039;150px&#039;, 170=&amp;gt;&#039;170px&#039;, 200=&amp;gt;&#039;200px&#039;, 240=&amp;gt;&#039;240px&#039;, 290=&amp;gt;&#039;290px&#039;, 350=&amp;gt;&#039;350px&#039;, 420=&amp;gt;&#039;420px&#039;);&lt;br /&gt;
$setting = new admin_setting_configselect($name, $title, $description, $default, $choices);&lt;br /&gt;
$setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
$settings-&amp;gt;add($setting);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So looking at the code: The first four lines you will recognise. $name, $title, $description, and $default are all being set.&lt;br /&gt;
&lt;br /&gt;
The fifth line of code however introduces something new. Of course in order to have a select box we have to have options, in this case we have an array of options stored in the variable $choices.&lt;br /&gt;
&lt;br /&gt;
The array of options is constructed of a collection of &#039;&#039;&#039;&#039;&#039;value&#039;&#039;&#039; =&amp;gt; &#039;&#039;&#039;label&#039;&#039;&#039;&#039;&#039; pairs. Notice how we don&#039;t add &#039;&#039;&#039;px&#039;&#039;&#039; to the value. This is is very intentional as later on I need to do a little bit of math with that value so we need it to be a number.&lt;br /&gt;
&lt;br /&gt;
The lines after should look familiar again, the only difference being that instead of a &#039;&#039;admin_setting_configtext&#039;&#039; setting we have created a &#039;&#039;admin_setting_configselect&#039;&#039; for which we must give the choices for the select box as the fifth argument.&lt;br /&gt;
&lt;br /&gt;
Woohoo, we&#039;ve just created our first select box setting.&lt;br /&gt;
&lt;br /&gt;
====Foot note====&lt;br /&gt;
Now to create the foot note setting. Here we want the user to be able to enter some arbitrary text that will be used in the footer of the page. For this I want the user to be able to enter some HTML so I will create an editor setting.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Foot note setting&lt;br /&gt;
$name = &#039;theme_demystified/footnote&#039;;&lt;br /&gt;
$title = get_string(&#039;footnote&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
$description = get_string(&#039;footnotedesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
$setting = new admin_setting_confightmleditor($name, $title, $description, &#039;&#039;);&lt;br /&gt;
$setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
$settings-&amp;gt;add($setting);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
How simple is that!&lt;br /&gt;
&lt;br /&gt;
It is just about identical to the first two settings except that for this we have created a &#039;&#039;admin_setting_confightmleditor&#039;&#039; setting rather than a text setting.&lt;br /&gt;
&lt;br /&gt;
Note: You can also set the columns and rows for the editor setting using the fifth and sixth arguments.&lt;br /&gt;
&lt;br /&gt;
====Custom CSS====&lt;br /&gt;
The final setting is to allow the user to add some custom CSS to the theme that will be used on every page. I want this to be a plain textarea into which the user can enter CSS.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Custom CSS file&lt;br /&gt;
$name = &#039;theme_demystified/customcss&#039;;&lt;br /&gt;
$title = get_string(&#039;customcss&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
$description = get_string(&#039;customcssdesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
$setting = new admin_setting_configtextarea($name, $title, $description, &#039;&#039;);&lt;br /&gt;
$setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
$settings-&amp;gt;add($setting);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Just like the editor or text settings. It&#039;s getting very easy now!&lt;br /&gt;
&lt;br /&gt;
====Finishing settings.php====&lt;br /&gt;
With all of our settings defined and added to our page that we created right at the beginning it is time to finish it all off.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
} // This is the closing brace that encloses all the above settings. &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The above line is the final line for the page. It is adding the page that we have created &#039;&#039;&#039;$setting&#039;&#039;&#039; to the admin tree structure. In this case it is adding it to the themes branch.&lt;br /&gt;
&lt;br /&gt;
The following is the completed source for our settings.php ..... for your copy/paste pleasure.&lt;br /&gt;
&amp;lt;div style=&#039;height:300px;overflow:auto;&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * Settings for the demystified theme&lt;br /&gt;
 */&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die;&lt;br /&gt;
 &lt;br /&gt;
if ($ADMIN-&amp;gt;fulltree) {&lt;br /&gt;
&lt;br /&gt;
    // Background colour setting&lt;br /&gt;
    name = &#039;theme_demystified/backgroundcolor&#039;;&lt;br /&gt;
    $title = get_string(&#039;backgroundcolor&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
    $description = get_string(&#039;backgroundcolordesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
    $default = &#039;#DDD&#039;;&lt;br /&gt;
    $setting = new admin_setting_configtext($name, $title, $description, $default, PARAM_CLEAN, 12);&lt;br /&gt;
    $setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
    $settings-&amp;gt;add($setting);&lt;br /&gt;
&lt;br /&gt;
    // Logo file setting.&lt;br /&gt;
    $name = &#039;theme_demystified/logo&#039;;&lt;br /&gt;
    $title = get_string(&#039;logo&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
    $description = get_string(&#039;logodesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
    $setting = new admin_setting_configtext($name, $title, $description, &#039;&#039;, PARAM_URL);&lt;br /&gt;
    $setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
    $settings-&amp;gt;add($setting);&lt;br /&gt;
&lt;br /&gt;
    // Block region width.&lt;br /&gt;
    $name = &#039;theme_demystified/regionwidth&#039;;&lt;br /&gt;
    $title = get_string(&#039;regionwidth&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
    $description = get_string(&#039;regionwidthdesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
    $default = 200;&lt;br /&gt;
    $choices = array(150=&amp;gt;&#039;150px&#039;, 170=&amp;gt;&#039;170px&#039;, 200=&amp;gt;&#039;200px&#039;, 240=&amp;gt;&#039;240px&#039;, 290=&amp;gt;&#039;290px&#039;, 350=&amp;gt;&#039;350px&#039;, 420=&amp;gt;&#039;420px&#039;);&lt;br /&gt;
    $setting = new admin_setting_configselect($name, $title, $description, $default, $choices);&lt;br /&gt;
    $setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
    $settings-&amp;gt;add($setting);&lt;br /&gt;
&lt;br /&gt;
    // Foot note setting.&lt;br /&gt;
    $name = &#039;theme_demystified/footnote&#039;;&lt;br /&gt;
    $title = get_string(&#039;footnote&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
    $description = get_string(&#039;footnotedesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
    $setting = new admin_setting_confightmleditor($name, $title, $description, &#039;&#039;);&lt;br /&gt;
    $setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
    $settings-&amp;gt;add($setting);&lt;br /&gt;
&lt;br /&gt;
    // Custom CSS file.&lt;br /&gt;
    $name = &#039;theme_demystified/customcss&#039;;&lt;br /&gt;
    $title = get_string(&#039;customcss&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
    $description = get_string(&#039;customcssdesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
    $setting = new admin_setting_configtextarea($name, $title, $description, &#039;&#039;);&lt;br /&gt;
    $setting-&amp;gt;set_updatedcallback(&#039;theme_reset_all_caches&#039;);&lt;br /&gt;
    $settings-&amp;gt;add($setting);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating a language file and adding our strings===&lt;br /&gt;
As I&#039;m sure none of you have forgotten, throughout the creation of the our settings.php page, we used a lot of strings that I mentioned we would set later on. Well now is the time to set those strings.&lt;br /&gt;
&lt;br /&gt;
First up create the following directories and file for our language strings:&lt;br /&gt;
* Directory &#039;&#039;&#039;theme/demystified/lang&#039;&#039;&#039;&lt;br /&gt;
* Directory &#039;&#039;&#039;theme/demystified/lang/en&#039;&#039;&#039;&lt;br /&gt;
* File &#039;&#039;&#039;theme/demystified/lang/theme_demystified.php&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
What we have created here is the required structure for Moodle to start looking for language strings.&lt;br /&gt;
&lt;br /&gt;
First Moodle locates the lang directory, once found it looks within that directory for another directory that uses the character code for the language the user has selected, by default this is &#039;&#039;&#039;en&#039;&#039;&#039; for English. Once that is found it looks for the appropriate language file, in this case &#039;&#039;&#039;theme_demystified.php&#039;&#039;&#039; from which it will load all language strings for our theme.&lt;br /&gt;
&lt;br /&gt;
If English isn&#039;t your chosen language simply replace the &#039;&#039;en&#039;&#039; directory with one that uses your chosen languages character code (two letters).&lt;br /&gt;
&lt;br /&gt;
We can now add our language strings to &#039;&#039;&#039;theme/demystified/lang/theme_demystified.php&#039;&#039;&#039;. Copy and paste the following lines of PHP into this file.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This file contains the strings used by the demystified theme&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
$string[&#039;backgroundcolor&#039;] = &#039;Background colour&#039;;&lt;br /&gt;
$string[&#039;backgroundcolordesc&#039;] = &#039;This sets the background colour for the theme.&#039;;&lt;br /&gt;
$string[&#039;configtitle&#039;] = &#039;Demystified theme&#039;;&lt;br /&gt;
$string[&#039;customcss&#039;] = &#039;Custom CSS&#039;;&lt;br /&gt;
$string[&#039;customcssdesc&#039;] = &#039;Any CSS you enter here will be added to every page allowing your to easily customise this theme.&#039;;&lt;br /&gt;
$string[&#039;footnote&#039;] = &#039;Footnote&#039;;&lt;br /&gt;
$string[&#039;footnotedesc&#039;] = &#039;The content from this textarea will be displayed in the footer of every page.&#039;;&lt;br /&gt;
$string[&#039;logo&#039;] = &#039;Logo&#039;;&lt;br /&gt;
$string[&#039;logodesc&#039;] = &#039;Enter the URL to an image to use as the logo for this site. Should be http://www.yoursite.com/path/to/logo.png&#039;;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Demystified&#039;;&lt;br /&gt;
$string[&#039;regionwidth&#039;] = &#039;Column width&#039;;&lt;br /&gt;
$string[&#039;regionwidthdesc&#039;] = &#039;This sets the width of the two block regions that form the left and right columns.&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above lines of code I have added an entry for each language string we used within &#039;&#039;settings.php&#039;&#039;. When adding language strings like this make sure you use single quotes and try to keep things alphabetical - it helps greatly when managing strings.&lt;br /&gt;
&lt;br /&gt;
Now when we view the settings page there will not be any errors or strings missing.&lt;br /&gt;
&lt;br /&gt;
===Having a look at what we have created===&lt;br /&gt;
Now that we have created our settings page (settings.php) and added all of the language strings it is time to have a look at things in your browser.&lt;br /&gt;
&lt;br /&gt;
Open your browser and enter the URL to your site. When you arrive at your site login as an administrator.&lt;br /&gt;
&lt;br /&gt;
If you are not redirected to view the new settings change your URL to &amp;lt;nowiki&amp;gt;http://www.yoursite.com/admin/&amp;lt;/nowiki&amp;gt; and your will see a screen to set the new theme settings we have just created. This lets us know that everything has worked correctly.&lt;br /&gt;
&lt;br /&gt;
At any point now you are able to log in as administrator and within the settings block browse to &#039;&#039;&#039;Site administration &amp;gt; Appearance &amp;gt; Themes &amp;gt; Demystified theme&#039;&#039;&#039; to change those settings.&lt;br /&gt;
&lt;br /&gt;
The screenshot below shows you what you should see at this point:&lt;br /&gt;
&lt;br /&gt;
[[Image:Theme.settings.page.03.png|715px|thumb|left|The settings page we just created]]&lt;br /&gt;
&amp;lt;br style=&amp;quot;clear:both;&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using the settings in CSS==&lt;br /&gt;
With the settings page now created and operational it is time to make use of our new settings. The settings that we want to use within our CSS is as follows:&lt;br /&gt;
&lt;br /&gt;
; backgroundcolor : Will be used to set the background colour in CSS.&lt;br /&gt;
; regionwidth : Will be the width of the column for CSS.&lt;br /&gt;
; customcss : Will be some custom CSS to add to our stylesheet.&lt;br /&gt;
&lt;br /&gt;
At this point those names are the names we used for our setting with the slash and everything before it having been removed.&lt;br /&gt;
&lt;br /&gt;
Before we start tearing into some code it is important that we have a look at what we are going to do and how we are going to go about it.&lt;br /&gt;
&lt;br /&gt;
===How it all works within Moodle===&lt;br /&gt;
The first thing to understand is that while Moodle allows you to create a settings page and automates it inclusion and management right into the administration interfaces there is no smart equivalent for using the settings. This is simply because there is no way to predict how people will want to use the settings.&lt;br /&gt;
&lt;br /&gt;
However don&#039;t think of this as a disadvantage, in fact it is quite the contrary. Although we can&#039;t just &#039;&#039;use&#039;&#039; our settings we can take full control over how and where we use them. It means it will take a little more code but in the end that will work to our advantage as we can do anything we want.&lt;br /&gt;
&lt;br /&gt;
Moodle does help us out a little but not in an obvious way. The first thing that Moodle does is look for a config variable &#039;&#039;&#039;csspostprocess&#039;&#039;&#039; that should be the name of a function which we want called to make any changes to the CSS after it has been prepared.&lt;br /&gt;
&lt;br /&gt;
The second thing Moodle does is include a lib.php from the theme&#039;s directory if one exists (also for the themes the current theme extends.) which ensures that as long as we write our code within &#039;&#039;&#039;theme/demystified/lib.php&#039;&#039;&#039; it will be included and ready to be used.&lt;br /&gt;
&lt;br /&gt;
The third and final thing Moodle does that will help us out here is ensure that by the time any of code is ready to execute the settings have been prepared and are ready to be used within a theme config object which is passed into our &#039;&#039;csspostprocess&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
===Our plan===&lt;br /&gt;
As you have already probably guessed we will need to create a function to make the changes to the CSS that we want. We will then set the theme config option &#039;&#039;&#039;$THEME-&amp;gt;csspostprocess&#039;&#039;&#039; to the name of our function.&lt;br /&gt;
&lt;br /&gt;
By doing this when Moodle builds the CSS file it will call our function afterwards with the CSS and the theme object that contains our setting.&lt;br /&gt;
&lt;br /&gt;
Now we know that we will use the &#039;&#039;csspostprocess&#039;&#039; function but how are we going to change the CSS, we could get the function to add CSS, or we could get the function to replace something within the CSS. My personal preference is to replace something within the CSS, just like what is happening with images. If you want to use an image within CSS you would write &amp;lt;nowiki&amp;gt;[[pix:theme|imagename]]&amp;lt;/nowiki&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For settings I am going to use &amp;lt;nowiki&amp;gt;[[setting:settingname]]&amp;lt;/nowiki&amp;gt;, this way it looks a bit like something you are already familiar with.&lt;br /&gt;
&lt;br /&gt;
What we need to decide upon next is the best way in which to replace our settings tag with the settings that the user has set.&lt;br /&gt;
&lt;br /&gt;
There are two immediate options available to us:&lt;br /&gt;
# Make the &#039;&#039;csspostprocess&#039;&#039; function do all the work.&lt;br /&gt;
# Make the &#039;&#039;csspostprocess&#039;&#039; function call a separate function for each setting.&lt;br /&gt;
Solution 1 might sound like the simplest however it is going to result in a &#039;&#039;&#039;VERY&#039;&#039;&#039; complex function. Remember the user might have left settings blank or entered something that wouldn&#039;t be valid so we would need to make the sure there is some validation and a good default.&lt;br /&gt;
Because of this I think that solution 2 is the better solution.&lt;br /&gt;
&lt;br /&gt;
So we are going to need a &#039;&#039;csspostprocess&#039;&#039; function and then a function for each of the three settings we have that will do the replacements.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// This is our css post process function&lt;br /&gt;
function demystified_process_css($css, $theme) {};&lt;br /&gt;
// This replaces [[setting:backgroundcolor]] with the background colour&lt;br /&gt;
function demystified_set_backgroundcolor($css, $backgroundcolor) {};&lt;br /&gt;
// This replaces [[setting:regionwidth]] with the correct region width&lt;br /&gt;
function demystified_set_regionwidth() {$css, $regionwidth};&lt;br /&gt;
// This replaces [[setting:customcss]] with the custom css&lt;br /&gt;
function demystified_set_customcss() {$css, $customcss};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What you should note about the above functions is that they all start with the theme&#039;s name. This is required to ensure that the functions are named uniquely as it is VERY unlikely that someone has already created these functions.&lt;br /&gt;
&lt;br /&gt;
So with our plan set out lets start writing the code.&lt;br /&gt;
&lt;br /&gt;
===Writing the code===&lt;br /&gt;
The very first thing that we need to do is create a lib.php for our theme into which our css processing functions are going to go. So please at this point create &#039;&#039;&#039;theme/demystified/lib.php&#039;&#039;&#039; and open it in your editor ready to go.&lt;br /&gt;
&lt;br /&gt;
The first bit of code we have to write is the function that will be called by Moodle to do the processing &#039;&#039;&#039;demystified_process_css&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Before we start out please remember that the wonderful thing about coding is that there is any number of solutions to a problem. The solutions that you are seeing here in this tutorial are solutions that I have come up with to meet fulfil the needs of the tutorial without being so complex that they are hard to understand. This probably isn&#039;t how I would go about it normally but this is a little easier to understand for those who aren&#039;t overly familiar with PHP and object orientation.&lt;br /&gt;
&lt;br /&gt;
====The function: demystified_process_css====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function demystified_process_css($css, $theme) {&lt;br /&gt;
&lt;br /&gt;
    if (!empty($theme-&amp;gt;settings-&amp;gt;backgroundcolor)) {&lt;br /&gt;
        $backgroundcolor = $theme-&amp;gt;settings-&amp;gt;backgroundcolor;&lt;br /&gt;
    } else {&lt;br /&gt;
        $backgroundcolor = null;&lt;br /&gt;
    }&lt;br /&gt;
    $css = demystified_set_backgroundcolor($css, $backgroundcolor);&lt;br /&gt;
&lt;br /&gt;
    if (!empty($theme-&amp;gt;settings-&amp;gt;regionwidth)) {&lt;br /&gt;
        $regionwidth = $theme-&amp;gt;settings-&amp;gt;regionwidth;&lt;br /&gt;
    } else {&lt;br /&gt;
        $regionwidth = null;&lt;br /&gt;
    }&lt;br /&gt;
    $css = demystified_set_regionwidth($css, $regionwidth);&lt;br /&gt;
&lt;br /&gt;
    if (!empty($theme-&amp;gt;settings-&amp;gt;customcss)) {&lt;br /&gt;
        $customcss = $theme-&amp;gt;settings-&amp;gt;customcss;&lt;br /&gt;
    } else {&lt;br /&gt;
        $customcss = null;&lt;br /&gt;
    }&lt;br /&gt;
    $css = demystified_set_customcss($css, $customcss);&lt;br /&gt;
&lt;br /&gt;
    return $css;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So lets look at the things that make up this function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function demystified_process_css($css, $theme) {&lt;br /&gt;
    //.....&lt;br /&gt;
    return $css&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This of course is the function declaration. &lt;br /&gt;
&lt;br /&gt;
The function gets given two variables, the first &#039;&#039;&#039;$css&#039;&#039;&#039; is a pile of CSS as one big string, and the second is the theme object &#039;&#039;&#039;$theme&#039;&#039;&#039; that contains all of the configuration, options, and settings for our theme.&lt;br /&gt;
&lt;br /&gt;
It then returns the &#039;&#039;$css&#039;&#039; variable, essentially returning the modified CSS.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    //...&lt;br /&gt;
    if (!empty($theme-&amp;gt;settings-&amp;gt;backgroundcolor)) {&lt;br /&gt;
        $backgroundcolor = $theme-&amp;gt;settings-&amp;gt;backgroundcolor;&lt;br /&gt;
    } else {&lt;br /&gt;
        $backgroundcolor = null;&lt;br /&gt;
    }&lt;br /&gt;
    $css = demystified_set_backgroundcolor($css, $backgroundcolor);&lt;br /&gt;
    //...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are processing our first setting &#039;&#039;backgroundcolor&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
The first thing that we need to do is check whether it has been set and whether it has a value. If it has then we store that value in &#039;&#039;&#039;$backgroundcolor&#039;&#039;&#039;. It is doesn&#039;t have a value then we set &#039;&#039;$backgroundcolor&#039;&#039; to null. This ensures that &#039;&#039;$backgroundcolor&#039;&#039; is set because if it isn&#039;t then you are going to get a notice (if you have debugging on).&lt;br /&gt;
&lt;br /&gt;
The final line of this block calls the function &#039;&#039;&#039;demystified_set_backgroundcolor&#039;&#039;&#039;. We haven&#039;t written this function yet but we will shortly. When we call it we give it the &#039;&#039;$css&#039;&#039; variable that contains all of the CSS and we give it the background colour variable &#039;&#039;$backgroundcolor&#039;&#039;. Once this function is finished it returns the &#039;&#039;$css&#039;&#039; variable with all of the changes made much like how our css processing function works.&lt;br /&gt;
&lt;br /&gt;
What you should also note about this code is this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$theme-&amp;gt;settings-&amp;gt;backgroundcolor&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
As mentioned earlier &#039;&#039;$theme&#039;&#039; is an object that contains all of the configuration and settings for our theme. The &#039;&#039;$theme&#039;&#039; object has a $settings property which contains all of the settings for our theme, and finally the settings property contains a variable backgroundcolor that is the value the user entered for that setting. That is how we get a settings value.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (!empty($theme-&amp;gt;settings-&amp;gt;regionwidth)) {&lt;br /&gt;
    $regionwidth = $theme-&amp;gt;settings-&amp;gt;regionwidth;&lt;br /&gt;
} else {&lt;br /&gt;
    $regionwidth = null;&lt;br /&gt;
}&lt;br /&gt;
$css = demystified_set_regionwidth($css, $regionwidth);&lt;br /&gt;
&lt;br /&gt;
if (!empty($theme-&amp;gt;settings-&amp;gt;customcss)) {&lt;br /&gt;
    $customcss = $theme-&amp;gt;settings-&amp;gt;customcss;&lt;br /&gt;
} else {&lt;br /&gt;
    $customcss = null;&lt;br /&gt;
}&lt;br /&gt;
$css = demystified_set_customcss($css, $customcss);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These two routines are nearly identical to the routine above. For both the regionwidth and the customcss we make sure it has a value and then store it in a variable. We then call the relevant function to make the changes for that setting.&lt;br /&gt;
&lt;br /&gt;
Now that we have the general processing function it is time to write the three functions we have used but not written, &#039;&#039;demystified_set_backgroundcolor&#039;&#039;, &#039;&#039;demystified_set_regionwidth&#039;&#039;, &#039;&#039;demystified_set_customcss&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
====The function: demystified_set_backgroundcolor====&lt;br /&gt;
&lt;br /&gt;
First up demystified_set_backgroundcolor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Sets the background colour variable in CSS&lt;br /&gt;
 *&lt;br /&gt;
 * @param string $css&lt;br /&gt;
 * @param mixed $backgroundcolor&lt;br /&gt;
 * @return string&lt;br /&gt;
 */&lt;br /&gt;
function demystified_set_backgroundcolor($css, $backgroundcolor) {&lt;br /&gt;
    $tag = &#039;[[setting:backgroundcolor]]&#039;;&lt;br /&gt;
    $replacement = $backgroundcolor;&lt;br /&gt;
    if (is_null($replacement)) {&lt;br /&gt;
        $replacement = &#039;#DDDDDD&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    $css = str_replace($tag, $replacement, $css);&lt;br /&gt;
    return $css;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ok so what is happening here?&lt;br /&gt;
&lt;br /&gt;
First we need a variable &#039;&#039;&#039;$tag&#039;&#039;&#039; that contains the tag we are going to replace. As mentioned earlier we are going to use tags that look like the image tags you are already familiar with &#039;&#039;&amp;lt;nowiki&amp;gt;[[setting:settingname]]&amp;lt;/nowiki&amp;gt;&#039;&#039;, in this case &#039;&#039;&amp;lt;nowiki&amp;gt;[[setting:backgroundcolor]]&amp;lt;/nowiki&amp;gt;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Next I am going to create a variable called &#039;&#039;&#039;$replacement&#039;&#039;&#039; into which I put &#039;&#039;&#039;$backgroundcolor&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;IF&#039;&#039;&#039; statement that comes next checks &#039;&#039;$replacement&#039;&#039; to make sure it is not null. If it is then we need to set it to a default value. In this case I have used &#039;&#039;#DDD&#039;&#039; as that was the default for the settings.&lt;br /&gt;
&lt;br /&gt;
The line after the IF statement puts it all together. The &#039;&#039;str_replace&#039;&#039; function that we are calling takes three arguments in this order:&lt;br /&gt;
# The text to search for.&lt;br /&gt;
# The text to replace it with.&lt;br /&gt;
# The text to do the replacement in.&lt;br /&gt;
It then returns the text with all of the replacements made. So in this case we are replacing the tag with the background colour and it is returning the changed CSS.&lt;br /&gt;
&lt;br /&gt;
The final thing is to return the &#039;&#039;$css&#039;&#039; variable which now contains the correct background colour.&lt;br /&gt;
&lt;br /&gt;
====The function: demystified_set_regionwidth====&lt;br /&gt;
&lt;br /&gt;
Next we have the demystified_set_regionwidth function.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Sets the region width variable in CSS&lt;br /&gt;
 *&lt;br /&gt;
 * @param string $css&lt;br /&gt;
 * @param mixed $regionwidth&lt;br /&gt;
 * @return string&lt;br /&gt;
 */&lt;br /&gt;
function demystified_set_regionwidth($css, $regionwidth) {&lt;br /&gt;
    $tag = &#039;[[setting:regionwidth]]&#039;;&lt;br /&gt;
    $doubletag = &#039;[[setting:regionwidthdouble]]&#039;;&lt;br /&gt;
    $replacement = $regionwidth;&lt;br /&gt;
    if (is_null($replacement)) {&lt;br /&gt;
        $replacement = 200;&lt;br /&gt;
    }&lt;br /&gt;
    $css = str_replace($tag, $replacement.&#039;px&#039;, $css);&lt;br /&gt;
    $css = str_replace($doubletag, ($replacement*2).&#039;px&#039;, $css);&lt;br /&gt;
    return $css;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function is very similar to the above function however there is one key thing we are doing different. We are doing two replacements.&lt;br /&gt;
&lt;br /&gt;
# The first replacement is for the width that the user selected. In this case I am replacing the tag &#039;&#039;&amp;lt;nowiki&amp;gt;[[setting:regionwidth]]&amp;lt;/nowiki&amp;gt;&#039;&#039; with the width.&lt;br /&gt;
# The second replacement is for the width x 2. This is because the page layout requires that the width be doubled for some of the CSS. Here I will replace &#039;&#039;&amp;lt;nowiki&amp;gt;[[setting:regionwidthdouble]]&amp;lt;/nowiki&amp;gt;&#039;&#039; with the doubled width.&lt;br /&gt;
&lt;br /&gt;
Remember because it is still just a number we need to add &#039;&#039;&#039;px&#039;&#039;&#039; to the end of each before we do the replacement.&lt;br /&gt;
&lt;br /&gt;
So the overall process of this function is:&lt;br /&gt;
# Define the two tags as &#039;&#039;&#039;$tag&#039;&#039;&#039; and &#039;&#039;&#039;$doubletag&#039;&#039;&#039;.&lt;br /&gt;
# Make &#039;&#039;&#039;$replacement&#039;&#039;&#039; the region width &#039;&#039;$regionwidth&#039;&#039;.&lt;br /&gt;
# Set &#039;&#039;$replacement&#039;&#039; to a default value of 200 is it is null.&lt;br /&gt;
# Replace &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;[[setting:regionwidth]]&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039; with the width.&lt;br /&gt;
# Replace &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;[[setting:regionwidthdouble]]&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039; with the width x 2.&lt;br /&gt;
# Return the changed CSS.&lt;br /&gt;
&lt;br /&gt;
====The function: demystified_set_customcss====&lt;br /&gt;
&lt;br /&gt;
The final function that we need to write is the demystified_set_customcss function.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Sets the custom css variable in CSS&lt;br /&gt;
 *&lt;br /&gt;
 * @param string $css&lt;br /&gt;
 * @param mixed $customcss&lt;br /&gt;
 * @return string&lt;br /&gt;
 */&lt;br /&gt;
function demystified_set_customcss($css, $customcss) {&lt;br /&gt;
    $tag = &#039;[[setting:customcss]]&#039;;&lt;br /&gt;
    $replacement = $customcss;&lt;br /&gt;
    if (is_null($replacement)) {&lt;br /&gt;
        $replacement = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    $css = str_replace($tag, $replacement, $css);&lt;br /&gt;
    return $css;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function is just like the first function. I&#039;m going to let you work it out on your own.&lt;br /&gt;
&lt;br /&gt;
And that is it no more PHP... Hallelujah I can hear you yelling. The final thing we need to do is tell our theme about the functions we have written and put the settings tags into the CSS.&lt;br /&gt;
&lt;br /&gt;
===Finishing it all off===&lt;br /&gt;
&lt;br /&gt;
So there are two things we have to do in order to complete this section and have our the settings page implemented and our settings being used.&lt;br /&gt;
&lt;br /&gt;
First we need to tell our theme that we want to use the function &#039;&#039;demystified_process_css&#039;&#039; as the &#039;&#039;csspostprocess&#039;&#039; function. This is done very simply by adding the following line of PHP to the bottom of our theme&#039;s config.php file &#039;&#039;&#039;theme/demystified/config.php&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$THEME-&amp;gt;csspostprocess = &#039;demystified_process_css&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that done the only thing left is to add the settings tag into the CSS. Remember those settings tags are:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;[[setting:backgroundcolor]]&amp;lt;/nowiki&amp;gt; : We need to add this where ever we want the background colour setting to be used.&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;[[setting:regionwidth]]&amp;lt;/nowiki&amp;gt; : We need to add this where ever we want to set the width of the block regions.&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;[[setting:regionwidthdouble]]&amp;lt;/nowiki&amp;gt; : We need to add this where ever we want to set the doubled width of the block regions.&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;[[setting:customcss]]&amp;lt;/nowiki&amp;gt; : We need to add this to the bottom of the CSS file that we want the custom CSS added to.&lt;br /&gt;
&lt;br /&gt;
So lets make those changes in CSS now, open up your &#039;&#039;core.css&#039;&#039; file and replace the CSS with the CSS below:&lt;br /&gt;
&amp;lt;code css&amp;gt;&lt;br /&gt;
/** Background color is a setting **/&lt;br /&gt;
html {background-color:[[setting:backgroundcolor]];}&lt;br /&gt;
body {margin:30px;padding:0;border:1px solid #333;border-width:0 10px 0 10px;background-color:#333;}&lt;br /&gt;
body #page {background-color:#FFF;position:relative;top:-10px;}&lt;br /&gt;
.block .header {background-image:none;background-color:#0C5CAC;border:1px solid #0C5CAC;color:#FFF;}&lt;br /&gt;
.block {border-color:#4BA7FF;background-color:#DDEEFF;}&lt;br /&gt;
.block .content {background-color:#F1F8FF;}&lt;br /&gt;
a:link,&lt;br /&gt;
a:visited {color:#0C5CAC;}&lt;br /&gt;
a:hover {color:#C77500;}&lt;br /&gt;
#page #page-header {background-color:#0C5CAC;margin:0;padding:0;width:100%;color:#fff;}&lt;br /&gt;
#page #page-header a:link, #page #page-header a:visited {color:#FFAC02}&lt;br /&gt;
#page #page-header .navbar, #page #page-header .navbar a:link, #page #page-header .navbar a:visited {color:#0C5CAC;}&lt;br /&gt;
/** Override the region width **/&lt;br /&gt;
#page-content #region-main-box {left:[[setting:regionwidth]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box {margin-left:-[[setting:regionwidthdouble]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box #region-pre {width:[[setting:regionwidth]];left:[[setting:regionwidth]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box #region-post {width:[[setting:regionwidth]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box #region-main-wrap #region-main {margin-left:[[setting:regionwidthdouble]];}&lt;br /&gt;
.side-pre-only #page-content #region-main-box #region-post-box {margin-left:-[[setting:regionwidth]];}&lt;br /&gt;
.side-pre-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {margin-left:[[setting:regionwidth]];}&lt;br /&gt;
.side-post-only #page-content #region-main-box #region-post-box {margin-left:-[[setting:regionwidth]];}&lt;br /&gt;
.side-post-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {margin-left:[[setting:regionwidth]];}&lt;br /&gt;
/** Custom CSS **/&lt;br /&gt;
[[setting:customcss]]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will notice that &amp;lt;nowiki&amp;gt;[[setting:backgroundcolor]]&amp;lt;/nowiki&amp;gt; has been used for the html tags background colour:&lt;br /&gt;
&amp;lt;code css&amp;gt;&lt;br /&gt;
html {background-color:[[setting:backgroundcolor]];}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have also set the width of the block regions by adding &amp;lt;nowiki&amp;gt;[[setting:regionwidth]]&amp;lt;/nowiki&amp;gt; as the width for region-pre and region-post as shown below:&lt;br /&gt;
&amp;lt;code css&amp;gt;&lt;br /&gt;
#page-content #region-main-box {left:[[setting:regionwidth]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box {margin-left:-[[setting:regionwidthdouble]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box #region-pre {width:[[setting:regionwidth]];left:[[setting:regionwidth]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box #region-post {width:[[setting:regionwidth]];}&lt;br /&gt;
#page-content #region-main-box #region-post-box #region-main-wrap #region-main {margin-left:[[setting:regionwidthdouble]];}&lt;br /&gt;
.side-pre-only #page-content #region-main-box #region-post-box {margin-left:-[[setting:regionwidth]];}&lt;br /&gt;
.side-pre-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {margin-left:[[setting:regionwidth]];}&lt;br /&gt;
.side-post-only #page-content #region-main-box #region-post-box {margin-left:-[[setting:regionwidth]];}&lt;br /&gt;
.side-post-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {margin-left:[[setting:regionwidth]];}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You&#039;ll notice here that we have to set several different widths and margins using the regionwidth setting and make use of the special regionwidthdouble setting that we added.&lt;br /&gt;
&lt;br /&gt;
The final thing that we did was add the &amp;lt;nowiki&amp;gt;[[setting:customcss]]&amp;lt;/nowiki&amp;gt; to the bottom of the file to ensure that the custom CSS comes last (and therefore can override all other CSS).&lt;br /&gt;
&lt;br /&gt;
And with that we are finished. The screenshot below shows how this now looks in the browser if I set the background colour setting to &#039;&#039;&#039;&amp;lt;span style=&#039;color:#FFA800;&#039;&amp;gt;#FFA800&amp;lt;/span&amp;gt;&#039;&#039;&#039; and made the column width 240px;&lt;br /&gt;
&lt;br /&gt;
[[Image:Theme.settings.page.04.png|715px|thumb|left|Our settings in action]]&amp;lt;br style=&amp;quot;clear:both;&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using the settings within our layout files==&lt;br /&gt;
Now that we have utilised the first three settings within our theme&#039;s CSS file it is time to implement the other two settings within the layout files so that they are written directly into the page.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll be glad to know this is no where near as difficult as utilising settings within a CSS file although it still does require a little bit of PHP.&lt;br /&gt;
&lt;br /&gt;
First up is the logo setting. Into this setting the user is able to enter the URL to an image to use as the logo for the site. In my case I want this to be just a background logo on top of which I want to position the page header.&lt;br /&gt;
&lt;br /&gt;
Before I start there is one thing I need to do however and that is create a default logo background that gets shown if the user hasn&#039;t set a specific logo file. To do this I simply created an image &#039;&#039;&#039;logo.jpg&#039;&#039;&#039; and put it into a pix directory within the demystified theme. You should end up with &#039;&#039;&#039;theme/demystified/pix/logo.jpg&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Next open up the front-page layout file &#039;&#039;theme/demystified/layout/frontpage.php&#039;&#039;. At the top of the file is the PHP that checks what blocks regions the page has and a bit of other stuff. Well right below the existing bit of PHP we want to add the following code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (!empty($PAGE-&amp;gt;theme-&amp;gt;settings-&amp;gt;logo)) {&lt;br /&gt;
    //$logourl = $PAGE-&amp;gt;theme-&amp;gt;settings-&amp;gt;logo; // not working give no link to the picture. give the name of the picture. /logo.jpg&lt;br /&gt;
    $logourl = $PAGE-&amp;gt;theme-&amp;gt;setting_file_url(&#039;logo&#039;, &#039;logo&#039;); // tested and works fine&lt;br /&gt;
} else {&lt;br /&gt;
    $logourl = $OUTPUT-&amp;gt;pix_url(&#039;logo&#039;, &#039;theme&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
What we are doing here is creating a variable called $logourl that will contain the URL to a logo file that was either entered by the user or if they left it blank is that of our default logo file.&lt;br /&gt;
&lt;br /&gt;
There are two things that you should notice about this. First the logo setting can be retrieved through &#039;&#039;&#039;$PAGE-&amp;gt;theme-&amp;gt;settings-&amp;gt;logo&#039;&#039;&#039; and second we get the default logo url by calling &#039;&#039;&#039;$OUTPUT-&amp;gt;pix_url(&#039;logo&#039;, &#039;theme&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now that we have the logo URL we are going to use we need to add an image to the header section of the page as shown below:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;page-header&amp;quot; class=&amp;quot;clearfix&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;img class=&amp;quot;sitelogo&amp;quot; src=&amp;quot;&amp;lt;?php echo $logourl;?&amp;gt;&amp;quot; alt=&amp;quot;Custom logo here&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;h1 class=&amp;quot;headermain&amp;quot;&amp;gt;&amp;lt;?php echo $PAGE-&amp;gt;heading ?&amp;gt;&amp;lt;/h1&amp;gt;&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you save that and browse to your sites front page you will notice that the logo file is now being shown. Hooray. However it is probably not styled too nicely so lets quickly fix that. Open up the core.css file and add the following lines of CSS to the bottom of the file.&lt;br /&gt;
&amp;lt;code css&amp;gt;&lt;br /&gt;
#page-header {position:relative;min-height:100px;}&lt;br /&gt;
#page-header .sitelogo {float:left;}&lt;br /&gt;
#page-header .headermain {position:absolute;left:0.5em;top:50px;margin:0;float:none;font-size:40px;}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
These three lines position the image correctly and if you now refresh everything should appear perfectly. &lt;br /&gt;
&lt;br /&gt;
With the logo done the last setting we need to deal with is the footnote setting. The idea with this setting was that the administrator could enter some text into the editor and it would be displayed in the footer of the page.&lt;br /&gt;
&lt;br /&gt;
This is probably the easiest setting to implement.&lt;br /&gt;
&lt;br /&gt;
Within the front page layout file that we edited above add the following lines below those we added previously.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (!empty($PAGE-&amp;gt;theme-&amp;gt;settings-&amp;gt;footnote)) {&lt;br /&gt;
    $footnote = $PAGE-&amp;gt;theme-&amp;gt;settings-&amp;gt;footnote;&lt;br /&gt;
} else {&lt;br /&gt;
    $footnote = &#039;&amp;lt;!-- There was no custom footnote set --&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are just collecting the footnote into a variable &#039;&#039;&#039;$footnote&#039;&#039;&#039; and setting a default footnote comment if the user hasn&#039;t entered one.&lt;br /&gt;
&lt;br /&gt;
We can now echo the $footnote variable within the page footer. This can be done as shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;!-- START OF FOOTER --&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;page-footer&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;footnote&amp;quot;&amp;gt;&amp;lt;?php echo $footnote; ?&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;helplink&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;?php echo page_doc_link(get_string(&#039;moodledocslink&#039;)) ?&amp;gt;&lt;br /&gt;
        &amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And with that done we are finished! Congratulations if you got this far.&lt;br /&gt;
&lt;br /&gt;
The screenshot below shows the demystified theme we have just created that is styled by the settings page.&lt;br /&gt;
&lt;br /&gt;
[[Image:Theme.settings.page.05.png|715px|thumb|left|My finished demystified theme]]&amp;lt;br style=&amp;quot;clear:both&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Testing our creation==&lt;br /&gt;
Congratulations to you! If you&#039;ve made it this far you have done very well. Now it is time to have a look at what we have created and give it a quick test run.&lt;br /&gt;
&lt;br /&gt;
So in this tutorial we have achieved the following:&lt;br /&gt;
&lt;br /&gt;
* We created a theme called demystified that is based on the standard theme.&lt;br /&gt;
* We added a settings page to our new theme.&lt;br /&gt;
* We added the following settings to our settings page:&lt;br /&gt;
** We can set a background colour through the admin interface.&lt;br /&gt;
** We can change the logo of the site.&lt;br /&gt;
** We can change the block region width.&lt;br /&gt;
** We can add a footnote to the page footer&lt;br /&gt;
** We can add some custom CSS to seal the deal.&lt;br /&gt;
* Those settings were then used in the CSS files for our theme.&lt;br /&gt;
* They were also used in the layout files for our theme.&lt;br /&gt;
* And here we are testing it all.&lt;br /&gt;
&lt;br /&gt;
The screenshots below show my testing process and I change each setting and view the outcome. The great thing about this is that with theme designer mode on you see the changes as soon as the form refreshes.&lt;br /&gt;
&lt;br /&gt;
[[Image:Theme.settings.page.06.png|300px|thumb|left|Default settings]]&lt;br /&gt;
[[Image:Theme.settings.page.07.png|300px|thumb|left|Changed the background colour setting]]&lt;br /&gt;
[[Image:Theme.settings.page.08.png|300px|thumb|left|Changed the logo and region width]]&lt;br /&gt;
[[Image:Theme.settings.page.09.png|300px|thumb|left|Added a footnote]]&lt;br /&gt;
[[Image:Theme.settings.page.10.png|300px|thumb|left|Added some custom CSS]]&lt;br /&gt;
&amp;lt;br style=&amp;quot;clear:both&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The different settings you can use==&lt;br /&gt;
During this tutorial we use four different kinds of settings, a text box, text area, a select (dropdown) and the editor however as I&#039;m sure you have all guessed there is many more some of which will certainly be useful for theme settings.&lt;br /&gt;
&lt;br /&gt;
The following are examples of some of the different settings. I should add that these build upon what we have already done within the tutorial but are not included in the download.&lt;br /&gt;
&lt;br /&gt;
===Colour picker===&lt;br /&gt;
[[Image:Theme.settings.page.11.png|400px|thumb|The colour picker]]&lt;br /&gt;
I can hear you all asking now &#039;Why didn&#039;t you mention this one earlier?&#039; well the answer is simple it didn&#039;t exist when I first wrote the tutorial. It is an admin setting that I wrote several days after because of the fantastic effort people were putting into trying out settings pages.&lt;br /&gt;
&lt;br /&gt;
A bit about the colour picker. First up it is a text box that when the page loads turns into a colour picker that can be used to select a colour and can even preview the selected colour in the page (by clicking the preview button). It is designed to be very easy to use and the code is just about as simple as creating a normal text box setting.&lt;br /&gt;
&lt;br /&gt;
In the image to the left I have replaced the background colour setting we created during the tutorial with the colour picker. Lets have a look at the code involved for that.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Background colour setting&lt;br /&gt;
$name = &#039;theme_demystified/backgroundcolor&#039;;&lt;br /&gt;
$title = get_string(&#039;backgroundcolor&#039;,&#039;theme_demystified&#039;);&lt;br /&gt;
$description = get_string(&#039;backgroundcolordesc&#039;, &#039;theme_demystified&#039;);&lt;br /&gt;
$default = &#039;#DDD&#039;;&lt;br /&gt;
$previewconfig = array(&#039;selector&#039;=&amp;gt;&#039;html&#039;, &#039;style&#039;=&amp;gt;&#039;backgroundColor&#039;);&lt;br /&gt;
$setting = new admin_setting_configcolourpicker($name, $title, $description, $default, $previewconfig);&lt;br /&gt;
$temp-&amp;gt;add($setting);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So first thing you should notice is that the first four lines of code have not changed at all!&lt;br /&gt;
&lt;br /&gt;
The first change we have is to create a variable &#039;&#039;&#039;$previewconfig&#039;&#039;&#039;. &lt;br /&gt;
This variable is used to tell the colour picker what to do when the user clicks the preview button. It should be an array that contains two things, first a selector, and second a style. &lt;br /&gt;
# The selector is a CSS selector like &#039;&#039;.page .header h2&#039;&#039;.&lt;br /&gt;
# The style is a what should change, there are two immediate values it can be, either &#039;&#039;&#039;backgroundColor&#039;&#039;&#039; or &#039;&#039;&#039;color&#039;&#039;&#039;.&lt;br /&gt;
In my case the selector is &#039;&#039;&#039;html&#039;&#039;&#039; to target the html tag and the style is backgroundColor to change the background colour.&lt;br /&gt;
&lt;br /&gt;
The second change is to use &#039;&#039;&#039;admin_setting_configcolourpicker&#039;&#039;&#039; instead of &#039;&#039;admin_setting_configtext&#039;&#039;. The arguments are nearly identical as well except that there is an extra argument to which we give the &#039;&#039;&#039;$previewconfig&#039;&#039;&#039; variable.&lt;br /&gt;
&lt;br /&gt;
And that is it! it is all you need to get the colour picker up and running.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Extra note:&#039;&#039;&#039; The $previewconfig variable is optional. If you don&#039;t want a preview button the simply set &#039;&#039;$previewconfig = null&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Common pitfalls and useful notes==&lt;br /&gt;
&lt;br /&gt;
This section is really just a collection of pointers, tips, notes, and other short useful stuff that may be of use to those attempting this tutorial.&lt;br /&gt;
&lt;br /&gt;
# First up and most importantly there are very few limitations to what you achieve in this fashion.&lt;br /&gt;
# If you get stuck or need a hand ask in the forums, there&#039;s always someone round who can help.&lt;br /&gt;
# If you do something really cool let us know, I know everyone in the community loves finding our what others are achieving.&lt;br /&gt;
# You don&#039;t have to use &#039;&#039;admin_settingpage&#039;&#039; you can also use &#039;&#039;&#039;admin_externalpage&#039;&#039;&#039; if you prefer. It should be noted however that it is not the preferred way to do it as admin_externalpage&#039;s were only left in Moodle 2.0 for backwards compatibility (although they are more flexible one day they will be deprecated or removed). Thank you to Darryl for raising this.&lt;br /&gt;
# If you find that your language strings aren&#039;t being used (you are getting language string notices) and you have double checked that you have turned &#039;&#039;&#039;langstringcache&#039;&#039;&#039; off then you may need to delete the language cache directory. This is located in the moodledata directory for you installation: moodledata/cache/lang/*. You should delete the directory for your chosen language by default &#039;&#039;en&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Adding theme upgrade code]]&lt;br /&gt;
* [[Styling and customising the dock]]&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/discuss.php?d=152053 Theme 2.0: Adding a settings page to your theme] forum discussion&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Jenkins_Integration_Testing_for_Plugin_Development&amp;diff=47808</id>
		<title>Jenkins Integration Testing for Plugin Development</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Jenkins_Integration_Testing_for_Plugin_Development&amp;diff=47808"/>
		<updated>2015-05-06T12:41:18Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: Suddenly (as of M2.9), a Moodle dependency seems to require the intl extension as well.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p&amp;gt;&lt;br /&gt;
This article describes how to use a Jenkins continuous integration server for automating PHPUnit and Behat tests for Moodle and Moodle plugins. The configuration uses Moodle&#039;s way of running PHPUnit and Behat tests using the command line.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Integration scenario: We are developing a Moodle plugin. We want to run automated tests, ensuring that it runs correctly on multiple Moodle platform versions, e.g., Moodle 2.7 and Moodle 2.8. This is motivated by the fact that we announce compatibility with these versions in the Moodle plugin directory, and we want to - at least - know, when a plugin becomes incompatible with an older version or needs to be changed. Since we are only running one recent Moodle version for development, automated tests with other versions are useful for us.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Prerequisites for this manual: Ubuntu-based integration server, headless (i.e. without a graphical environment), here: Ubuntu 14.04. &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Installation of required software ==&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Install Jenkins as a Ubuntu native package, as described on their website (instructions currently at http://pkg.jenkins-ci.org/debian/).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Install a Moodle runtime environment. A web server is not needed, since PHP 5&#039;s standalone server will be used. The following commands were used for our system:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install postgresql&amp;lt;br /&amp;gt;&lt;br /&gt;
sudo apt-get install php5-cli php-pear php5-curl php5-pgsql php5-gd php5-intl&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Install git, which will be used to obtain the sources of Moodle and of your plugin.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install git&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For acceptance tests, you need to use a browser. Therefore you need to be able to run graphical applications. On headless servers (i.e. those without a display), you can install Xvfb, emulating a display which will be used by your browser.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install xvfb firefox&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Furthermore, acceptance tests require the Selenium standalone server to be installed. Download from http://www.seleniumhq.org/download/ (here: Version 2.45.0) and unpack it at a jenkins-accessible location, e.g. $JENKINS_HOME/selenium (here: /var/lib/jenkins/selenium).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Basic configuration of Jenkins using the shell ==&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Configuration of the init script can be found at &amp;lt;code&amp;gt;/etc/default/jenkins&amp;lt;/code&amp;gt;. It is not strictly necessary to change anything here, but we set &amp;lt;code&amp;gt;HTTP_PORT=-1&amp;lt;/code&amp;gt; and added &amp;lt;code&amp;gt;HTTPS_PORT=8998&amp;lt;/code&amp;gt; to make the server only via HTTPS.  In that case, you also need to add &amp;lt;code&amp;gt;--httpsPort=$HTTPS_PORT&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;JENKINS_ARGS&amp;lt;/code&amp;gt; variable and restart Jenkins.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Furthermore, Moodle requires a &amp;lt;code&amp;gt;config.php&amp;lt;/code&amp;gt; which is not contained in a repository. Therefore, configuration files need to be stored at a separate location, from where they can be pulled in. For our scenario, we stored the configuration file at &amp;lt;code&amp;gt;$JENKINS_HOME/moodle-config/config_$target.php&amp;lt;/code&amp;gt;, where $target indicates a Moodle branch to build against (e.g., /var/lib/jenkins/moodle-config/config_MOODLE_28_STABLE.php).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Jenkins server configuration ==&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
For all following steps you need to open Jenkins in your browser.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Install or update the following plugins:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Xvfb plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Multiple SCMs plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Matrix project plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;JUnit plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;GitHub plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Job Configuration History Plugin (optional, but very useful)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Configure Jenkins in the following way (Manage Jenkins → Configure System):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Git&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a git installation. Choose an arbitrary name and set the path to &amp;lt;code&amp;gt;git&amp;lt;/code&amp;gt; &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Git plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Set the fields &amp;lt;code&amp;gt;Global Config user.name Value&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Global Config user.email Value&amp;lt;/code&amp;gt;. In our case, we set those values to account and e-mail of a technical GitHub account that is only used by the Jenkins server&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Create a job for your integration scenario:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;On the dashboard, select New Item. &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Choose Multi-configuration project and define an Item name without any spaces.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;On the next page, define the following settings (Change to your requirements. Remember our integration scenario described above!):&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;GitHub project: URL to the web page of your repository&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Source Code management:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Choose &amp;lt;code&amp;gt;Multiple SCMs&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Use &amp;lt;code&amp;gt;Add SCM&amp;lt;/code&amp;gt; to add two git repositories.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the first repository,&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;set the URL to &amp;lt;code&amp;gt;git://git.moodle.org/moodle.git&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Since this is a public repository, credentials are not required.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Under Additional Behaviours, add &amp;lt;code&amp;gt;Advanced clone behaviours&amp;lt;/code&amp;gt; and check the option &amp;lt;code&amp;gt;Shallow clone&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the second repository,&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;set the URL to the URL of your git repository.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;If this is a private repository, add credentials for accessing it (consider SSH authentication!)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Under Additional Behaviours, add &amp;lt;code&amp;gt;Check out to a sub-directory&amp;lt;/code&amp;gt; and, for &amp;lt;code&amp;gt;Local subdirectory for repo&amp;lt;/code&amp;gt;, enter the path where you would deploy your module in a usual Moodle installation (e.g., &amp;lt;code&amp;gt;mod/yourmodule&amp;lt;/code&amp;gt;), without surrounding slashes.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Under Additional Behaviours, add &amp;lt;code&amp;gt;Advanced clone behaviours&amp;lt;/code&amp;gt; and check the option &amp;lt;code&amp;gt;Shallow clone&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Build Triggers:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Build when a change is pushed to GitHub.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Poll SCM. As a schedule, define e.g. &amp;lt;code&amp;gt;H/5 * * * *&amp;lt;/code&amp;gt; for polling every five minutes.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Configuration Matrix:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add two user-defined axes.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the first axis, set name to &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; and values to &amp;lt;code&amp;gt;MOODLE_28_STABLE MOODLE_27_STABLE&amp;lt;/code&amp;gt;. These values correspond to the branch names used in the Moodle repository.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the second axis, set name to &amp;lt;code&amp;gt;testtype&amp;lt;/code&amp;gt; and values to &amp;lt;code&amp;gt;UnitTest BehatTest&amp;lt;/code&amp;gt;. These are used later on to perform different test types - be patient :).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Run each configuration sequentially.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Build Environment:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Start Xvfb before the build, and shut it down after.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Build:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Execute shell&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Enter the following commands (Note that we use &amp;lt;code&amp;gt;$target&amp;lt;/code&amp;gt; here, which always holds the current value of &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; in a particular configuration. Furthermore, this copies an appropriate config.php file into the current workspace):&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 120pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;git checkout $target;&amp;lt;br /&amp;gt;&lt;br /&gt;
curl -s https://getcomposer.org/installer | php;&amp;lt;br /&amp;gt;&lt;br /&gt;
php composer.phar install --prefer-source;&amp;lt;br /&amp;gt;&lt;br /&gt;
cp $JENKINS_HOME/moodle-config/config_$target.php config.php;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul style=&#039;padding-left: 60pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Conditional step (single)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Run?: &amp;lt;code&amp;gt;Strings match&amp;lt;/code&amp;gt;, String 1: &amp;lt;code&amp;gt;${testtype}&amp;lt;/code&amp;gt;, String 2: &amp;lt;code&amp;gt;UnitTest&amp;lt;/code&amp;gt; (the motivation is that this command is only run for unit tests. In contrast, the other step will be for behat acceptance tests).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Builder: &amp;lt;code&amp;gt;Execute shell&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Enter the following commands:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 120pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mkdir -p ../moodledata/phpunit;&amp;lt;br /&amp;gt;&lt;br /&gt;
php admin/tool/phpunit/cli/init.php;&amp;lt;br /&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --log-junit results/phpunit/phpunit.xml --group mod_ratingallocate || true;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul style=&#039;padding-left: 60pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Conditional step (single)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Run?: &amp;lt;code&amp;gt;Strings match&amp;lt;/code&amp;gt;, String 1: &amp;lt;code&amp;gt;${testtype}&amp;lt;/code&amp;gt;, String 2: &amp;lt;code&amp;gt;BehatTest&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Builder: &amp;lt;code&amp;gt;Execute shell&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Enter the following commands (replace &amp;lt;code&amp;gt;[SeleniumVersion]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[ModuleTag]&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;[JobName]&amp;lt;/code&amp;gt; accordingly):&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 120pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mkdir -p ../moodledata/behat;&amp;lt;br /&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php;&amp;lt;br /&amp;gt;&lt;br /&gt;
java -jar /var/lib/jenkins/selenium/selenium-server-standalone-[SeleniumVersion].jar &amp;amp;gt; /dev/null 2&amp;amp;gt;&amp;amp;amp;1 &amp;amp;amp;&amp;lt;br /&amp;gt;&lt;br /&gt;
SELENIUM_PID=$!&amp;lt;br /&amp;gt;&lt;br /&gt;
php -S localhost:8000 &amp;amp;gt; /dev/null 2&amp;amp;gt;&amp;amp;amp;1 &amp;amp;amp;&amp;lt;br /&amp;gt;&lt;br /&gt;
PHP_PID=$!&amp;lt;br /&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /var/lib/jenkins/jobs/[JobName]/workspace/target/moodledata/behat/behat/behat.yml --tags &#039;@[ModuleTag]&#039; --format moodle_progress,junit --out ,behatlog || true;&amp;lt;br /&amp;gt;&lt;br /&gt;
kill $SELENIUM_PID&amp;lt;br /&amp;gt;&lt;br /&gt;
kill $PHP_PID&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Post-build Actions:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Publish JUnit test result report&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Change the pattern for &amp;lt;code&amp;gt;Test report XMLs&amp;lt;/code&amp;gt; to the value &amp;lt;code&amp;gt;**/phpunit/phpunit.xml,**/behatlog/*.xml&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Save the configuration and start the first build to test it.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Developer tools]]&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Jenkins_Integration_Testing_for_Plugin_Development&amp;diff=47756</id>
		<title>Jenkins Integration Testing for Plugin Development</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Jenkins_Integration_Testing_for_Plugin_Development&amp;diff=47756"/>
		<updated>2015-05-05T10:19:15Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: Created page with &amp;quot;&amp;lt;p&amp;gt; This article describes how to use a Jenkins continuous integration server for automating PHPUnit and Behat tests for Moodle and Moodle plugins. The configuration uses Mood...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p&amp;gt;&lt;br /&gt;
This article describes how to use a Jenkins continuous integration server for automating PHPUnit and Behat tests for Moodle and Moodle plugins. The configuration uses Moodle&#039;s way of running PHPUnit and Behat tests using the command line.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Integration scenario: We are developing a Moodle plugin. We want to run automated tests, ensuring that it runs correctly on multiple Moodle platform versions, e.g., Moodle 2.7 and Moodle 2.8. This is motivated by the fact that we announce compatibility with these versions in the Moodle plugin directory, and we want to - at least - know, when a plugin becomes incompatible with an older version or needs to be changed. Since we are only running one recent Moodle version for development, automated tests with other versions are useful for us.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Prerequisites for this manual: Ubuntu-based integration server, headless (i.e. without a graphical environment), here: Ubuntu 14.04. &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Installation of required software ==&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Install Jenkins as a Ubuntu native package, as described on their website (instructions currently at http://pkg.jenkins-ci.org/debian/).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Install a Moodle runtime environment. A web server is not needed, since PHP 5&#039;s standalone server will be used. The following commands were used for our system:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install postgresql&amp;lt;br /&amp;gt;&lt;br /&gt;
sudo apt-get install php5-cli php-pear php5-curl php5-pgsql php5-gd&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Install git, which will be used to obtain the sources of Moodle and of your plugin.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install git&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For acceptance tests, you need to use a browser. Therefore you need to be able to run graphical applications. On headless servers (i.e. those without a display), you can install Xvfb, emulating a display which will be used by your browser.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install xvfb firefox&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Furthermore, acceptance tests require the Selenium standalone server to be installed. Download from http://www.seleniumhq.org/download/ (here: Version 2.45.0) and unpack it at a jenkins-accessible location, e.g. $JENKINS_HOME/selenium (here: /var/lib/jenkins/selenium).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Basic configuration of Jenkins using the shell ==&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Configuration of the init script can be found at &amp;lt;code&amp;gt;/etc/default/jenkins&amp;lt;/code&amp;gt;. It is not strictly necessary to change anything here, but we set &amp;lt;code&amp;gt;HTTP_PORT=-1&amp;lt;/code&amp;gt; and added &amp;lt;code&amp;gt;HTTPS_PORT=8998&amp;lt;/code&amp;gt; to make the server only via HTTPS.  In that case, you also need to add &amp;lt;code&amp;gt;--httpsPort=$HTTPS_PORT&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;JENKINS_ARGS&amp;lt;/code&amp;gt; variable and restart Jenkins.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Furthermore, Moodle requires a &amp;lt;code&amp;gt;config.php&amp;lt;/code&amp;gt; which is not contained in a repository. Therefore, configuration files need to be stored at a separate location, from where they can be pulled in. For our scenario, we stored the configuration file at &amp;lt;code&amp;gt;$JENKINS_HOME/moodle-config/config_$target.php&amp;lt;/code&amp;gt;, where $target indicates a Moodle branch to build against (e.g., /var/lib/jenkins/moodle-config/config_MOODLE_28_STABLE.php).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Jenkins server configuration ==&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
For all following steps you need to open Jenkins in your browser.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Install or update the following plugins:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Xvfb plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Multiple SCMs plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Matrix project plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;JUnit plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;GitHub plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Job Configuration History Plugin (optional, but very useful)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Configure Jenkins in the following way (Manage Jenkins → Configure System):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Git&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a git installation. Choose an arbitrary name and set the path to &amp;lt;code&amp;gt;git&amp;lt;/code&amp;gt; &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Git plugin&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Set the fields &amp;lt;code&amp;gt;Global Config user.name Value&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Global Config user.email Value&amp;lt;/code&amp;gt;. In our case, we set those values to account and e-mail of a technical GitHub account that is only used by the Jenkins server&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
Create a job for your integration scenario:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;On the dashboard, select New Item. &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Choose Multi-configuration project and define an Item name without any spaces.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;On the next page, define the following settings (Change to your requirements. Remember our integration scenario described above!):&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;GitHub project: URL to the web page of your repository&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Source Code management:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Choose &amp;lt;code&amp;gt;Multiple SCMs&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Use &amp;lt;code&amp;gt;Add SCM&amp;lt;/code&amp;gt; to add two git repositories.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the first repository,&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;set the URL to &amp;lt;code&amp;gt;git://git.moodle.org/moodle.git&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Since this is a public repository, credentials are not required.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Under Additional Behaviours, add &amp;lt;code&amp;gt;Advanced clone behaviours&amp;lt;/code&amp;gt; and check the option &amp;lt;code&amp;gt;Shallow clone&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the second repository,&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;set the URL to the URL of your git repository.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;If this is a private repository, add credentials for accessing it (consider SSH authentication!)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Under Additional Behaviours, add &amp;lt;code&amp;gt;Check out to a sub-directory&amp;lt;/code&amp;gt; and, for &amp;lt;code&amp;gt;Local subdirectory for repo&amp;lt;/code&amp;gt;, enter the path where you would deploy your module in a usual Moodle installation (e.g., &amp;lt;code&amp;gt;mod/yourmodule&amp;lt;/code&amp;gt;), without surrounding slashes.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Under Additional Behaviours, add &amp;lt;code&amp;gt;Advanced clone behaviours&amp;lt;/code&amp;gt; and check the option &amp;lt;code&amp;gt;Shallow clone&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Build Triggers:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Build when a change is pushed to GitHub.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Poll SCM. As a schedule, define e.g. &amp;lt;code&amp;gt;H/5 * * * *&amp;lt;/code&amp;gt; for polling every five minutes.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Configuration Matrix:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add two user-defined axes.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the first axis, set name to &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; and values to &amp;lt;code&amp;gt;MOODLE_28_STABLE MOODLE_27_STABLE&amp;lt;/code&amp;gt;. These values correspond to the branch names used in the Moodle repository.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;For the second axis, set name to &amp;lt;code&amp;gt;testtype&amp;lt;/code&amp;gt; and values to &amp;lt;code&amp;gt;UnitTest BehatTest&amp;lt;/code&amp;gt;. These are used later on to perform different test types - be patient :).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Run each configuration sequentially.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Build Environment:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Check Start Xvfb before the build, and shut it down after.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Build:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Execute shell&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Enter the following commands (Note that we use &amp;lt;code&amp;gt;$target&amp;lt;/code&amp;gt; here, which always holds the current value of &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; in a particular configuration. Furthermore, this copies an appropriate config.php file into the current workspace):&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 120pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;git checkout $target;&amp;lt;br /&amp;gt;&lt;br /&gt;
curl -s https://getcomposer.org/installer | php;&amp;lt;br /&amp;gt;&lt;br /&gt;
php composer.phar install --prefer-source;&amp;lt;br /&amp;gt;&lt;br /&gt;
cp $JENKINS_HOME/moodle-config/config_$target.php config.php;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul style=&#039;padding-left: 60pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Conditional step (single)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Run?: &amp;lt;code&amp;gt;Strings match&amp;lt;/code&amp;gt;, String 1: &amp;lt;code&amp;gt;${testtype}&amp;lt;/code&amp;gt;, String 2: &amp;lt;code&amp;gt;UnitTest&amp;lt;/code&amp;gt; (the motivation is that this command is only run for unit tests. In contrast, the other step will be for behat acceptance tests).&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Builder: &amp;lt;code&amp;gt;Execute shell&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Enter the following commands:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 120pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mkdir -p ../moodledata/phpunit;&amp;lt;br /&amp;gt;&lt;br /&gt;
php admin/tool/phpunit/cli/init.php;&amp;lt;br /&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --log-junit results/phpunit/phpunit.xml --group mod_ratingallocate || true;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul style=&#039;padding-left: 60pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Conditional step (single)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Run?: &amp;lt;code&amp;gt;Strings match&amp;lt;/code&amp;gt;, String 1: &amp;lt;code&amp;gt;${testtype}&amp;lt;/code&amp;gt;, String 2: &amp;lt;code&amp;gt;BehatTest&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Builder: &amp;lt;code&amp;gt;Execute shell&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Enter the following commands (replace &amp;lt;code&amp;gt;[SeleniumVersion]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[ModuleTag]&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;[JobName]&amp;lt;/code&amp;gt; accordingly):&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;div style=&#039;padding-left: 120pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mkdir -p ../moodledata/behat;&amp;lt;br /&amp;gt;&lt;br /&gt;
php admin/tool/behat/cli/init.php;&amp;lt;br /&amp;gt;&lt;br /&gt;
java -jar /var/lib/jenkins/selenium/selenium-server-standalone-[SeleniumVersion].jar &amp;amp;gt; /dev/null 2&amp;amp;gt;&amp;amp;amp;1 &amp;amp;amp;&amp;lt;br /&amp;gt;&lt;br /&gt;
SELENIUM_PID=$!&amp;lt;br /&amp;gt;&lt;br /&gt;
php -S localhost:8000 &amp;amp;gt; /dev/null 2&amp;amp;gt;&amp;amp;amp;1 &amp;amp;amp;&amp;lt;br /&amp;gt;&lt;br /&gt;
PHP_PID=$!&amp;lt;br /&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /var/lib/jenkins/jobs/[JobName]/workspace/target/moodledata/behat/behat/behat.yml --tags &#039;@[ModuleTag]&#039; --format moodle_progress,junit --out ,behatlog || true;&amp;lt;br /&amp;gt;&lt;br /&gt;
kill $SELENIUM_PID&amp;lt;br /&amp;gt;&lt;br /&gt;
kill $PHP_PID&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
&amp;lt;ul style=&#039;padding-left: 30pt&#039;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Post-build Actions:&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add Publish JUnit test result report&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Change the pattern for &amp;lt;code&amp;gt;Test report XMLs&amp;lt;/code&amp;gt; to the value &amp;lt;code&amp;gt;**/phpunit/phpunit.xml,**/behatlog/*.xml&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Save the configuration and start the first build to test it.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Developer tools]]&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=sandbox&amp;diff=47754</id>
		<title>sandbox</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=sandbox&amp;diff=47754"/>
		<updated>2015-05-05T08:03:07Z</updated>

		<summary type="html">&lt;p&gt;Jan.dagefoerde: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;For playing around in...&lt;br /&gt;
a&lt;/div&gt;</summary>
		<author><name>Jan.dagefoerde</name></author>
	</entry>
</feed>