<?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=Brendanheywood</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=Brendanheywood"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Brendanheywood"/>
	<updated>2026-05-16T04:29:30Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Hooks_spec&amp;diff=64470</id>
		<title>Hooks spec</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Hooks_spec&amp;diff=64470"/>
		<updated>2025-02-09T22:14:57Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
&lt;br /&gt;
= Moodle hooks/features (Abandoned) =&lt;br /&gt;
&lt;br /&gt;
{{Note|This proposal was worked on for several years but no consensus was reached. A decision was made by the integrators to abandon this project as it has consumed too much of the development communities time and resources. The text on this page is kept for historical reasons only. See the comments on MDL-44078 for the full discussion.|gotcha}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then a later effort to introduce hooks then DID land in core, and now most callbacks on progressively being converted to hooks:&lt;br /&gt;
&lt;br /&gt;
https://moodledev.io/docs/4.5/apis/core/hooks&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you are looking for the existing callback based API please see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Callbacks&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The full discussion is available on MDL-44078 and in forum: https://moodle.org/mod/forum/discuss.php?d=254508 and https://moodle.org/mod/forum/discuss.php?d=327349 but this issue is dead, please lets not keep discussing it.&lt;br /&gt;
&lt;br /&gt;
==Terms used==&lt;br /&gt;
&lt;br /&gt;
Terminology in the discussion around this feature has often been confusing because of the ways that other systems have used them (eg Drupal).&lt;br /&gt;
&lt;br /&gt;
As a guide - the following terms are equivalent and any other interpretation is not allowed:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;hook&#039;&#039;&#039; === hook point === trigger === caller&lt;br /&gt;
* &#039;&#039;&#039;callback&#039;&#039;&#039; === listener === executor&lt;br /&gt;
&lt;br /&gt;
== Goals ==&lt;br /&gt;
&lt;br /&gt;
# Create a consistent/simple/performant/secure way to create extension points where all plugins can run code (never specific plugin types) and modify data (no restrictions - not tied specifically to events)&lt;br /&gt;
# Create a consistent/simple/performant/secure way to call code from another specific plugin&lt;br /&gt;
# Allow implementations in an autoloaded class &lt;br /&gt;
# Allow implementations in lib.php (only for legacy)&lt;br /&gt;
# Allow testing hook implementations even if no plugin in core currently implements a callback&lt;br /&gt;
# Enforce organisation and categorisation of all &amp;quot;callers&amp;quot; and &amp;quot;executors&amp;quot; in a plugin/subsystem/core&lt;br /&gt;
&lt;br /&gt;
=== Disregarded Goals ===&lt;br /&gt;
This section is only kept to show the history of this issue - these goals were initially listed, but since then nobody requested and they seem like a bad idea (overly complex - no use case).&lt;br /&gt;
# Provide an admin UI to control the executed plugins for each hook (order and enable/disable)&lt;br /&gt;
# Dropping in a hook implementation from some other large framework/web app that does not suit Moodle&lt;br /&gt;
# Blowing this feature up into some full controller for business logic / dependency injection / bloat&lt;br /&gt;
&lt;br /&gt;
== Current solutions ==&lt;br /&gt;
&lt;br /&gt;
* Defining functions in /plugindir/lib.php with the name &amp;lt;pluginname&amp;gt;_&amp;lt;hookname&amp;gt; and querying all plugins if somebody implements function and/or list of plugins implementing it. *This is the current implementation of hooks.* Disadvantage: functions must be defined in lib.php therefore they are always included even when function is not there (performance loss); no UI to configure if only one plugin can implement feature; difficult for developers because they don&#039;t know if they can or how they can implement a hook (no separate place for documentation).&lt;br /&gt;
&lt;br /&gt;
* Events system. The core notifies whoever wants to listen to it about something that happens. Disadvantage: one-way communication, no feedback. Advantage: plugins cannot break important core functionality.&lt;br /&gt;
&lt;br /&gt;
* $CFG variables. These are fine if it makes sense to have a setting to choose different implementations of a feature (e.g. lock factories).&lt;br /&gt;
&lt;br /&gt;
* Creating a new plugin type. Disadvantage: very long process, involves lots of documentation, creating lots of strings and UI, may be available in major releases only. Also not applicable for minor features. Forcing a &amp;quot;feature&amp;quot; to be split amongst many plugins.&lt;br /&gt;
&lt;br /&gt;
* Renderers. These can be used to override a lot of standard Moodle behaviour, particularly on the view side. Only can be overridden by themes&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
* Hook definition is a class stored in /classes/hook/ folder (of core component or plugin). They have a function execute() that calls all registered callbacks and passes the actual instance of the hook definition class to them. Some hooks may be called on one component/plugin at a time only.&lt;br /&gt;
&lt;br /&gt;
* Hooks to be executed must be registered in /db/hooks.php so it is possible to determine all the hooks called by a plugin/subsystem.&lt;br /&gt;
&lt;br /&gt;
* Plugins that want to register hook executions (callbacks) do so in /db/hooks.php&lt;br /&gt;
&lt;br /&gt;
* Hook manager, is responsible for caching of the list of existing hooks and their callbacks. It also has an interface with list of all hooks and callbacks.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
Typical example is a hook that allows plugins to add fields to the existing form. Note that actual hook classes may be much more complicated, with lots of methods that can be called from callbacks and also with properties/methods that can be called by the component who executed the hook to access the collected data from plugins.&lt;br /&gt;
&lt;br /&gt;
0. Base class does almost nothing, it&#039;s just a suggestion for implementation, methods are not final (unlike events) &#039;&#039;&#039;/lib/classes/hook/base.php&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
namespace core\hook;&lt;br /&gt;
abstract class base {&lt;br /&gt;
    private $isexecuting = false;&lt;br /&gt;
    public function execute($component = null) {&lt;br /&gt;
        if ($this-&amp;gt;isexecuting) {&lt;br /&gt;
            throw new \moodle_exception(&#039;Already executing&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        $this-&amp;gt;isexecuting = true;&lt;br /&gt;
        \core\hook\manager::dispatch($this, $component);&lt;br /&gt;
        $this-&amp;gt;isexecuting = false;&lt;br /&gt;
        return $this;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;/lib/classes/hook/courseresetform.php&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
namespace core\hook;&lt;br /&gt;
public class courseresetform extends \core\hook\base {&lt;br /&gt;
    private $mform;&lt;br /&gt;
    public function get_mform() {&lt;br /&gt;
        return $this-&amp;gt;mform;&lt;br /&gt;
    }&lt;br /&gt;
    public static function create($mform) {&lt;br /&gt;
        $hook = new self();&lt;br /&gt;
        $hook-&amp;gt;mform = $mform;&lt;br /&gt;
        return $hook;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;/course/reset_form.php&#039;&#039;&#039;, function course_reset_form::definition()&lt;br /&gt;
at the moment this function looks for function xxx_reset_course_form_definition() in all mod plugins and call this function passing $mform. We can have a legacy hook execution that does this for backward compatibility but it will just call:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\core\hook\courseresetform::create($mform)-&amp;gt;execute();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
The hook also must be listed in db/hooks.php&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Hooks that exist in Moodle core.&lt;br /&gt;
$hooks = array(&lt;br /&gt;
    &#039;\core\hook\courseresetform&#039;,           // Allows components/plugins to extend the course reset form.&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Now any plugin type and not only module but also blocks (existing issue MDL-24359) can register their callbacks in &#039;&#039;&#039;/blocks/myblock/db/hooks.php&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$callbacks = array(&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;hookname&#039;    =&amp;gt; &#039;\core\hook\courseresetform&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; array(&#039;block_myblock_hook_callbacks&#039;, &#039;courseresetform&#039;),&lt;br /&gt;
        //&#039;includefile&#039; =&amp;gt; &#039;/optional/file/path.php&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. Actual callback implementation from /blocks/myblock/classes/hook_callbacks.php:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class block_myblock_hook_callbacks {&lt;br /&gt;
    public static function courseresetform(\core\hook\courseresetform $courseresetformhook) {&lt;br /&gt;
        $courseresetformhook-&amp;gt;get_mform()-&amp;gt;addElement(&#039;checkbox&#039;, &#039;Reset contents of block Myblock&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Use cases ==&lt;br /&gt;
&lt;br /&gt;
# Navigation: we want to allow any plugin to put own items anywhere&lt;br /&gt;
# Recent activity in course&lt;br /&gt;
# Global search: similar to above but result will be list of title+url+abstract&lt;br /&gt;
# Front page: We have $CFG-&amp;gt;customfrontpageinclude for including a file displaying frontpage contents. It could be also hook/feature&lt;br /&gt;
# Extending mimetypes list&lt;br /&gt;
# Extending licenses list&lt;br /&gt;
# Adding fields to the standard forms. This may be a comprehensive feature that includes several functions - adding fields, validation, processing.&lt;br /&gt;
&lt;br /&gt;
== Comparison with Moodle Events ==&lt;br /&gt;
&lt;br /&gt;
Differences with [[Events API]]:&lt;br /&gt;
* events are one way communication (from one to many) - hooks are two way communication (usually many to one)&lt;br /&gt;
* event data must not be modified - hook callbacks may alter the hook instance data&lt;br /&gt;
* events are triggered after actions - hooks can be executed at any useful stage (pre, post, validation, etc.)&lt;br /&gt;
* event structure is fixed - hooks can be arbitrary classes that may extend the base&lt;br /&gt;
* database transaction may cause event buffering - hook manager does does not consider transactions&lt;br /&gt;
* all events are logged (this is contentious) - hooks are not logged&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=63909</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=63909"/>
		<updated>2023-06-05T03:15:18Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== What is a session lock? ==&lt;br /&gt;
When you create a normal moodle page and include config.php then by default a large amount of moodle bootstrapping runs and you will have the $SESSION global setup for you. While the php script is executing it holds a session lock and at the end it writes back to the session an unlocks it. This means that only a single script can run at a time for a given user while holding the session lock. In practice most scripts run quite fast and most of the time is spent sending the result back over the network and so you often don&#039;t notice this. But if you have a long running page like a report or backup this starts to be noticed by the end user as a pause while the affected page is blocked waiting for another page to finish execution on the server. &lt;br /&gt;
&lt;br /&gt;
When writing higher performance code it is better to reduce or eliminate the session locks where possible.&lt;br /&gt;
== Debugging session lock issues ==&lt;br /&gt;
If you have &#039;pages that are slow&#039; and you profile them and see that you are waiting on a lock to free up then this is potentially an easy thing to fix to improve your overall performance.&lt;br /&gt;
 $CFG-&amp;gt;debugsessionlock = 5; // Time in seconds&lt;br /&gt;
When a session is locked for more N seconds a debugging call will be made with details of what the other page was which is holding onto the lock. A good strategy is to set this to a value like 30 seconds and then gradually reduce this down to a very small number of seconds as you identify and fix various issues. As a rule of thumb in an ideal world no page should ever lock the session for more than a second.&lt;br /&gt;
== Session unlocking ==&lt;br /&gt;
By default core assumes that you might need to mutate the $SESSION object so it will hold a lock on the session until the page finished and a shutdown handler will release the session lock.&lt;br /&gt;
&lt;br /&gt;
If you are working on any page which is potentially long running, then you should cleanly separate logic which runs early which could mutate the session from the long running processing code and unlock the session.&lt;br /&gt;
 \core\session\manager::write_close();&lt;br /&gt;
== Read only session in pages ==&lt;br /&gt;
For this to work, READONLY sessions must be enabled as well needing your code to support it. &lt;br /&gt;
&lt;br /&gt;
If you know ahead of time that you will never mutate the session, but you still need to be able to read it, then you can declare your page to be read only. This will mean your page will never block the session in another http request.&lt;br /&gt;
 define(&#039;READ_ONLY_SESSION&#039;, true);&lt;br /&gt;
== Read only sessions in web services ==&lt;br /&gt;
The same is possible in web services. When you declare your web service you can specify it will not need a session lock:&lt;br /&gt;
     &#039;core_message_get_unread_conversations_count&#039; =&amp;gt; array(&lt;br /&gt;
         &#039;classname&#039; =&amp;gt; &#039;core_message_external&#039;,&lt;br /&gt;
         &#039;methodname&#039; =&amp;gt; &#039;get_unread_conversations_count&#039;,&lt;br /&gt;
         &#039;classpath&#039; =&amp;gt; &#039;message/externallib.php&#039;,&lt;br /&gt;
         &#039;description&#039; =&amp;gt; &#039;Retrieve the count of unread conversations for a given user&#039;,&lt;br /&gt;
         &#039;type&#039; =&amp;gt; &#039;read&#039;,&lt;br /&gt;
         &#039;ajax&#039; =&amp;gt; true,&lt;br /&gt;
         &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE),&lt;br /&gt;
         &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&#039;readonlysession&#039; =&amp;gt; true, // We don&#039;t modify the session.&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
     ), &lt;br /&gt;
== No session at all ==&lt;br /&gt;
If your script doesn&#039;t actually need $SESSION in the first place then save even more processing and locks by declaring:&lt;br /&gt;
 define(&#039;NO_MOODLE_COOKIES&#039;, true);&lt;br /&gt;
== No config is needed ==&lt;br /&gt;
Going to the absolute extreme, if you do not even need the full moodle bootstrap to run then you can skip it via:&lt;br /&gt;
 define(&#039;ABORT_AFTER_CONFIG&#039;, true);&lt;br /&gt;
[[ja:セッションロック_(Dev_docs)]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=63908</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=63908"/>
		<updated>2023-06-05T03:10:57Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== What is a session lock? ==&lt;br /&gt;
When you create a normal moodle page and include config.php then by default a large amount of moodle bootstrapping runs and you will have the $SESSION global setup for you. While the php script is executing it holds a session lock and at the end it writes back to the session an unlocks it. This means that only a single script can run at a time for a given user while holding the session lock. In practice most scripts run quite fast and most of the time is spent sending the result back over the network and so you often don&#039;t notice this. But if you have a long running page like a report or backup this starts to be noticed by the end user as a pause while the victim page is blocked waiting for another page to finish execution on the server. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This is a safe starting assumption but when writing higher performance code it is better to reduce or eliminate the session locks where possible.&lt;br /&gt;
== Debugging session lock issues ==&lt;br /&gt;
If you have &#039;pages that are slow&#039; and you profile them and see that you are waiting on a lock to free up then this is potentially an easy thing to fix to improve your overall performance.&lt;br /&gt;
 $CFG-&amp;gt;debugsessionlock = 5; // Time in seconds&lt;br /&gt;
When a session is locked for more N seconds a debugging call will be made with details of what the other page was which is holding onto the lock.&lt;br /&gt;
== Session unlocking ==&lt;br /&gt;
By default core assumes that you might need to mutate the $SESSION object so it will hold a lock on the session until the page finished and a shutdown handler will release the session lock.&lt;br /&gt;
If you are working on any page which is potentially long running, then you should cleanly separate logic which runs early which could mutate the session from the long running processin code and unlock the session.&lt;br /&gt;
 \core\session\manager::write_close();&lt;br /&gt;
== Read only session in pages ==&lt;br /&gt;
For this to work, READONLY sessions must be enabled as well needing your code to support it. Not all session &lt;br /&gt;
&lt;br /&gt;
If you know ahead of time that you will never mutate the session, but you still need to be able to read it, then you can declare your page to be read only. This will mean your page will never block the session in another http request.&lt;br /&gt;
 define(&#039;READ_ONLY_SESSION&#039;, true);&lt;br /&gt;
== Read only sessions in web services ==&lt;br /&gt;
The same is possible in web services. When you declare your web service you can specify it will not need a session lock:&lt;br /&gt;
     &#039;core_message_get_unread_conversations_count&#039; =&amp;gt; array(&lt;br /&gt;
         &#039;classname&#039; =&amp;gt; &#039;core_message_external&#039;,&lt;br /&gt;
         &#039;methodname&#039; =&amp;gt; &#039;get_unread_conversations_count&#039;,&lt;br /&gt;
         &#039;classpath&#039; =&amp;gt; &#039;message/externallib.php&#039;,&lt;br /&gt;
         &#039;description&#039; =&amp;gt; &#039;Retrieve the count of unread conversations for a given user&#039;,&lt;br /&gt;
         &#039;type&#039; =&amp;gt; &#039;read&#039;,&lt;br /&gt;
         &#039;ajax&#039; =&amp;gt; true,&lt;br /&gt;
         &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE),&lt;br /&gt;
         &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&#039;readonlysession&#039; =&amp;gt; true, // We don&#039;t modify the session.&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
     ), &lt;br /&gt;
== No session at all ==&lt;br /&gt;
If your script doesn&#039;t actually need $SESSION in the first place then save even more processing and locks by declaring:&lt;br /&gt;
 define(&#039;NO_MOODLE_COOKIES&#039;, true);&lt;br /&gt;
== No config is needed ==&lt;br /&gt;
Going to the absolute extreme, if you do not even need the full moodle bootstrap to run then you can skip it via:&lt;br /&gt;
 define(&#039;ABORT_AFTER_CONFIG&#039;, true);&lt;br /&gt;
[[ja:セッションロック_(Dev_docs)]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63891</id>
		<title>Error pages</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63891"/>
		<updated>2023-05-23T04:44:11Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* 403 in Nginx */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is for any Moodle admins or developers who want to setup or customize error pages on their Moodle site. There is a variety of ways different errors can happen at different layers in a Moodle stack and because they each happen under different circumstance there cannot be a single way of handling them.&lt;br /&gt;
== 404 Normal Moodle errors and exceptions ==&lt;br /&gt;
These types of errors are things like you tried to access a course which no longer exists, or access a course which you don&#039;t have access to. Because these are &amp;quot;Moodle&amp;quot; pages they are themed correctly and normally there isn&#039;t much you want to do to improve them.&lt;br /&gt;
&lt;br /&gt;
If you want to change the wording of the errors see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/en/Language_customisation&lt;br /&gt;
&lt;br /&gt;
If you want to change the theme, that is one and the same as normal theme customization:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Themes_overview&lt;br /&gt;
&lt;br /&gt;
If a Moodle page throws an exception which isn&#039;t captured and results in an error page, and it will also helpfully, but blindly, link into this Moodle wiki where you might find more information on the error. This is great for sites in development or for admin type errors but typically less useful. You may want to divert users to your own support instead of off into a wiki page with generic Moodle information.&lt;br /&gt;
&lt;br /&gt;
This links behavior can be changed and removed in your admin settings here:&lt;br /&gt;
&lt;br /&gt;
/admin/settings.php?section=documentation&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Exceptions&lt;br /&gt;
&lt;br /&gt;
You can also simply hide the links using a capability too:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Linking_Moodle_and_Docs#Removing_Moodle_Docs_links_for_teachers&lt;br /&gt;
== 403 Forbidden - Web server errors ==&lt;br /&gt;
For additional security you should not allow access to directory listings and you can also block access to certain files.&lt;br /&gt;
&lt;br /&gt;
For more security you can serve a 404 instead of a 403, and if you prefer you can reuse the themed 404 page below.&lt;br /&gt;
=== 403 in Apache ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
DirectoryIndex disabled&lt;br /&gt;
Options -Indexes&lt;br /&gt;
ErrorDocument 403 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex&lt;br /&gt;
=== 403 in Nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php?code=404&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;If you want to transform all 403 errors into 404&#039;s then:&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php?code=404&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Note the &#039;=&#039; char above is important as it allows Moodle to reset the http headers to correctly serve the page in all cases.&lt;br /&gt;
== 404 File not found - Web server errors ==&lt;br /&gt;
These are for errors which are technically outside of moodle such a 404 file not found at the server level rather than a 404 served by moodle. If you want to theme these then the approach is the same as you normally would for anything non Moodle, configure your web server to show a custom error page. But instead of writing your own, there is a built in Moodle error handler php script so they get the normal Moodle theme applied. You can see an example of what this error page looks like here:&lt;br /&gt;
&lt;br /&gt;
https://moodle.org/errors/&lt;br /&gt;
=== 404 in Apache ===&lt;br /&gt;
You can configure this using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
ErrorDocument 404 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== 404 in nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
error_page 404 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Note the &#039;=&#039; char above is important as it allows Moodle to reset the http headers to correctly serve the page in all cases.&lt;br /&gt;
== 502 Bad Gateway - Proxy / CDN issues ==&lt;br /&gt;
This means some upstream of your moodle server could not connect, ie nginx, varnish, a load balancer, proxy or CDN.&lt;br /&gt;
&lt;br /&gt;
These error pages cannot be themed by at the application level and how to customize them depends on what is proxying moodle.&lt;br /&gt;
&lt;br /&gt;
NOTE: It is very important if you do customize these to make sure you are not overriding the 40x and 50x error pages that Moodle may serve.&lt;br /&gt;
== 503 Service unavailable - Fatal Moodle bootstrap errors ==&lt;br /&gt;
New in 4.0&lt;br /&gt;
&lt;br /&gt;
These types of errors are more fundamental, and something has gone wrong so Moodle itself can&#039;t load, which means the error page can&#039;t access the database, MUC, or the language and strings API.&lt;br /&gt;
&lt;br /&gt;
Previously these were difficult to style, and there are many edge cases to capture. This was fixed in this tracker:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-56041&lt;br /&gt;
&lt;br /&gt;
These low level error pages resulted in the &#039;yellow box&#039; and in 4.0 are now improved to more closely match the boost / bootstrap theme. You can customize this error page by editing this file:&lt;br /&gt;
&lt;br /&gt;
error/plainpage.php&lt;br /&gt;
&lt;br /&gt;
Note: this file cannot contain any Moodle API&#039;s and cannot link to files which are served by moodle such as theme files or plugin files. Ideally serve all dependencies such as css and even image inline.&lt;br /&gt;
== 503 Moodle under maintenance - Maintenance mode ==&lt;br /&gt;
During maintenance mode Moodle will serve a &amp;quot;503 Service Unavailable&amp;quot;. This page can be customized:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Maintenance_mode#CLI_maintenance_mode&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=63882</id>
		<title>Cache API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Cache_API&amp;diff=63882"/>
		<updated>2023-05-10T13:29:45Z</updated>

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

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

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is for any Moodle admins or developers who want to setup or customize error pages on their Moodle site. There is a variety of ways different errors can happen at different layers in a Moodle stack and because they each happen under different circumstance there cannot be a single way of handling them.&lt;br /&gt;
== 404 Normal Moodle errors and exceptions ==&lt;br /&gt;
These types of errors are things like you tried to access a course which no longer exists, or access a course which you don&#039;t have access to. Because these are &amp;quot;Moodle&amp;quot; pages they are themed correctly and normally there isn&#039;t much you want to do to improve them.&lt;br /&gt;
&lt;br /&gt;
If you want to change the wording of the errors see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/en/Language_customisation&lt;br /&gt;
&lt;br /&gt;
If you want to change the theme, that is one and the same as normal theme customization:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Themes_overview&lt;br /&gt;
&lt;br /&gt;
If a Moodle page throws an exception which isn&#039;t captured and results in an error page, and it will also helpfully, but blindly, link into this Moodle wiki where you might find more information on the error. This is great for sites in development or for admin type errors but typically less useful. You may want to divert users to your own support instead of off into a wiki page with generic Moodle information.&lt;br /&gt;
&lt;br /&gt;
This links behavior can be changed and removed in your admin settings here:&lt;br /&gt;
&lt;br /&gt;
/admin/settings.php?section=documentation&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Exceptions&lt;br /&gt;
&lt;br /&gt;
You can also simply hide the links using a capability too:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Linking_Moodle_and_Docs#Removing_Moodle_Docs_links_for_teachers&lt;br /&gt;
== 403 Forbidden - Web server errors ==&lt;br /&gt;
For additional security you should not allow access to directory listings and you can also block access to certain files.&lt;br /&gt;
&lt;br /&gt;
For more security you can serve a 404 instead of a 403, and if you prefer you can reuse the themed 404 page below.&lt;br /&gt;
=== 403 in Apache ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
DirectoryIndex disabled&lt;br /&gt;
Options -Indexes&lt;br /&gt;
ErrorDocument 403 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex&lt;br /&gt;
=== 403 in Nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;If you want to transform all 403 errors into 404&#039;s then:&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php?code=404&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Note the &#039;=&#039; char above is important as it allows Moodle to reset the http headers to correctly serve the page in all cases.&lt;br /&gt;
== 404 File not found - Web server errors ==&lt;br /&gt;
These are for errors which are technically outside of moodle such a 404 file not found at the server level rather than a 404 served by moodle. If you want to theme these then the approach is the same as you normally would for anything non Moodle, configure your web server to show a custom error page. But instead of writing your own, there is a built in Moodle error handler php script so they get the normal Moodle theme applied. You can see an example of what this error page looks like here:&lt;br /&gt;
&lt;br /&gt;
https://moodle.org/errors/&lt;br /&gt;
=== 404 in Apache ===&lt;br /&gt;
You can configure this using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
ErrorDocument 404 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== 404 in nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
error_page 404 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Note the &#039;=&#039; char above is important as it allows Moodle to reset the http headers to correctly serve the page in all cases.&lt;br /&gt;
== 502 Bad Gateway - Proxy / CDN issues ==&lt;br /&gt;
This means some upstream of your moodle server could not connect, ie nginx, varnish, a load balancer, proxy or CDN.&lt;br /&gt;
&lt;br /&gt;
These error pages cannot be themed by at the application level and how to customize them depends on what is proxying moodle.&lt;br /&gt;
&lt;br /&gt;
NOTE: It is very important if you do customize these to make sure you are not overriding the 40x and 50x error pages that Moodle may serve.&lt;br /&gt;
== 503 Service unavailable - Fatal Moodle bootstrap errors ==&lt;br /&gt;
New in 4.0&lt;br /&gt;
&lt;br /&gt;
These types of errors are more fundamental, and something has gone wrong so Moodle itself can&#039;t load, which means the error page can&#039;t access the database, MUC, or the language and strings API.&lt;br /&gt;
&lt;br /&gt;
Previously these were difficult to style, and there are many edge cases to capture. This was fixed in this tracker:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-56041&lt;br /&gt;
&lt;br /&gt;
These low level error pages resulted in the &#039;yellow box&#039; and in 4.0 are now improved to more closely match the boost / bootstrap theme. You can customize this error page by editing this file:&lt;br /&gt;
&lt;br /&gt;
error/plainpage.php&lt;br /&gt;
&lt;br /&gt;
Note: this file cannot contain any Moodle API&#039;s and cannot link to files which are served by moodle such as theme files or plugin files. Ideally serve all dependencies such as css and even image inline.&lt;br /&gt;
== 503 Moodle under maintenance - Maintenance mode ==&lt;br /&gt;
During maintenance mode Moodle will serve a &amp;quot;503 Service Unavailable&amp;quot;. This page can be customized:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Maintenance_mode#CLI_maintenance_mode&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63879</id>
		<title>Error pages</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63879"/>
		<updated>2023-05-09T23:29:44Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Nginx */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is for any Moodle admins or developers who want to setup or customize error pages on their Moodle site. There is a variety of ways different errors can happen at different layers in a Moodle stack and because they each happen under different circumstance there cannot be a single way of handling them.&lt;br /&gt;
== 404 Normal Moodle errors and exceptions ==&lt;br /&gt;
These types of errors are things like you tried to access a course which no longer exists, or access a course which you don&#039;t have access to. Because these are &amp;quot;Moodle&amp;quot; pages they are themed correctly and normally there isn&#039;t much you want to do to improve them.&lt;br /&gt;
&lt;br /&gt;
If you want to change the wording of the errors see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/en/Language_customisation&lt;br /&gt;
&lt;br /&gt;
If you want to change the theme, that is one and the same as normal theme customization:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Themes_overview&lt;br /&gt;
&lt;br /&gt;
If a Moodle page throws an exception which isn&#039;t captured and results in an error page, and it will also helpfully, but blindly, link into this Moodle wiki where you might find more information on the error. This is great for sites in development or for admin type errors but typically less useful. You may want to divert users to your own support instead of off into a wiki page with generic Moodle information.&lt;br /&gt;
&lt;br /&gt;
This links behavior can be changed and removed in your admin settings here:&lt;br /&gt;
&lt;br /&gt;
/admin/settings.php?section=documentation&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Exceptions&lt;br /&gt;
&lt;br /&gt;
You can also simply hide the links using a capability too:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Linking_Moodle_and_Docs#Removing_Moodle_Docs_links_for_teachers&lt;br /&gt;
== 403 Forbidden - Web server errors ==&lt;br /&gt;
For additional security you should not allow access to directory listings and you can also block access to certain files.&lt;br /&gt;
&lt;br /&gt;
For more security you can serve a 404 instead of a 403, and if you prefer you can reuse the themed 404 page below.&lt;br /&gt;
=== Apache ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
DirectoryIndex disabled&lt;br /&gt;
Options -Indexes&lt;br /&gt;
ErrorDocument 403 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex&lt;br /&gt;
=== Nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;If you want to transform all 403 errors into 404&#039;s then:&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php?code=404&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Note the &#039;=&#039; char above is important as it allows Moodle to reset the http headers to correctly serve the page in all cases.&lt;br /&gt;
&lt;br /&gt;
== 404 File not found - Web server errors ==&lt;br /&gt;
These are for errors which are technically outside of moodle such a 404 file not found at the server level rather than a 404 served by moodle. If you want to theme these then the approach is the same as you normally would for anything non Moodle, configure your web server to show a custom error page. But instead of writing your own, there is a built in Moodle error handler php script so they get the normal Moodle theme applied. You can see an example of what this error page looks like here:&lt;br /&gt;
&lt;br /&gt;
https://moodle.org/errors/&lt;br /&gt;
=== Apache ===&lt;br /&gt;
You can configure this using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
ErrorDocument 404 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
error_page 404 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Note the &#039;=&#039; char above is important as it allows Moodle to reset the http headers to correctly serve the page in all cases.&lt;br /&gt;
&lt;br /&gt;
== 502 Bad Gateway - Proxy / CDN issues ==&lt;br /&gt;
This means some upstream of your moodle server could not connect, ie nginx, varnish, a load balancer, proxy or CDN.&lt;br /&gt;
&lt;br /&gt;
These error pages cannot be themed by at the application level and how to customize them depends on what is proxying moodle.&lt;br /&gt;
&lt;br /&gt;
NOTE: It is very important if you do customize these to make sure you are not overriding the 40x and 50x error pages that Moodle may serve.&lt;br /&gt;
== 503 Service unavailable - Fatal Moodle bootstrap errors ==&lt;br /&gt;
New in 4.0&lt;br /&gt;
&lt;br /&gt;
These types of errors are more fundamental, and something has gone wrong so Moodle itself can&#039;t load, which means the error page can&#039;t access the database, MUC, or the language and strings API.&lt;br /&gt;
&lt;br /&gt;
Previously these were difficult to style, and there are many edge cases to capture. This was fixed in this tracker:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-56041&lt;br /&gt;
&lt;br /&gt;
These low level error pages resulted in the &#039;yellow box&#039; and in 4.0 are now improved to more closely match the boost / bootstrap theme. You can customize this error page by editing this file:&lt;br /&gt;
&lt;br /&gt;
error/plainpage.php&lt;br /&gt;
&lt;br /&gt;
Note: this file cannot contain any Moodle API&#039;s and cannot link to files which are served by moodle such as theme files or plugin files. Ideally serve all dependencies such as css and even image inline.&lt;br /&gt;
== 503 Moodle under maintenance - Maintenance mode ==&lt;br /&gt;
During maintenance mode Moodle will serve a &amp;quot;503 Service Unavailable&amp;quot;. This page can be customized:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Maintenance_mode#CLI_maintenance_mode&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63878</id>
		<title>Error pages</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63878"/>
		<updated>2023-05-09T23:28:16Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* 403 Forbidden - Web server errors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is for any Moodle admins or developers who want to setup or customize error pages on their Moodle site. There is a variety of ways different errors can happen at different layers in a Moodle stack and because they each happen under different circumstance there cannot be a single way of handling them.&lt;br /&gt;
== 404 Normal Moodle errors and exceptions ==&lt;br /&gt;
These types of errors are things like you tried to access a course which no longer exists, or access a course which you don&#039;t have access to. Because these are &amp;quot;Moodle&amp;quot; pages they are themed correctly and normally there isn&#039;t much you want to do to improve them.&lt;br /&gt;
&lt;br /&gt;
If you want to change the wording of the errors see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/en/Language_customisation&lt;br /&gt;
&lt;br /&gt;
If you want to change the theme, that is one and the same as normal theme customization:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Themes_overview&lt;br /&gt;
&lt;br /&gt;
If a Moodle page throws an exception which isn&#039;t captured and results in an error page, and it will also helpfully, but blindly, link into this Moodle wiki where you might find more information on the error. This is great for sites in development or for admin type errors but typically less useful. You may want to divert users to your own support instead of off into a wiki page with generic Moodle information.&lt;br /&gt;
&lt;br /&gt;
This links behavior can be changed and removed in your admin settings here:&lt;br /&gt;
&lt;br /&gt;
/admin/settings.php?section=documentation&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Exceptions&lt;br /&gt;
&lt;br /&gt;
You can also simply hide the links using a capability too:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Linking_Moodle_and_Docs#Removing_Moodle_Docs_links_for_teachers&lt;br /&gt;
== 403 Forbidden - Web server errors ==&lt;br /&gt;
For additional security you should not allow access to directory listings and you can also block access to certain files.&lt;br /&gt;
&lt;br /&gt;
For more security you can serve a 404 instead of a 403, and if you prefer you can reuse the themed 404 page below.&lt;br /&gt;
=== Apache ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
DirectoryIndex disabled&lt;br /&gt;
Options -Indexes&lt;br /&gt;
ErrorDocument 403 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex&lt;br /&gt;
&lt;br /&gt;
=== Nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;If you want to transform all 403 errors into 404&#039;s then:&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
error_page 403 = /error/index.php?code=404&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 404 File not found - Web server errors ==&lt;br /&gt;
These are for errors which are technically outside of moodle such a 404 file not found at the server level rather than a 404 served by moodle. If you want to theme these then the approach is the same as you normally would for anything non Moodle, configure your web server to show a custom error page. But instead of writing your own, there is a built in Moodle error handler php script so they get the normal Moodle theme applied. You can see an example of what this error page looks like here:&lt;br /&gt;
&lt;br /&gt;
https://moodle.org/errors/&lt;br /&gt;
=== Apache ===&lt;br /&gt;
You can configure this using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
ErrorDocument 404 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
error_page 404 = /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== 502 Bad Gateway - Proxy / CDN issues ==&lt;br /&gt;
This means some upstream of your moodle server could not connect, ie nginx, varnish, a load balancer, proxy or CDN.&lt;br /&gt;
&lt;br /&gt;
These error pages cannot be themed by at the application level and how to customize them depends on what is proxying moodle.&lt;br /&gt;
&lt;br /&gt;
NOTE: It is very important if you do customize these to make sure you are not overriding the 40x and 50x error pages that Moodle may serve.&lt;br /&gt;
== 503 Service unavailable - Fatal Moodle bootstrap errors ==&lt;br /&gt;
New in 4.0&lt;br /&gt;
&lt;br /&gt;
These types of errors are more fundamental, and something has gone wrong so Moodle itself can&#039;t load, which means the error page can&#039;t access the database, MUC, or the language and strings API.&lt;br /&gt;
&lt;br /&gt;
Previously these were difficult to style, and there are many edge cases to capture. This was fixed in this tracker:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-56041&lt;br /&gt;
&lt;br /&gt;
These low level error pages resulted in the &#039;yellow box&#039; and in 4.0 are now improved to more closely match the boost / bootstrap theme. You can customize this error page by editing this file:&lt;br /&gt;
&lt;br /&gt;
error/plainpage.php&lt;br /&gt;
&lt;br /&gt;
Note: this file cannot contain any Moodle API&#039;s and cannot link to files which are served by moodle such as theme files or plugin files. Ideally serve all dependencies such as css and even image inline.&lt;br /&gt;
== 503 Moodle under maintenance - Maintenance mode ==&lt;br /&gt;
During maintenance mode Moodle will serve a &amp;quot;503 Service Unavailable&amp;quot;. This page can be customized:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Maintenance_mode#CLI_maintenance_mode&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63877</id>
		<title>Error pages</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=63877"/>
		<updated>2023-05-09T23:24:27Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* 404 File not found - Web server errors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is for any Moodle admins or developers who want to setup or customize error pages on their Moodle site. There is a variety of ways different errors can happen at different layers in a Moodle stack and because they each happen under different circumstance there cannot be a single way of handling them.&lt;br /&gt;
== 404 Normal Moodle errors and exceptions ==&lt;br /&gt;
These types of errors are things like you tried to access a course which no longer exists, or access a course which you don&#039;t have access to. Because these are &amp;quot;Moodle&amp;quot; pages they are themed correctly and normally there isn&#039;t much you want to do to improve them.&lt;br /&gt;
&lt;br /&gt;
If you want to change the wording of the errors see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/en/Language_customisation&lt;br /&gt;
&lt;br /&gt;
If you want to change the theme, that is one and the same as normal theme customization:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Themes_overview&lt;br /&gt;
&lt;br /&gt;
If a Moodle page throws an exception which isn&#039;t captured and results in an error page, and it will also helpfully, but blindly, link into this Moodle wiki where you might find more information on the error. This is great for sites in development or for admin type errors but typically less useful. You may want to divert users to your own support instead of off into a wiki page with generic Moodle information.&lt;br /&gt;
&lt;br /&gt;
This links behavior can be changed and removed in your admin settings here:&lt;br /&gt;
&lt;br /&gt;
/admin/settings.php?section=documentation&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Exceptions&lt;br /&gt;
&lt;br /&gt;
You can also simply hide the links using a capability too:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Linking_Moodle_and_Docs#Removing_Moodle_Docs_links_for_teachers&lt;br /&gt;
== 403 Forbidden - Web server errors ==&lt;br /&gt;
For additional security you should not allow access to directory listings and you can also block access to certain files.&lt;br /&gt;
&lt;br /&gt;
For more security you can serve a 404 instead of a 403, and if you prefer you can reuse the themed 404 page below.&lt;br /&gt;
=== Apache ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
DirectoryIndex disabled&lt;br /&gt;
Options -Indexes&lt;br /&gt;
ErrorDocument 403 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex&lt;br /&gt;
== 404 File not found - Web server errors ==&lt;br /&gt;
These are for errors which are technically outside of moodle such a 404 file not found at the server level rather than a 404 served by moodle. If you want to theme these then the approach is the same as you normally would for anything non Moodle, configure your web server to show a custom error page. But instead of writing your own, there is a built in Moodle error handler php script so they get the normal Moodle theme applied. You can see an example of what this error page looks like here:&lt;br /&gt;
&lt;br /&gt;
https://moodle.org/errors/&lt;br /&gt;
=== Apache ===&lt;br /&gt;
You can configure this using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
ErrorDocument 404 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
error_page 404 = /error/index.php;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== 502 Bad Gateway - Proxy / CDN issues ==&lt;br /&gt;
This means some upstream of your moodle server could not connect, ie nginx, varnish, a load balancer, proxy or CDN.&lt;br /&gt;
&lt;br /&gt;
These error pages cannot be themed by at the application level and how to customize them depends on what is proxying moodle.&lt;br /&gt;
&lt;br /&gt;
NOTE: It is very important if you do customize these to make sure you are not overriding the 40x and 50x error pages that Moodle may serve.&lt;br /&gt;
== 503 Service unavailable - Fatal Moodle bootstrap errors ==&lt;br /&gt;
New in 4.0&lt;br /&gt;
&lt;br /&gt;
These types of errors are more fundamental, and something has gone wrong so Moodle itself can&#039;t load, which means the error page can&#039;t access the database, MUC, or the language and strings API.&lt;br /&gt;
&lt;br /&gt;
Previously these were difficult to style, and there are many edge cases to capture. This was fixed in this tracker:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-56041&lt;br /&gt;
&lt;br /&gt;
These low level error pages resulted in the &#039;yellow box&#039; and in 4.0 are now improved to more closely match the boost / bootstrap theme. You can customize this error page by editing this file:&lt;br /&gt;
&lt;br /&gt;
error/plainpage.php&lt;br /&gt;
&lt;br /&gt;
Note: this file cannot contain any Moodle API&#039;s and cannot link to files which are served by moodle such as theme files or plugin files. Ideally serve all dependencies such as css and even image inline.&lt;br /&gt;
== 503 Moodle under maintenance - Maintenance mode ==&lt;br /&gt;
During maintenance mode Moodle will serve a &amp;quot;503 Service Unavailable&amp;quot;. This page can be customized:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Maintenance_mode#CLI_maintenance_mode&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improve_Annotation&amp;diff=63681</id>
		<title>Improve Annotation</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improve_Annotation&amp;diff=63681"/>
		<updated>2022-11-08T22:45:11Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|state = Proposal&lt;br /&gt;
|name = Improve Annotation&lt;br /&gt;
|tracker = https://tracker.moodle.org/browse/MDL-76243&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=422700&lt;br /&gt;
|assignee = Catalyst IT&lt;br /&gt;
}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
This is a proposal for improving Annotation in Moodle by [https://www.catalyst.net.nz Catalyst IT] - thanks to [https://www.xjtlu.edu.cn Xi’an Jiaotong-Liverpool University] for providing support with researching this project.&lt;br /&gt;
&lt;br /&gt;
The “uploadpdf” assignment feedback plugin allows teachers to annotate assignments during the grading process. We would like to improve the user experience for annotation by allowing the teacher to scroll through the pages like a normal document and implement an “Undo” feature, allowing a teacher to undo previous changes like the deletion of an object.&lt;br /&gt;
&lt;br /&gt;
The existing annotation feature in Moodle is built using the YUI Javascript library which has not been maintained by the original developers since 2014 and has been generally deprecated in Moodle (no new YUI code has been generally allowed in Moodle since 2.9).&lt;br /&gt;
&lt;br /&gt;
Moodle has also been improving the core grade API and recently in Moodle 3.8 added a new user interface within the forum activity for grading with the intention of using this same interface for the assignment grading interface but has not yet implemented this in assignment yet.&lt;br /&gt;
&lt;br /&gt;
Investing significant development effort on the existing annotation plugin would be unwise due to the existing use of YUI and we believe it would be more useful to develop a brand new annotation tool in Moodle.&lt;br /&gt;
&lt;br /&gt;
There is also potential for annotation of content to be useful outside the assignment activity – for example in essay questions, forum, workshop activities and 3rd party plugins like mod_annotatepdf so we propose that annotation functionality is added to the core Grade API so that it can be re-used anywhere in Moodle where grades are also used.&lt;br /&gt;
== External libraries ==&lt;br /&gt;
=== Rendering the PDF ===&lt;br /&gt;
Moodle uses a combination of libraries to convert a document to a pdf, then converts the pdf into an image per page to allow annotation and then stiches it all back into a pdf again so that it can be downloaded - this is complex, results in accessibility issues and bugs like MDL-64431.&lt;br /&gt;
&lt;br /&gt;
We propose that we that we render the PDF directly in the browser using Mozilla&#039;s [https://github.com/mozilla/pdf.js pdf.js], and then add an annotation UI on top which stores the annotations directly in the PDF.&lt;br /&gt;
=== Annotating the PDF ===&lt;br /&gt;
Ideally we wouldn&#039;t implement our own Annotation library like we have done in the existing plugin, however there doesn’t appear to be many well maintained open source annotation libraries that we can re-use in Moodle.&lt;br /&gt;
* https://github.com/highkite/pdfAnnotate – This seems to be the most promising, however it is in early development stage, stores annotations within the pdf itself, Supports the main components that uploadpdf tool currently supports except the “stamp” and doesn’t seem to have built-in undo/redo support. Doesn’t implement a UI, but Moodle would be providing this anyway. Other advantage of using a JS library with annotations stored within the document itself is that there is potential for this to be re-used within an offline mobile app. Using this library would also mean a decision to only support annotations on pdf documents which I think is ok. The developer behind this project has expressed an interest in helping us use this.&lt;br /&gt;
&lt;br /&gt;
*Apache Annotator https://annotator.apache.org/ - Incubating project - allows annotation on any html content which would be a nice idea to allow annotations directly on forum posts etc, however at this state the project only allows you to select text or css selectors – annoation in Moodle requires more flexibility – drawing/circles etc.&lt;br /&gt;
&lt;br /&gt;
* http://annotatorjs.org/ - Unmaintained since 2017hypothes.is – uses a python-based back-end to store annotations.&lt;br /&gt;
&lt;br /&gt;
* https://github.com/bhargavacc/pdf-annotation – unmaintained since 2016&lt;br /&gt;
&lt;br /&gt;
* https://github.com/instructure/pdf-annotate.js – unmaintained since 2018, requires a storage layer for annotations.&lt;br /&gt;
&lt;br /&gt;
* https://moodle.org/plugins/mod_pdfannotator - (3rd party Moodle plugin) Uses Mozilla&#039;s pdf.js already, uses https://github.com/instructure/pdf-annotate.js for performing annotations - doesn&#039;t contain undo/redo features.&lt;br /&gt;
== Prototype / mock-ups ==&lt;br /&gt;
See below for a mockup of a possible pdf annotation interface using the new grading UI from the forum activity.&lt;br /&gt;
&lt;br /&gt;
Note: Moodle’s existing interface provides the ability to rotate the image clockwise or anti-clockwise, and the addition of a new “undo” button may require some changes to help make it clear.&lt;br /&gt;
[[File:improveannotation-assign-mockup.png|Mockup of new grading interface]]&lt;br /&gt;
== Development tasks ==&lt;br /&gt;
# Implement Behat tests for the forum grading panel (https://tracker.moodle.org/browse/MDL-66666) to help prevent any regressions while reworking the grading UI.&lt;br /&gt;
# Re-work the Forum grading UI so that it is a core function that other activities can re-use.&lt;br /&gt;
# Create a new centralised annotation API initally using https://github.com/highkite/pdfAnnotate with a nice Moodle UI (images/buttons etc) but leaving room for the annotation API to be extended further in future with other interfaces like Apache annotator for html content. Initially this would only support in-browser editing with the full pdf being sent back to the server for storing.&lt;br /&gt;
# Create a new assignment feedback plugin that re-uses the new core grading UI (based on forum) and integrates the core annotation API &lt;br /&gt;
# Add support for “stamps” (custom images) within the pdfAnnotate library.&lt;br /&gt;
# Add support for undo process within the pdfAnnotate library and Moodle’s UI.&lt;br /&gt;
== Ideas for future development (outside initial scope) ==&lt;br /&gt;
* Some PDF files are quite large and sending the full edited pdf back to the server is in-efficient, the highkite library supports being run server side using nodejs - investigate using an optional server-side process to save annotations.&lt;br /&gt;
* Add support for annotations to other areas like essay quiz questions, Workshop activity etc.&lt;br /&gt;
* Add support for devices like the iPad Pencil.&lt;br /&gt;
* Add support to allow grading to occur offline (embedding the JS library within our Mobile app).&lt;br /&gt;
* Add support for Apache annotator for annotating html content.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=String_API&amp;diff=62409</id>
		<title>String API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=String_API&amp;diff=62409"/>
		<updated>2022-05-23T01:50:31Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Should the colon sign be hard-coded or included in a language string? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The String API is how you get language text strings to use in the user interface. It handles internationalisation issues, and will use a number of settings and environment variables to present the best text to every user. Moodle has a mechanism that allows a number of places to be searched (in order) to find language strings. This enables language strings to be packaged with plugins and avoids the step of having to copy the language files over to the language directory when a plugin is installed.&lt;br /&gt;
&lt;br /&gt;
Moodle also provide general string functions like substr, strlen etc. for multibyte safe, string operations. It uses mbstring or iconv for UTF-8 strings and falls back to typo3.&lt;br /&gt;
==Basic concepts==&lt;br /&gt;
When it is required to lookup a string, two basic items of information are required.&lt;br /&gt;
# The component providing the string.&lt;br /&gt;
# The identifier of the string.&lt;br /&gt;
Example: The function call &amp;lt;tt&amp;gt;get_string(&#039;editingquiz&#039;, &#039;mod_quiz&#039;)&amp;lt;/tt&amp;gt; will return &amp;quot;Editing quiz&amp;quot; if the current language is English, or relevant translation of the text. Here the string identifier is &amp;quot;editingquiz&amp;quot; and the string is provided by the &amp;quot;mod_quiz&amp;quot; component (that is the Quiz activity module).&lt;br /&gt;
===Adding language file to plugin===&lt;br /&gt;
Language support for plugin(s) is added by creating a &#039;&#039;&#039;lang/en/&#039;&#039;&#039; subdirectory in the plugin directory and putting the plugin&#039;s English string file there. The name of the string file is supposed to follow the plugin&#039;s [[Frankenstyle|component name]], so that it is something like &amp;lt;tt&amp;gt;plugintype_pluginname.php&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The default &amp;quot;en&amp;quot; language is the [[:en:Language FAQ#Which_is_the_official_language_for_Moodle.3F|Australian English]] which is same as the British English when it comes to spelling and grammar. American English is a separate language pack &amp;quot;en_us&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
There is no need to include languages other than the default English, as almost all approved [https://lang.moodle.org/mod/forum/discuss.php?d=2485 plugin strings will be automatically imported into AMOS] for translation by the language packs translators.&lt;br /&gt;
===Adding a string to the language file===&lt;br /&gt;
Strings are defined via the associative array &amp;lt;tt&amp;gt;$string&amp;lt;/tt&amp;gt; provided by the string file. The array key is the string identifier, the value is the string text in the given language.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;editingquiz&#039;] = &#039;Editing quiz&#039;;&lt;br /&gt;
$string[&#039;editingquiz_help&#039;] = &#039;Help for editing quiz&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Run-time parameters===&lt;br /&gt;
Strings can define placeholders like &amp;lt;tt&amp;gt;{$a}&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;{$a-&amp;gt;foobar}&amp;lt;/tt&amp;gt;. These placeholders are replaced with a value passed to the &amp;lt;tt&amp;gt;get_string()&amp;lt;/tt&amp;gt; function call.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// Strings defined in the language file.&lt;br /&gt;
$string[&#039;greeting&#039;] = &#039;Dear {$a}&#039;;&lt;br /&gt;
$string[&#039;info&#039;] = &#039;There are {$a-&amp;gt;count} new messages from {$a-&amp;gt;from}.&#039;;&lt;br /&gt;
&lt;br /&gt;
// Passing values for the placeholders.&lt;br /&gt;
echo get_string(&#039;greeting&#039;, &#039;tool_example&#039;, &#039;Mr. Anderson&#039;);&lt;br /&gt;
echo get_string(&#039;info&#039;, &#039;tool_example&#039;, [&#039;count&#039; =&amp;gt; 42, &#039;from&#039; =&amp;gt; &#039;Mr. Smith&#039;]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Help strings ==&lt;br /&gt;
Help strings are texts that are displayed in the help tooltips. There are certain conventions on how to define them.&lt;br /&gt;
[[Image:Roles help popup.png|thumb|Help popup containing &amp;quot;More help&amp;quot; link]] Help strings names start with the string name of the setting to which they apply, so that they are kept together in the language file.&lt;br /&gt;
* &#039;&#039;stringname&#039;&#039; - identifier of the string we provide the help for, may be used as a title of the help popup&lt;br /&gt;
* &#039;&#039;stringname_help&#039;&#039; - string with the help text&lt;br /&gt;
* &#039;&#039;stringname_link&#039;&#039; - an optional string naming a path in Moodle Docs, only if required, calculated just like the link in the footer to go to the right language etc, and shown after the help text in the popup as a link with an icon to &amp;quot;More help...&amp;quot;&lt;br /&gt;
* &#039;&#039;stringname_desc&#039;&#039; - an admin setting description replacing the legacy &#039;&#039;configstringname&#039;&#039;&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;submissionsize&#039;] = &#039;Submission size&#039;;&lt;br /&gt;
$string[&#039;submissionsize_help&#039;] = &#039;Blah blah blah some useful text, optionally using Markdown syntax&#039;;&lt;br /&gt;
$string[&#039;submissionsize_link&#039;] = &#039;mod/workshop/submission&#039;;&lt;br /&gt;
$string[&#039;submissionsize_desc&#039;] = &#039;Default value for submission size. This value will be pre-filled and can be blah blah ...&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Help string text should be short and simple - two small paragraphs maximum with no headings, no links, no bold, no tables etc.&lt;br /&gt;
&lt;br /&gt;
[[:en:Markdown|Markdown]] can be used for some basic formatting of strings with the &amp;quot;_help&amp;quot; suffix. It is recommended to stick to paragraphs and lists only. Markdown can only be used for help strings, not for other language strings.&lt;br /&gt;
=== More help links ===&lt;br /&gt;
To display the optional &#039;&#039;More help...&#039;&#039; link at the bottom of the help popup, a string with the &amp;quot;_link&amp;quot; suffix must exist beside the relevant &amp;quot;_help&amp;quot; string. These link strings should consist of a short relative path like &#039;component/thing&#039; (eg. &#039;mod/folder/view&#039; or &#039;group/import&#039;). This gets turned into a link to MoodleDocs in the user&#039;s language, and for the appropriate Moodle version.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;groupimport_link&#039;] = &#039;group/import&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
will make a link to something like &amp;lt;nowiki&amp;gt;&#039;https://docs.moodle.org/32/en/group/import&#039;&amp;lt;/nowiki&amp;gt; (if you are an English user of Moodle 3.2). This page in turn should be a redirect page to the actual page providing the info. Note the &amp;lt;nowiki&amp;gt;&#039;https://docs.moodle.org&#039;&amp;lt;/nowiki&amp;gt; part comes from $CFG-&amp;gt;docroot.&lt;br /&gt;
&lt;br /&gt;
The second option is that you can give an absolute link, if you want, and that will be used unmodified.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;patronising_link&#039;] = &amp;lt;nowiki&amp;gt;&#039;http://lmgtfy.com/?q=Moodle+for+dummies&#039;;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This case is detected by seeing if the string starts with &amp;lt;nowiki&amp;gt;http:// or https://&amp;lt;/nowiki&amp;gt;. This option is useful if the documentation for your plugin is hosted at Github wiki, for example.&lt;br /&gt;
&lt;br /&gt;
The third option is to start the link with &amp;lt;tt&amp;gt;%%WWWROOT%%&amp;lt;/tt&amp;gt;. That is replaced by &amp;lt;tt&amp;gt;$CFG-&amp;gt;wwwroot&amp;lt;/tt&amp;gt;. This is useful for contributed plugins that want to keep the help within the plugin.&lt;br /&gt;
=== Words for roles ===&lt;br /&gt;
The following words should be used in the English (en) help strings whenever it is necessary to refer to people with particular roles in a generic sense.&lt;br /&gt;
* Participants - all people with a role&lt;br /&gt;
* Teachers - people with some sort of a facilitation/editing role&lt;br /&gt;
* Students - people primarily there to learn&lt;br /&gt;
* Users - people across the site&lt;br /&gt;
([[User:Martin Dougiamas|Martin Dougiamas]] 16:13, 20 April 2010 (UTC): I&#039;m not entirely comfortable with this choice even though I just re-confirmed it, as I&#039;d prefer to be using words like facilitator/moderator to subtly and continually promote a less transmissionist pedgagogy and I know many Moodle users would agree. However, I think these terms are far more commonly used in the community and in discussion, and from the point of view that we need to build a consistent system for usability they are less distracting and better for documentation.)&lt;br /&gt;
==Files==&lt;br /&gt;
String functions are defined in &lt;br /&gt;
# lib/moodlelib.php - locale related functions&lt;br /&gt;
# lib/textlib.class.php - general string functions (substr, strlen etc.)&lt;br /&gt;
==Functions and examples==&lt;br /&gt;
There are three main functions, used in moodle for getting/displaying the localised string, based on user preferred language.&lt;br /&gt;
===get_string()===&lt;br /&gt;
Returns a localised string for current user.&lt;br /&gt;
&lt;br /&gt;
Example for displaying string &amp;quot;This is my plug-in&amp;quot; in any language that supports on your site, then you need to use the following identifier in language file (located in appropriate lang directory)&lt;br /&gt;
   $string[&#039;plugintitle&#039;] = &#039;This is my plug-in&#039;;&lt;br /&gt;
If you want to display this string in any supported language, then you would use this function.&lt;br /&gt;
   echo get_string(&#039;plugintitle&#039;, &#039;module_pluginlangfilename&#039;);&lt;br /&gt;
If you want to substitute value in the language string then use &#039;&#039;&#039;{$a}&#039;&#039;&#039; for substituting value. $a is an object, string or number that can be used within translation strings. The variable has to be $a, and it has to be in single quotes. For example, if you want to display answer number in Drag &amp;amp; drop plugin then add&lt;br /&gt;
   $string[&#039;answerno1&#039;] = &#039;Answer {$a}&#039;; //Substituting string/integer&lt;br /&gt;
   $string[&#039;answerno2&#039;] = &#039;Answer {$a-&amp;gt;name}&#039;; //Substituting object member&lt;br /&gt;
in qtype_dragdrop.php (Drag &amp;amp; Drop language file). And call it using&lt;br /&gt;
   //Get string by substituting integer. &lt;br /&gt;
   get_string(&#039;answerno1&#039;, &#039;qtype_dragdrop&#039;, $number);&lt;br /&gt;
   //Get string by substituting object member integer&lt;br /&gt;
   $user-&amp;gt;number = 10;&lt;br /&gt;
   get_string(&#039;answerno2&#039;, &#039;qtype_dragdrop&#039;, $user);&lt;br /&gt;
In Moodle 2.3 there is a new argument to this function $lazyload. Setting $lazyload to true causes get_string to return a lang_string object rather than the string itself. &lt;br /&gt;
   $stringobject = get_string(&#039;answerno&#039;, &#039;qtype_dragdrop&#039;, $number, true);&lt;br /&gt;
The fetching of the string is then put off until the string object is first used. The object can be used by calling it&#039;s out method or by casting the object to a string, either directly e.g. &lt;br /&gt;
   (string)$stringobject &lt;br /&gt;
or indirectly by using the string within another string or echoing it out e.g.&lt;br /&gt;
   echo $stringobject;&lt;br /&gt;
return &amp;quot;&amp;lt;p&amp;gt;{$stringobject}&amp;lt;/p&amp;gt;&amp;quot;;&lt;br /&gt;
Note: using $lazyload and attempting to use the string as an array key will cause a fatal error as objects cannot be used as array keys.&lt;br /&gt;
===get_strings()===&lt;br /&gt;
Converts an array of string names to localised strings for a specific plugin. lazy loading is not supported in this function.&lt;br /&gt;
   $txt = get_strings(array(&#039;enable&#039;, &#039;disable&#039;, &#039;up&#039;, &#039;down&#039;, &#039;none&#039;), &#039;qtype_dragdrop&#039;);&lt;br /&gt;
   echo $txt-&amp;gt;up;  //Display localised string for up&lt;br /&gt;
   echo $txt-&amp;gt;down //Display localised string for down&lt;br /&gt;
===print_string()===&lt;br /&gt;
Prints out a translated string by using &#039;&#039;&#039;get_string()&#039;&#039;&#039; function&lt;br /&gt;
===lang_string class===&lt;br /&gt;
In Moodle 2.3 a special class (lang_string) is used to create an object representation of a string request. In this case string processing doesn&#039;t occur until the object is first used. The class was created especially to aid performance in areas where strings were required to be generated but were not necessarily used. As an example the admin navigation tree when generated uses over 1500 strings, of which normally only 1/3 are ever actually printed at any time. The performance advantage is achieved by not actually processing strings that aren&#039;t being used, as such reducing the processing required for the page.&lt;br /&gt;
&lt;br /&gt;
lang_string class can be used in two ways&lt;br /&gt;
# Setting $lazyload (fourth argument of the get_string function), to true.&lt;br /&gt;
   $string = get_string(&#039;yes&#039;, &#039;qtype_dragdrop&#039;, null, true);&lt;br /&gt;
# Direct instantiation&lt;br /&gt;
   $string = new lang_string(&#039;yes&#039;, &#039;qtype_dragdrop&#039;, null, &#039;en&#039;);&lt;br /&gt;
== String manager ==&lt;br /&gt;
Most of function listed above are just handful wrappers for the methods provided by the string manager class. Some strings related functionality is available only via directly calling the manager&#039;s methods.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$stringman = get_string_manager();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The factory function get_string_manager() returns singleton instance of &amp;lt;tt&amp;gt;core_string_manager_install&amp;lt;/tt&amp;gt; in early stages of the site installation, or instance of &amp;lt;tt&amp;gt;core_string_manager_standard&amp;lt;/tt&amp;gt; in all normal situations. These managers implement interface &amp;lt;tt&amp;gt;core_string_manager&amp;lt;/tt&amp;gt;. Methods provided by the interface are&lt;br /&gt;
;get_string():Returns the given component string localised in the given language. Can be used to obtain the translation of the string in the other language than the current user&#039;s language.&lt;br /&gt;
;string_exists():Does the given string actually exist? This is typically checked by a code like&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
if (get_string_manager()-&amp;gt;string_exists(&#039;stringidentifier&#039;, &#039;component_name&#039;)) {&lt;br /&gt;
    // Do something.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;string_deprecated():Has the string been deprecated? See [[String deprecation]] for details.&lt;br /&gt;
;get_list_of_countries():Returns a localised list of all country names, sorted by country keys.&lt;br /&gt;
;get_list_of_languages():Returns a localised list of languages, sorted by code keys.&lt;br /&gt;
;translation_exists():Checks if the translation exists for the given language.&lt;br /&gt;
;get_list_of_translations():Returns localised list of installed language packs.&lt;br /&gt;
;get_list_of_currencies():Returns localised list of known currencies.&lt;br /&gt;
;load_component_strings():Loads all strings for one component.&lt;br /&gt;
;reset_caches():Invalidates all caches, should the manager use some.&lt;br /&gt;
;get_revision():Returns string revision counter.&lt;br /&gt;
=== Custom string managers ===&lt;br /&gt;
{{Moodle 2.9}}Plugins can provide custom implementation of the string manager. This is supposed to be used in experimental and/or development scenarios only, not in typical production environment. See MDL-49361 for use cases and implementation details. An example of such an implementation can be found at [https://github.com/mudrd8mz/moodle-local_stringman moodle-local_stringman.git] repository.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// Custom string manager class can be defined in the main config.php file.&lt;br /&gt;
$CFG-&amp;gt;customstringmanager = &#039;\local_stringman\dummy_string_manager&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==textlib (core_text) class==&lt;br /&gt;
textlib class provide pool of safe functions to operate on UTF-8 text. textlib provide set of static functions to operate on strings and gets included in setup.php&lt;br /&gt;
&lt;br /&gt;
{{Moodle 2.6}}In Moodle 2.6 the textlib class was renamed to &#039;&#039;&#039;core_text&#039;&#039;&#039;.&lt;br /&gt;
===asort()===&lt;br /&gt;
Locale aware sorting, the key associations are kept, values are sorted alphabetically.&lt;br /&gt;
===code2utf8===&lt;br /&gt;
Returns the utf8 string corresponding to the unicode value&lt;br /&gt;
===convert===&lt;br /&gt;
Converts the text between different encodings. It uses iconv extension with //TRANSLIT parameter, fall back to typo3&lt;br /&gt;
===encode_mimeheader===&lt;br /&gt;
Generate a correct base64 encoded header to be used in MIME mail messages.&lt;br /&gt;
===entities_to_utf8===&lt;br /&gt;
Converts all the numeric entities &amp;amp;#nnnn; or &amp;amp;#xnnn; to UTF-8&lt;br /&gt;
===specialtoascii===&lt;br /&gt;
Converts upper unicode characters to plain ascii, the returned string may contain unconverted unicode characters.&lt;br /&gt;
===strlen===&lt;br /&gt;
Multibyte safe strlen() function, uses iconv for utf-8, falls back to typo3.&lt;br /&gt;
===strpos===&lt;br /&gt;
Find the position of the first occurrence of a substring in a string. UTF-8 ONLY safe strpos(), uses iconv.&lt;br /&gt;
===strrpos===&lt;br /&gt;
Find the position of the last occurrence of a substring in a string. UTF-8 ONLY safe strrpos(), uses iconv.&lt;br /&gt;
===strtolower===&lt;br /&gt;
Multibyte safe strtolower() function, uses mbstring, falls back to typo3.&lt;br /&gt;
===strtotitle===&lt;br /&gt;
Makes first letter of each word capital - words must be separated by spaces.&lt;br /&gt;
===strtoupper===&lt;br /&gt;
Multibyte safe strtoupper() function, uses mbstring, falls back to typo3.&lt;br /&gt;
===substr===&lt;br /&gt;
Multibyte safe substr() function, uses iconv for utf-8, falls back to typo3.&lt;br /&gt;
===trim_utf8_bom===&lt;br /&gt;
Removes the BOM from unicode string. [http://unicode.org/faq/utf_bom.html more info]&lt;br /&gt;
===utf8_to_entities===&lt;br /&gt;
Converts all Unicode chars &amp;gt; 127 to numeric entities &amp;amp;#nnnn; or &amp;amp;#xnnn;&lt;br /&gt;
==FAQ==&lt;br /&gt;
===When should I use a lang_string object?===&lt;br /&gt;
The lang_string object is designed to be used in any situation where a string may not be needed, but needs to be generated. The admin navigation tree is a good example of where lang_string objects should be used. A more practical example would be any class that requires strings that may not be printed (after all classes get renderer by renderers and who knows what they will do ;))&lt;br /&gt;
===When should I not use a lang_string object?===&lt;br /&gt;
Don&#039;t use lang_strings when you are going to use a string immediately. There is no need as it will be processed immediately and there will be no advantage, and in fact perhaps a negative hit as a class has to be instantiated for a lang_string object, however get_string won&#039;t require that.&lt;br /&gt;
===Limitation of lang_string===&lt;br /&gt;
lang_string object cannot be used as an array offset. Doing so will result in PHP throwing an error. (You can use it as an object property!)&lt;br /&gt;
===How to compare strings properties in two object===&lt;br /&gt;
collatorlib_property_comparison class can be used to compare properties of two objects&lt;br /&gt;
===Should the colon sign be hard-coded or included in a language string?===&lt;br /&gt;
The colon sign should not be hard-coded because languages may use a different symbol, or have a space before or after the colon sign. Instead, the colon sign may be included in a language string, though please consider first if it is really necessary. A colon sign in a field label is not recommended. (MDL-12192 is for removing existing hard-coded colon signs.)&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// Do NOT hard code any punctuation:&lt;br /&gt;
$string[&#039;fieldname&#039;] = &#039;Name&#039;;&lt;br /&gt;
&lt;br /&gt;
echo get_string(&#039;fieldname&#039;, &#039;mod_foobar&#039;) . &#039;: &#039; . $somevalue;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Slightly better with no hard coded colon:&lt;br /&gt;
$string[&#039;fieldname&#039;] = &#039;Name: &#039;;&lt;br /&gt;
&lt;br /&gt;
echo get_string(&#039;fieldname&#039;, &#039;mod_foobar&#039;) . $somevalue;&lt;br /&gt;
&lt;br /&gt;
// Better with no assumed word order:&lt;br /&gt;
$string[&#039;fieldname&#039;] = &#039;Name: {$a-&amp;gt;name}&#039;;&lt;br /&gt;
&lt;br /&gt;
echo get_string(&#039;fieldname&#039;, &#039;mod_foobar&#039;, [&#039;name&#039; =&amp;gt; $somevalue]);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Changing or creating a new lang string?===&lt;br /&gt;
New strings were required in 1.x times when we did not have branches for translations. Since 2.0, for changes in master, structural and/or semantic modification of a string (such as adding or removing {$a} placeholders etc) is acceptable as such strings are highlighted in AMOS as outdated. Of course, if lang pack maintainers do not pay attention to it, mismatch can happen - but this is considered to be the lang pack bug.&lt;br /&gt;
&lt;br /&gt;
When a new string is introduced, the old one should be [[String deprecation | deprecated or removed]] so that unused strings do not accumulate.&lt;br /&gt;
:A) Basically I see it as (for master only issues):&lt;br /&gt;
:&lt;br /&gt;
:# Any change is allowed (semantic or structural). It does not have sense to keep the old string because there won&#039;t be use for it when the new Moodle version is released. So we can forget about the old one 100%. Of course if the string is radically different, then it may have sense to create a new, completely different string (and proceed with next point).&lt;br /&gt;
:# If for any reason we stop using any string (because a feature is out or because we have decided to create a new different string instead of change existing)), then:&lt;br /&gt;
:#* if the old string is really specific (not suitable for reuse) we can safely proceed to delete it,.&lt;br /&gt;
:#* If the string belongs to a plugin, IMO we can also proceed to delete it (reuse of plugin strings should not be allowed).&lt;br /&gt;
:#* But, if the string is generic (may be reused) and it&#039;s not part of a plugin then the string must be deprecated.&lt;br /&gt;
&lt;br /&gt;
:B) And, for issues involving stables, only small semantic changes are allowed. No structural, no deprecation and no deletion. Ever. If still something different must be shown it will be, always, via new string. When applied to master, these changes will also imply the [[String deprecation | standard old string deprecation]]. No CPY instruction will be performed between the old and the new strings (safest, easier, consistent behavior, no matter there are some cases where the instruction may be acceptable. Stop overthinking. Now!).&lt;br /&gt;
===User names placeholders===&lt;br /&gt;
Some strings may contain placeholders to display the user&#039;s name. Common examples include various email templates or welcome messages. Developers may be tempted to use just the user&#039;s firstname in such cases, to make the text feel informal and friendlier:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// Do NOT do this.&lt;br /&gt;
$string[&#039;welcome&#039;] = &#039;Hello {$a}! Welcome to the course.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
But some cultures or institutions may want the full name or last name to be used, rather than the first name. To enable sites to easily customise such strings and have lastname and alternatename placeholders available, the following pattern should be used.&lt;br /&gt;
&lt;br /&gt;
Let the language strings file for the given component define the default display of the name:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;welcome&#039;] = &#039;Hello {$a-&amp;gt;firstname}! Welcome to the course.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the file making use of the string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$namefields = [&lt;br /&gt;
    &#039;fullname&#039; =&amp;gt; fullname($USER),&lt;br /&gt;
    &#039;alternativefullname&#039; =&amp;gt; fullname($USER, true),&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
foreach (\core_user\fields::get_name_fields() as $namefield) {&lt;br /&gt;
    $namefields[$namefield] = $USER-&amp;gt;{$namefield};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
echo get_string(&#039;welcome&#039;, &#039;xyz_component&#039;, $namefields);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
That way, the string can be locally customised and use any combination of the following placeholders as needed: firstnamephonetic, lastnamephonetic, middlename, alternatename, firstname, lastname, fullname and alternativefullname.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// Locally customised variant of the string.&lt;br /&gt;
$string[&#039;welcome&#039;] = &#039;Welcome to the course, {$a-&amp;gt;fullname}&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Output_functions]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [[AMOS]]&lt;br /&gt;
* The section &#039;Language strings&#039; in [[Coding style]]&lt;br /&gt;
* [[String deprecation]]&lt;br /&gt;
* [[Improving English language strings]]&lt;br /&gt;
[[Category:API]]&lt;br /&gt;
[[Category:Language]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Check_API&amp;diff=62379</id>
		<title>Check API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Check_API&amp;diff=62379"/>
		<updated>2022-05-11T03:07:30Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* The result summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
{{Moodle 3.9}}&lt;br /&gt;
== Checks ==&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-67818&lt;br /&gt;
&lt;br /&gt;
A Check is a runtime test to make sure something is working well. You can think of Checks as similar and complimentary to the [[PHPUnit]] and [[Acceptance_testing]] but the next layer around them, and done at run time rather than dev time or build time. Like other forms of testing the tests themselves should easy to read and reason about and confirm as valid. As many types of runtime checks can&#039;t be unit tested, the checks &#039;&#039;&#039;are&#039;&#039;&#039; the test.&lt;br /&gt;
&lt;br /&gt;
Checks can be used for a variety of purposes including:&lt;br /&gt;
* configuration checks&lt;br /&gt;
* security checks&lt;br /&gt;
* status checks&lt;br /&gt;
* performance checks&lt;br /&gt;
* health checks&lt;br /&gt;
Moodle has had various types of checks and reports for a long time but they were inconsistent and not machine readable. In Moodle 3.9 they were unified under a single Check API which also enabled plugins to cleanly define their own additional checks. Some examples include:&lt;br /&gt;
* a password policy plugin could add a security check&lt;br /&gt;
* a custom authentication plugin can add a check that the upstream identity system can be connected to&lt;br /&gt;
* a MUC store plugin could add a performance check&lt;br /&gt;
Having these centralized and with a consistent contract makes it much easier to ensure the whole system is running smoothly. This makes it possible for an external integration with monitoring systems such as Nagios / Icinga. The Check API is expose as an NPRE compliance cli script:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
php admin/cli/checks.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Result states of a check ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Status&lt;br /&gt;
! Meaning&lt;br /&gt;
! Example&lt;br /&gt;
|-&lt;br /&gt;
| N/A&lt;br /&gt;
| This check doesn&#039;t apply - but we may still want to expose the check&lt;br /&gt;
| secure cookies setting is disabled because site is not https&lt;br /&gt;
|-&lt;br /&gt;
| Ok&lt;br /&gt;
| A component is configured, working and fast.&lt;br /&gt;
| ldap can bind and return value with low latency&lt;br /&gt;
|-&lt;br /&gt;
| Info&lt;br /&gt;
| A component is OK, and we may want to alert the admin to something non urgent such as a deprecation, or something which needs to be checked manually.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Unknown&lt;br /&gt;
| We don&#039;t yet know the state. eg it may be very expensive so it is run using the Task API and we are waiting for the answer. NOTE: unknown is generally a bad thing and is semantically treated as an error. It is better to have a result of Unknown until the first result happens, and from then on it is Ok, or perhaps Warning or Error if the last known result is getting stale. If you are caching or showing a stale result you should expose the time of this in the result summary text.&lt;br /&gt;
| A complex user security report is still running for the first time.&lt;br /&gt;
|-&lt;br /&gt;
| Warning&lt;br /&gt;
| Something is not ideal and should be addressed, eg usability or the speed of the site may be affected, but it may self heal (eg a load spike)&lt;br /&gt;
| auth_ldap could bind but was slower than normal&lt;br /&gt;
|-&lt;br /&gt;
| Error&lt;br /&gt;
| Something is wrong with a component and a feature is not working&lt;br /&gt;
| auth_ldap could not connect, so users cannot start new sessions&lt;br /&gt;
|-&lt;br /&gt;
| Critical&lt;br /&gt;
| An error which is affecting everyone in a major way&lt;br /&gt;
| Cannot read sitedata or the database, the whole site is down&lt;br /&gt;
|}&lt;br /&gt;
How the various states are then leveraged is a local decision. A typical policy might be that health checks with a status of &#039;Error&#039; or &#039;Critical&#039; will page a system administrator 24/7, while &#039;Warning&#039; only pages during business hours.&lt;br /&gt;
== Check types and reports ==&lt;br /&gt;
Checks are broken down into types, which roughly map to a step life cycle of your Moodle System&lt;br /&gt;
=== Environmental checks (?) ===&lt;br /&gt;
/admin/environment.php&lt;br /&gt;
&lt;br /&gt;
These are environmental checks to make sure a Moodle instance is fully setup. This page is potential candidate to move to the new Check API but it slightly more complex than the other checks so hasn&#039;t been tackled yet. It would be a deeper change and this is intrinsically part of the install and upgrade system. It is not as critical to refactor as it is already possible for a plugin to declare its own checks, via either declarative [[Environment_checking]] or programmatically with a custom check: &lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Environment_checking#Custom_checks&lt;br /&gt;
=== Configuration checks (?) ===&lt;br /&gt;
/admin/index.php?cache=1&lt;br /&gt;
&lt;br /&gt;
The Admin notifications page performs a mixture of checks, including security, status, and performance but none are as exhaustive as the checks in the reports below. It also does checks such as whether the web services for the Moodle Mobile App are turned on, and whether the site has been registered. Long term this page could show a summary of all the other check reports, or perhaps just the checks which are not in an OK state.&lt;br /&gt;
=== Security checks (security) ===&lt;br /&gt;
These are checks to make sure a Moodle instance is hardened correctly for you needs.&lt;br /&gt;
&lt;br /&gt;
/report/security/index.php&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-67776&lt;br /&gt;
=== Status checks (status) ===&lt;br /&gt;
/report/status/index.php&lt;br /&gt;
&lt;br /&gt;
A status check is an &#039;in the moment&#039; test and covers operational tests such as &#039;can moodle connect to ldap&#039;. The main core status checks are that cron is running regularly and there has been no failed tasks.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT:&#039;&#039;&#039; It is critical to understand that Status checks are conceptually defined at the level off the application and not at a lower host level such as a docker container or node in a cluster. Checks should be defined so that whichever instance you ask you should get a consistent answer. DO NOT use the Status Checks to detect containers which need reaped and restarted. If you do, any status error will mean all containers will simultaneously be marked for reaping.&lt;br /&gt;
&lt;br /&gt;
An additional status check is likely the most common type of check a plugin would define. Especially a plugin that connects to a 3rd party service. If the concept of &#039;OK&#039; requires some sort of threshold, eg network response within 500ms, then that threshold should be managed by the plugin and optionally exposed as a admin setting. The plugin may choose to have different thresholds for Warning / Error / Critical. When designing a new Status Check be mindful that it needs to be actionable, for instance if you are asserting that a remote domain is available and it goes down, which then alerts your infrastructure team, there isn&#039;t much they can do about it if it isn&#039;t their domain. If it is borderline then make things like this configurable so that each site has to option of tune their own policies of what should be considered an issue or not.&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-47271&lt;br /&gt;
=== Performance checks (performance) ===&lt;br /&gt;
/report/performance/index.php&lt;br /&gt;
&lt;br /&gt;
Each check might simply check for certain settings which are known to slow things down, or it might actually do some sort of test like multiple reads and writes to the db or filesystem to get a performance metric.&lt;br /&gt;
=== Health checks (health) ===&lt;br /&gt;
/admin/tool/health/&lt;br /&gt;
&lt;br /&gt;
The &#039;health center&#039; is currently unsupported and contains some very old code. It is conceptually similar to &#039;status&#039; checks except larger more long terms issues, such as detecting corrupt records. Ideally it is improved and converted to the Check API, see https://tracker.moodle.org/browse/MDL-67228&lt;br /&gt;
== Implementing a new check ==&lt;br /&gt;
=== A check class ===&lt;br /&gt;
And make a new check class in mod/myplugin/classes/check/foobar.php and the only mandatory method is get_result(). By default it will use a set language string but you can override the get_name() method to reuse an existing string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;checkfoobar&#039;] = &#039;Check the foos to make sure they are barred&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
namespace mod_myplugin\check;&lt;br /&gt;
use core\check\check;&lt;br /&gt;
 &lt;br /&gt;
class foobar extends check {&lt;br /&gt;
&lt;br /&gt;
    public function get_action_link(): ?\action_link {&lt;br /&gt;
        $url = new \moodle_url(&#039;/mod/myplugin/dosomething.php&#039;),&lt;br /&gt;
        return new \action_link($url, get_string(&#039;sitepolicies&#039;, &#039;admin&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_result() : result {&lt;br /&gt;
        if (some_check()) {&lt;br /&gt;
            $status = result::ERROR;&lt;br /&gt;
            $summary = get_string(&#039;check_foobar_error&#039;, &#039;mod_myplugin&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $status = result::OK;&lt;br /&gt;
            $summary = get_string(&#039;check_foobar_ok&#039;, &#039;mod_myplugin&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        $details = get_string(&#039;check_details&#039;, &#039;mod_myplugin&#039;);&lt;br /&gt;
        return new result($status, $summary, $details);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== The result summary ===&lt;br /&gt;
The summary could change depending on the result of the check but for a simple check might be a fixed string, not html. Try to keep the summary to 1 line as this might typically be the thing which gets passed through to a paging system and could be truncated.&lt;br /&gt;
&lt;br /&gt;
=== The details ===&lt;br /&gt;
Unlike the summary the details is allowed and encouraged to be html. Often it will be a bullet list of a table of the things that it asserted which make up the check.&lt;br /&gt;
&lt;br /&gt;
=== The action link ===&lt;br /&gt;
The action link is the place to go to help fix the issue. It should be as specific as possibly, such as a deep link into an admin settings page, and can include hash anchors.&lt;br /&gt;
=== lib.php callback ===&lt;br /&gt;
First decide if and when your new check(s) should be shown. Should it be present if your plugin is disabled? If you do not want it show if disabled then do not return it in callback below. If you do want it to show when disabled, but the check doesn&#039;t much sense then you can return a value of NA.&lt;br /&gt;
&lt;br /&gt;
Next decide on what type of check it should be which determines what report it will be included in. Some checks might make sense to be reused with more than one report, eg it could be in both Status and Performance.&lt;br /&gt;
&lt;br /&gt;
Implement the right callback in lib.php for the report you want to add it to, and return an array (usually with only 1 item) of check objects:&lt;br /&gt;
&lt;br /&gt;
/mod/myplugin/lib.php&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function mod_myplugin_security_checks() {&lt;br /&gt;
    return [new mod_myplugin\check\foobar()];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Multiple instances of checks ===&lt;br /&gt;
Checks have been designed to be dynamic so you can return different checks depending on configuration, so auth_ldap would not return a check if the plugin is not enabled. Hypothetically if auth_ldap could be configured with 5 ldap servers then you could return 5 independent checks for each remote connection, each with different labels and information.&lt;br /&gt;
&lt;br /&gt;
If you plan to return multiple instances of a check class, make sure that each instance has a unique id.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function mod_myplugin_security_checks() {&lt;br /&gt;
    return [&lt;br /&gt;
        new mod_myplugin\check\foobar(&#039;one&#039;),&lt;br /&gt;
        new mod_myplugin\check\foobar(&#039;two&#039;),&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Set the internal id in a way which is unique across all instances in your components namespace:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php&lt;br /&gt;
namespace mod_myplugin\check;&lt;br /&gt;
use core\check\check;&lt;br /&gt;
 &lt;br /&gt;
class foobar extends check {&lt;br /&gt;
    protected $id = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($id) {&lt;br /&gt;
        $this-&amp;gt;id = &#039;foobar&#039; . $id;&lt;br /&gt;
    }&lt;br /&gt;
    public function get_id(): string {&lt;br /&gt;
        return $this-&amp;gt;id;&lt;br /&gt;
    }&lt;br /&gt;
    ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Make checks as fast as practical ===&lt;br /&gt;
As many checks will be run and compiled into a report we want the checks themselves to be simple and as fast as possible. For instance an auth_ldap check while authenticating an end user could have a timeout of 60 seconds, and the check could warn if it takes more than 2 seconds. But the check could have a hard timeout of say 5 seconds and have a result status of ERROR for 5 or more seconds.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Lazy loading expensive result details ===&lt;br /&gt;
Checks can provide details on a check, such as the complete list of bad records. Generally this type of information might be expensive to produce so you can defer this lookup until get_details() is called specifically rather than setting this in the constructor. It will only be loaded on demand and shown when you drill down into the check details page.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
namespace mod_myplugin\check;&lt;br /&gt;
use core\check\check;&lt;br /&gt;
 &lt;br /&gt;
class foobar extends check {&lt;br /&gt;
    public function get_result(): result {&lt;br /&gt;
        return new foobar_result();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
class foobar_result extends \core\check\result {&lt;br /&gt;
    ...&lt;br /&gt;
    public function get_details(): string {&lt;br /&gt;
        // Do expensive lookups in here.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
For a real example see:&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/lib/classes/check/access/riskxss_result.php&lt;br /&gt;
=== Asynchronous checks ===&lt;br /&gt;
Some checks are by their nature asynchronous. For instance having moodle send an email to itself and then having it processed by the inbound mail handler to make it&#039;s properly configured (see https://tracker.moodle.org/browse/MDL-48800). In cases like these please make sure the age or staleness of the check is shown in the summary, and you should also consider turning the result status into a warning if the result is too old. If appropriate make the threshold a configurable admin setting.&lt;br /&gt;
==See also==&lt;br /&gt;
* [[:en:Performance overview|Performance overview]] user docs&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=61987</id>
		<title>Error pages</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Error_pages&amp;diff=61987"/>
		<updated>2022-04-19T04:10:33Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* 404 File not found - Web server errors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is for any Moodle admins or developers who want to setup or customize error pages on their Moodle site. There is a variety of ways different errors can happen at different layers in a Moodle stack and because they each happen under different circumstance there cannot be a single way of handling them.&lt;br /&gt;
== 404 Normal Moodle errors and exceptions ==&lt;br /&gt;
These types of errors are things like you tried to access a course which no longer exists, or access a course which you don&#039;t have access to. Because these are &amp;quot;Moodle&amp;quot; pages they are themed correctly and normally there isn&#039;t much you want to do to improve them.&lt;br /&gt;
&lt;br /&gt;
If you want to change the wording of the errors see:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/en/Language_customisation&lt;br /&gt;
&lt;br /&gt;
If you want to change the theme, that is one and the same as normal theme customization:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Themes_overview&lt;br /&gt;
&lt;br /&gt;
If a Moodle page throws an exception which isn&#039;t captured and results in an error page, and it will also helpfully, but blindly, link into this Moodle wiki where you might find more information on the error. This is great for sites in development or for admin type errors but typically less useful. You may want to divert users to your own support instead of off into a wiki page with generic Moodle information.&lt;br /&gt;
&lt;br /&gt;
This links behavior can be changed and removed in your admin settings here:&lt;br /&gt;
&lt;br /&gt;
/admin/settings.php?section=documentation&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Exceptions&lt;br /&gt;
&lt;br /&gt;
You can also simply hide the links using a capability too:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Linking_Moodle_and_Docs#Removing_Moodle_Docs_links_for_teachers&lt;br /&gt;
== 403 Forbidden - Web server errors ==&lt;br /&gt;
For additional security you should not allow access to directory listings and you can also block access to certain files.&lt;br /&gt;
&lt;br /&gt;
For more security you can serve a 404 instead of a 403, and if you prefer you can reuse the themed 404 page below.&lt;br /&gt;
=== Apache ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
DirectoryIndex disabled&lt;br /&gt;
Options -Indexes&lt;br /&gt;
ErrorDocument 403 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex&lt;br /&gt;
== 404 File not found - Web server errors ==&lt;br /&gt;
These are for errors which are technically outside of moodle such a 404 file not found at the server level rather than a 404 served by moodle. If you want to theme these then the approach is the same as you normally would for anything non Moodle, configure your web server to show a custom error page. But instead of writing your own, there is a built in Moodle error handler php script so they get the normal Moodle theme applied. You can see an example of what this error page looks like here:&lt;br /&gt;
&lt;br /&gt;
https://moodle.org/errors/&lt;br /&gt;
=== Apache ===&lt;br /&gt;
You can configure this using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
ErrorDocument 404 /error/index.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== nginx ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
error_page 404 /error/index.php;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== 502 Bad Gateway - Proxy / CDN issues ==&lt;br /&gt;
This means some upstream of your moodle server could not connect, ie nginx, varnish, a load balancer, proxy or CDN.&lt;br /&gt;
&lt;br /&gt;
These error pages cannot be themed by at the application level and how to customize them depends on what is proxying moodle.&lt;br /&gt;
&lt;br /&gt;
NOTE: It is very important if you do customize these to make sure you are not overriding the 40x and 50x error pages that Moodle may serve.&lt;br /&gt;
== 503 Service unavailable - Fatal Moodle bootstrap errors ==&lt;br /&gt;
New in 4.0&lt;br /&gt;
&lt;br /&gt;
These types of errors are more fundamental, and something has gone wrong so Moodle itself can&#039;t load, which means the error page can&#039;t access the database, MUC, or the language and strings API.&lt;br /&gt;
&lt;br /&gt;
Previously these were difficult to style, and there are many edge cases to capture. This was fixed in this tracker:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-56041&lt;br /&gt;
&lt;br /&gt;
These low level error pages resulted in the &#039;yellow box&#039; and in 4.0 are now improved to more closely match the boost / bootstrap theme. You can customize this error page by editing this file:&lt;br /&gt;
&lt;br /&gt;
error/plainpage.php&lt;br /&gt;
&lt;br /&gt;
Note: this file cannot contain any Moodle API&#039;s and cannot link to files which are served by moodle such as theme files or plugin files. Ideally serve all dependencies such as css and even image inline.&lt;br /&gt;
== 503 Moodle under maintenance - Maintenance mode ==&lt;br /&gt;
During maintenance mode Moodle will serve a &amp;quot;503 Service Unavailable&amp;quot;. This page can be customized:&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/38/en/Maintenance_mode#CLI_maintenance_mode&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Task_API&amp;diff=61872</id>
		<title>Task API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Task_API&amp;diff=61872"/>
		<updated>2022-03-20T22:45:15Z</updated>

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

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

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

		<summary type="html">&lt;p&gt;Brendanheywood: /* See also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The Web services API allows you to expose your plugin&#039;s functions (usually [[External functions API|external functions]]) as Web services.&lt;br /&gt;
&lt;br /&gt;
Once you have done this, your plugin&#039;s functions will be accessible to other systems through Web services using one of a number of protocols, like XML-RPC, REST or SOAP.&lt;br /&gt;
&lt;br /&gt;
Exposing functions as Web service functions is done in one file called services.php.&lt;br /&gt;
== services.php ==&lt;br /&gt;
* This file can be added to the &#039;&#039;&#039;db&#039;&#039;&#039; sub-folder of your [[Frankenstyle#Plugin_types|plugin]].&lt;br /&gt;
* This file contains one or two arrays. The first array declares your web service functions. Each of these declarations reference a function in your module (usually [[External functions API|an external function]]).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = array(&lt;br /&gt;
        &#039;local_PLUGINNAME_FUNCTIONNAME&#039; =&amp;gt; array( // local_PLUGINNAME_FUNCTIONNAME is the name of the web service function that the client will call.                                                                                &lt;br /&gt;
                &#039;classname&#039;   =&amp;gt; &#039;component\external[\optional\sub\namespaces\classname&#039;, // create this class in componentdir/classes/external&lt;br /&gt;
                &#039;methodname&#039;  =&amp;gt; &#039;FUNCTIONNAME&#039;, // implement this function into the above class&lt;br /&gt;
                &#039;description&#039; =&amp;gt; &#039;This documentation will be displayed in the generated API documentation &lt;br /&gt;
                                          (Administration &amp;gt; Plugins &amp;gt; Webservices &amp;gt; API documentation)&#039;,&lt;br /&gt;
                &#039;type&#039;        =&amp;gt; &#039;write&#039;, // the value is &#039;write&#039; if your function does any database change, otherwise it is &#039;read&#039;.&lt;br /&gt;
                &#039;ajax&#039;        =&amp;gt; true, // true/false if you allow this web service function to be callable via ajax&lt;br /&gt;
                &#039;capabilities&#039;  =&amp;gt; &#039;moodle/xxx:yyy, addon/xxx:yyy&#039;,  // List the capabilities required by the function (those in a require_capability() call) (missing capabilities are displayed for authorised users and also for manually created tokens in the web interface, this is just informative).&lt;br /&gt;
                &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;)    // Optional, only available for Moodle 3.1 onwards. List of built-in services (by shortname) where the function will be included. Services created manually via the Moodle interface are not supported.&lt;br /&gt;
        )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* The second, optional array declares the pre-built services.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
// OPTIONAL&lt;br /&gt;
// During the plugin installation/upgrade, Moodle installs these services as pre-build services. &lt;br /&gt;
// A pre-build service is not editable by administrator.&lt;br /&gt;
$services = array(&lt;br /&gt;
        &#039;MY SERVICE&#039; =&amp;gt; array(&lt;br /&gt;
                &#039;functions&#039; =&amp;gt; array (&#039;local_PLUGINNAME_FUNCTIONNAME&#039;), &lt;br /&gt;
                &#039;restrictedusers&#039; =&amp;gt; 0, // if 1, the administrator must manually select which user can use this service. &lt;br /&gt;
                                                   // (Administration &amp;gt; Plugins &amp;gt; Web services &amp;gt; Manage services &amp;gt; Authorised users)&lt;br /&gt;
                &#039;enabled&#039;=&amp;gt;1, // if 0, then token linked to this service won&#039;t work&lt;br /&gt;
                &#039;shortname&#039;=&amp;gt;&#039;myservice&#039; //the short name used to refer to this service from elsewhere including when fetching a token&lt;br /&gt;
        )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;Don&#039;t forget to increment the version number in the version.php file of your plugin whenever services.php changes, otherwise Moodle will not detect the changes.&amp;lt;/p&amp;gt;&lt;br /&gt;
== Detailed tutorial ==&lt;br /&gt;
A more detailed tutorial for this system can be found at the following page:&lt;br /&gt;
* [[Adding a web service to a plugin]]&lt;br /&gt;
Among other things, that tutorial explains how you define the parameters and return value of your function.&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [[External functions API]]&lt;br /&gt;
* [[Web services API Changes]]&lt;br /&gt;
* [[Session locks#Read only sessions in web services]]&lt;br /&gt;
[[Category:Web_Services]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61850</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61850"/>
		<updated>2022-03-16T03:04:43Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
When you create a normal moodle page and include config.php then by default a large amount of moodle bootstrapping runs and you will have the $SESSION global setup for you. This is a safe starting assumption but when writing higher performance code it is better to reduce or eliminate the session locks where possible.&lt;br /&gt;
== Debugging session lock issues ==&lt;br /&gt;
If you have &#039;pages that are slow&#039; and you profile them and see that you are waiting on a lock to free up then this is potentially an easy thing to fix to improve your overall performance.&lt;br /&gt;
 $CFG-&amp;gt;debugsessionlock = 5; // Time in seconds&lt;br /&gt;
When a session is locked for more N seconds a debugging call will be made with details of what the other page was which is holding onto the lock.&lt;br /&gt;
== Session unlocking ==&lt;br /&gt;
By default core assumes that you might need to mutate the $SESSION object so it will hold a lock on the session until the page finished and a shutdown handler will release the session lock.&lt;br /&gt;
If you are working on any page which is potentially long running, then you should cleanly separate logic which runs early which could mutate the session from the long running processin code and unlock the session.&lt;br /&gt;
 \core\session\manager::write_close();&lt;br /&gt;
== Read only session in pages ==&lt;br /&gt;
For this to work, READONLY sessions must be enabled as well needing your code to support it. Not all session &lt;br /&gt;
&lt;br /&gt;
If you know ahead of time that you will never mutate the session, but you still need to be able to read it, then you can declare your page to be read only. This will mean your page will never block the session in another http request.&lt;br /&gt;
 define(&#039;READ_ONLY_SESSION&#039;, true);&lt;br /&gt;
== Read only sessions in web services ==&lt;br /&gt;
The same is possible in web services. When you declare your web service you can specify it will not need a session lock:&lt;br /&gt;
     &#039;core_message_get_unread_conversations_count&#039; =&amp;gt; array(&lt;br /&gt;
         &#039;classname&#039; =&amp;gt; &#039;core_message_external&#039;,&lt;br /&gt;
         &#039;methodname&#039; =&amp;gt; &#039;get_unread_conversations_count&#039;,&lt;br /&gt;
         &#039;classpath&#039; =&amp;gt; &#039;message/externallib.php&#039;,&lt;br /&gt;
         &#039;description&#039; =&amp;gt; &#039;Retrieve the count of unread conversations for a given user&#039;,&lt;br /&gt;
         &#039;type&#039; =&amp;gt; &#039;read&#039;,&lt;br /&gt;
         &#039;ajax&#039; =&amp;gt; true,&lt;br /&gt;
         &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE),&lt;br /&gt;
         &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&#039;readonlysession&#039; =&amp;gt; true, // We don&#039;t modify the session.&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
     ), &lt;br /&gt;
== No session at all ==&lt;br /&gt;
If your script doesn&#039;t actually need $SESSION in the first place then save even more processing and locks by declaring:&lt;br /&gt;
 define(&#039;NO_MOODLE_COOKIES&#039;, true);&lt;br /&gt;
== No config is needed ==&lt;br /&gt;
Going to the absolute extreme, if you do not even need the full moodle bootstrap to run then you can skip it via:&lt;br /&gt;
 define(&#039;ABORT_AFTER_CONFIG&#039;, true);&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Performance_and_scalability&amp;diff=61849</id>
		<title>Performance and scalability</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Performance_and_scalability&amp;diff=61849"/>
		<updated>2022-03-16T02:45:25Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Be cautious about other external calls */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Performance&#039;&#039;&#039; is about allowing Moodle to support as many users as possible with a certain amount of hardware.&lt;br /&gt;
&lt;br /&gt;
Of course you can always always buy a bigger server. &#039;&#039;&#039;Scalability&#039;&#039;&#039; means ensuring that if you buy a server that is about twice as powerful, it can then handle about twice as much load.&lt;br /&gt;
&lt;br /&gt;
This page is part of the [[Coding|Moodle coding guidelines]]&lt;br /&gt;
==Writing code that scales and performs==&lt;br /&gt;
===Every page should only use a fixed number of database queries===&lt;br /&gt;
* Be very suspicious if you ever see database code inside a loop.&lt;br /&gt;
** This is sometimes hard to spot if the database access is hidden in a function.&lt;br /&gt;
* Instead use JOINs and subqueries. (&amp;lt;tt&amp;gt;get_records_sql&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;get_recordset_sql&amp;lt;/tt&amp;gt;, etc.).&lt;br /&gt;
** Or look for a Moodle API function that gets the information you want as efficiently as possible. (For example, &amp;lt;tt&amp;gt;get_users_by_capability&amp;lt;/tt&amp;gt;)&lt;br /&gt;
** See the [[Database|Database guidelines]] for how to write SQL that will work on all our supported databases.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Limit the amount of RAM each page requires to generate===&lt;br /&gt;
* Large reports should be broken into pages of a fixed size.&lt;br /&gt;
* Processing large amounts of data from the database should be handled with a recordset (when you cannot do all the processing in the database with SQL) and using [https://github.com/moodle/moodle/blob/master/lib/classes/dml/recordset_walk.php recordset_walk iterator], as there is no RAM benefit on using recordsets if you load all the results in a massive PHP array.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Be cautious about other external calls===&lt;br /&gt;
Like a database query, there are other operations that are much slower than just executing PHP code. For example:&lt;br /&gt;
* running a shell script&lt;br /&gt;
* making a web-service call&lt;br /&gt;
* (to a lesser extent) working with files.&lt;br /&gt;
Whenever you are doing these things, worry about performance.&lt;br /&gt;
&lt;br /&gt;
=== Limit the scope of session locks ===&lt;br /&gt;
If you don&#039;t need a session lock, or only need it for part of your page, then unlock the session. Full details here: [[Session locks]]&lt;br /&gt;
&lt;br /&gt;
==How to improve the performance of your code==&lt;br /&gt;
===Measure during development===&lt;br /&gt;
* Turn on [[:en:Debugging#Performance_info|display of performance information]] (including counting database queries), so you are aware of what your code is doing.&lt;br /&gt;
&lt;br /&gt;
* Use tools like [http://jakarta.apache.org/jmeter/ JMeter] to subject your new code to load.&lt;br /&gt;
&lt;br /&gt;
* Use https://github.com/moodlehq/moodle-performance-comparison (Moodle 2.5 onwards) to compare performance. You can also use the [[Test site generator]] or [[Test course generator]] generators (also Moodle 2.5 onwards).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Measure in production===&lt;br /&gt;
* If you&#039;re using postgres, there&#039;s a script that can parse the logs and output the top 10 slow queries, ready to be plugged into a cronjob to email you every day. It can be found here:&lt;br /&gt;
http://git.catalyst.net.nz/gw?p=pgtools.git;a=blob_plain;f=scripts/pg-log-process.pl;hb=refs/heads/pg-log-process-multidb&lt;br /&gt;
&lt;br /&gt;
You need to configure postgres a bit to make it log things in the correct format. Instructions are in the file.&lt;br /&gt;
==How big can a Moodle site be?==&lt;br /&gt;
Looking at the [http://moodle.org/stats/ statistics], the largest sites in the world currently have&lt;br /&gt;
* Up to 1 000 000 users&lt;br /&gt;
* Up to 50 000 courses&lt;br /&gt;
* Up to 5 000 users per course&lt;br /&gt;
* Up to 50 roles&lt;br /&gt;
* Up to 100 course categories nested up to about 10 levels deep.&lt;br /&gt;
* Up to XXX activities in a course.&lt;br /&gt;
* Please add more things here.&lt;br /&gt;
When planning and testing your code, these are the sorts of numbers you should be contemplating. However, do not assume that Moodle sites will never get bigger than this.&lt;br /&gt;
&lt;br /&gt;
Even if you can&#039;t test sites this big on your development server, you should use the generator script so you can test your code in a Moodle site that is not tiny.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Coding|Moodle coding guidelines]]&lt;br /&gt;
* [[Performance]] guidance for administrators&lt;br /&gt;
* [[Performance FAQ]]&lt;br /&gt;
[[Category:Coding guidelines|Performance]]&lt;br /&gt;
[[ja:パフォーマンスおよびスケーラビリティ]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=61848</id>
		<title>Core APIs</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Core_APIs&amp;diff=61848"/>
		<updated>2022-03-16T02:44:00Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* See also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Moodle has a number of core APIs that provide tools for Moodle scripts.&lt;br /&gt;
&lt;br /&gt;
They are essential when writing [[Plugins|Moodle plugins]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Most-used General APIs==&lt;br /&gt;
These APIs are critical and will be used by nearly every Moodle plugin.&lt;br /&gt;
=== Access API (access) ===&lt;br /&gt;
The [[Access API]] gives you functions so you can determine what the current user is allowed to do, and it allows modules to extend Moodle with new capabilities.&lt;br /&gt;
=== Data manipulation API (dml) ===&lt;br /&gt;
The [[Data manipulation API]] allows you to read/write to databases in a consistent and safe way.&lt;br /&gt;
=== File API (files) ===&lt;br /&gt;
The [[File API]] controls the storage of files in connection to various plugins.&lt;br /&gt;
=== Form API (form) ===&lt;br /&gt;
The [[Form API]] defines and handles user data via web forms.&lt;br /&gt;
=== Logging API (log) ===&lt;br /&gt;
The [[Events API]] allows you to log events in Moodle, while [[Logging 2]] describes how logs are stored and retrieved.&lt;br /&gt;
=== Navigation API (navigation) ===&lt;br /&gt;
The [[Navigation API]] allows you to manipulate the navigation tree to add and remove items as you wish.&lt;br /&gt;
=== Page API (page) ===&lt;br /&gt;
The [[Page API]] is used to set up the current page, add JavaScript, and configure how things will be displayed to the user.&lt;br /&gt;
=== Output API (output) ===&lt;br /&gt;
The [[Output API]] is used to render the HTML for all parts of the page.&lt;br /&gt;
=== String API (string) ===&lt;br /&gt;
The [[String API]] is how you get language text strings to use in the user interface. It handles any language translations that might be available.&lt;br /&gt;
=== Upgrade API (upgrade) ===&lt;br /&gt;
The [[Upgrade API]] is how your module installs and upgrades itself, by keeping track of its own version.&lt;br /&gt;
=== Moodlelib API (core) ===&lt;br /&gt;
The [[Moodlelib API]] is the central library file of miscellaneous general-purpose Moodle functions. Functions can over the handling of request parameters, configs, user preferences, time, login, mnet, plugins, strings and others. There are plenty of defined constants too.&lt;br /&gt;
==Other General APIs==&lt;br /&gt;
=== Admin settings API (admin) ===&lt;br /&gt;
The [[Admin settings]] API deals with providing configuration options for each plugin and Moodle core.&lt;br /&gt;
=== Admin presets API (adminpresets) ===&lt;br /&gt;
The [[AdminPresetsAPI|Admin presets API]] allows plugins to make some decisions/implementations related to the Site admin presets.&lt;br /&gt;
=== Analytics API (analytics) ===&lt;br /&gt;
The [[Analytics API]] allow you to create prediction models and generate insights.&lt;br /&gt;
=== Availability API (availability) ===&lt;br /&gt;
The [[Availability API]] controls access to activities and sections.&lt;br /&gt;
=== Backup API (backup) ===&lt;br /&gt;
The [[Backup API]] defines exactly how to convert course data into XML for backup purposes, and the [[Restore API]] describes how to convert it back the other way.&lt;br /&gt;
=== Cache API (cache) ===&lt;br /&gt;
The [[The Moodle Universal Cache (MUC)]] is the structure for storing cache data within Moodle. [[Cache_API]] explains some of what is needed to use a cache in your code.&lt;br /&gt;
=== Calendar API (calendar) ===&lt;br /&gt;
The [[Calendar API]] allows you to add and modify events in the calendar for user, groups, courses, or the whole site.&lt;br /&gt;
=== Check API (check) ===&lt;br /&gt;
The [[Check API]] allows you to add security, performance or health checks to your site.&lt;br /&gt;
=== Comment API (comment) ===&lt;br /&gt;
The [[Comment API]] allows you to save and retrieve user comments, so that you can easily add commenting to any of your code.&lt;br /&gt;
=== Competency API (competency) ===&lt;br /&gt;
The [[Competency API]] allows you to list and add evidence of competencies to learning plans, learning plan templates, frameworks, courses and activities.&lt;br /&gt;
=== Data definition API (ddl) ===&lt;br /&gt;
The [[Data definition API]] is what you use to create, change and delete tables and fields in the database during upgrades.&lt;br /&gt;
=== Editor API ===&lt;br /&gt;
The [[Editor API]] is used to control HTML text editors.&lt;br /&gt;
=== Enrolment API (enrol) ===&lt;br /&gt;
The [[Enrolment API]] deals with course participants.&lt;br /&gt;
=== Events API (event) ===&lt;br /&gt;
The [[Events API]] allows to define &amp;quot;events&amp;quot; with payload data to be fired whenever you like, and it also allows you to define handlers to react to these events when they happen. This is the recommended form of inter-plugin communication. This also forms the basis for logging in Moodle.&lt;br /&gt;
=== Experience API (xAPI) ===&lt;br /&gt;
The Experience API (xAPI) is an e-learning standard that allows learning content and learning systems to speak to each other. The [[Experience API (xAPI)]]&lt;br /&gt;
allows any plugin to generate and handle xAPI standard statements.&lt;br /&gt;
=== External functions API (external) ===&lt;br /&gt;
The [[External functions API]] allows you to create fully parametrised methods that can be accessed by external programs (such as [[Web services]]).&lt;br /&gt;
=== Favourites API ===&lt;br /&gt;
The [[Favourites API]] allows you to mark items as favourites for a user and manage these favourites. This is often referred to as &#039;Starred&#039;.&lt;br /&gt;
=== H5P API (h5p) ===&lt;br /&gt;
The [[H5P API]] allows plugins to make some decisions/implementations related to the [[H5P|H5P integration]].&lt;br /&gt;
=== Lock API (lock) ===&lt;br /&gt;
The [[Lock API]] lets you synchronise processing between multiple requests, even for separate nodes in a cluster.&lt;br /&gt;
=== Message API (message) ===&lt;br /&gt;
The [[Message API]] lets you post messages to users. They decide how they want to receive them.&lt;br /&gt;
=== Media API (media) ===&lt;br /&gt;
The [[Media_players#Using_media_players|Media]] API can be used to embed media items such as audio, video, and Flash.&lt;br /&gt;
=== My profile API ===&lt;br /&gt;
The [[My profile API]] is used to add things to the profile page.&lt;br /&gt;
=== OAuth 2 API (oauth2) ===&lt;br /&gt;
The [[OAuth 2 API]] is used to provide a common place to configure and manage external systems using OAuth 2.&lt;br /&gt;
=== Payment API (payment) ===&lt;br /&gt;
The [[Payment API]] deals with payments.&lt;br /&gt;
=== Preference API (preference) ===&lt;br /&gt;
The [[Preference API]] is a simple way to store and retrieve preferences for individual users.&lt;br /&gt;
=== Portfolio API (portfolio) ===&lt;br /&gt;
The [[Portfolio API]] allows you to add portfolio interfaces on your pages and allows users to package up data to send to their portfolios.&lt;br /&gt;
=== Privacy API (privacy) ===&lt;br /&gt;
The [[Privacy API]] allows you to describe the personal data that you store, and provides the means for that data to be discovered, exported, and deleted on a per-user basis.&lt;br /&gt;
This allows compliance with regulation such as the General Data Protection Regulation (GDPR) in Europe.&lt;br /&gt;
=== Rating API (rating) ===&lt;br /&gt;
The [[Rating API]] lets you create AJAX rating interfaces so that users can rate items in your plugin. In an activity module, you may choose to aggregate ratings to form grades.&lt;br /&gt;
=== Report builder API (reportbuilder) ===&lt;br /&gt;
The [[Report builder API]] allows you to create reports in your plugin, as well as providing custom reporting data which users can use to build their own reports.&lt;br /&gt;
=== RSS API (rss) ===&lt;br /&gt;
The [[RSS API]] allows you to create secure RSS feeds of data in your module.&lt;br /&gt;
=== Search API (search) ===&lt;br /&gt;
The [[Search API]] allows you to index contents in a search engine and query the search engine for results.&lt;br /&gt;
=== Tag API (tag) ===&lt;br /&gt;
The [[Tag API]] allows you to store tags (and a tag cloud) to items in your module.&lt;br /&gt;
=== Task API (task) ===&lt;br /&gt;
The [[Task API]] lets you run jobs in the background. Either once off, or on a regular schedule.&lt;br /&gt;
=== Time API (time) ===&lt;br /&gt;
The [[Time API]] takes care of translating and displaying times between users in the site.&lt;br /&gt;
=== Testing API (test) ===&lt;br /&gt;
The testing API contains the Unit test API ([[PHPUnit]]) and Acceptance test API ([[Acceptance testing]]). Ideally all new code should have unit tests written FIRST.&lt;br /&gt;
=== User-related APIs (user) ===&lt;br /&gt;
This is a rather informal grouping of miscellaneous [[User-related APIs]] relating to sorting and searching lists of users.&lt;br /&gt;
=== Web services API (webservice) ===&lt;br /&gt;
The [[Web services API]] allows you to expose particular functions (usually external functions) as web services.&lt;br /&gt;
=== Badges API (badges) ===&lt;br /&gt;
The [https://docs.moodle.org/dev/OpenBadges_User_Documentation Badges] user documentation (is a temp page until we compile a proper page with all the classes and APIs that allows you to manage particular badges and OpenBadges Backpack).&lt;br /&gt;
=== Custom fields API ===&lt;br /&gt;
The [[Custom fields API]] allows you to configure and add custom fields for different entities&lt;br /&gt;
== Activity module APIs ==&lt;br /&gt;
Activity modules are the most important plugin in Moodle. There are several core APIs that service only Activity modules.&lt;br /&gt;
=== Activity completion API (completion) ===&lt;br /&gt;
The [[Activity completion API]] is to indicate to the system how activities are completed.&lt;br /&gt;
=== Advanced grading API (grading) ===&lt;br /&gt;
The [[Advanced grading API]] allows you to add more advanced grading interfaces (such as rubrics) that can produce simple grades for the gradebook.&lt;br /&gt;
=== Conditional activities API (condition) - deprecated in 2.7 ===&lt;br /&gt;
The deprecated [[Conditional activities API]] used to provide conditional access to modules and sections in Moodle 2.6 and below. It has been replaced by the [[Availability API]].&lt;br /&gt;
=== Groups API (group) ===&lt;br /&gt;
The [[Groups API]] allows you to check the current activity group mode and set the current group.&lt;br /&gt;
=== Gradebook API (grade) ===&lt;br /&gt;
The [[Gradebook API]] allows you to read and write from the gradebook. It also allows you to provide an interface for detailed grading information.&lt;br /&gt;
=== Plagiarism API (plagiarism) ===&lt;br /&gt;
The [[Plagiarism API]] allows your activity module to send files and data to external services to have them checked for plagiarism.&lt;br /&gt;
=== Question API (question) ===&lt;br /&gt;
The [[Question API]] (which can be divided into the Question bank API and the Question engine API), can be used by activities that want to use questions from the question bank.&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Plugins]] - plugin types also have their own APIs&lt;br /&gt;
* [[Callbacks]] - list of all callbacks in Moodle&lt;br /&gt;
* [[Coding style]] - general information about writing PHP code for Moodle&lt;br /&gt;
* [[Session locks]]&lt;br /&gt;
[[ja:コアAPI]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61847</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61847"/>
		<updated>2022-03-16T02:43:31Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
When you create a normal moodle page and include config.php then by default a large amount of moodle bootstrapping runs and you will have the $SESSION global setup for you.&lt;br /&gt;
&lt;br /&gt;
== Debugging session lock issues ==&lt;br /&gt;
If you have &#039;pages that are slow&#039; and you profile them and see that you are waiting on a lock to free up then this is potentially an easy thing to fix to improve your overall performance.&lt;br /&gt;
 $CFG-&amp;gt;debugsessionlock = 5; // Time in seconds&lt;br /&gt;
When a session is locked for more N seconds a debugging call will be made with details of what the other page was which is holding onto the lock.&lt;br /&gt;
&lt;br /&gt;
== Session unlocking ==&lt;br /&gt;
By default core assumes that you might need to mutate the $SESSION object so it will hold a lock on the session until the page finished and a shutdown handler will release the session lock.&lt;br /&gt;
If you are working on any page which is potentially long running, then you should cleanly separate logic which runs early which could mutate the session from the long running processin code and unlock the session.&lt;br /&gt;
 \core\session\manager::write_close();&lt;br /&gt;
== Read only session in pages ==&lt;br /&gt;
For this to work, READONLY sessions must be enabled as well needing your code to support it. Not all session &lt;br /&gt;
&lt;br /&gt;
If you know ahead of time that you will never mutate the session, but you still need to be able to read it, then you can declare your page to be read only. This will mean your page will never block the session in another http request.&lt;br /&gt;
 define(&#039;READ_ONLY_SESSION&#039;, true);&lt;br /&gt;
== Read only sessions in web services ==&lt;br /&gt;
The same is possible in web services. When you declare your web service you can specify it will not need a session lock:&lt;br /&gt;
     &#039;core_message_get_unread_conversations_count&#039; =&amp;gt; array(&lt;br /&gt;
         &#039;classname&#039; =&amp;gt; &#039;core_message_external&#039;,&lt;br /&gt;
         &#039;methodname&#039; =&amp;gt; &#039;get_unread_conversations_count&#039;,&lt;br /&gt;
         &#039;classpath&#039; =&amp;gt; &#039;message/externallib.php&#039;,&lt;br /&gt;
         &#039;description&#039; =&amp;gt; &#039;Retrieve the count of unread conversations for a given user&#039;,&lt;br /&gt;
         &#039;type&#039; =&amp;gt; &#039;read&#039;,&lt;br /&gt;
         &#039;ajax&#039; =&amp;gt; true,&lt;br /&gt;
         &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE),&lt;br /&gt;
         &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&#039;readonlysession&#039; =&amp;gt; true, // We don&#039;t modify the session.&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
     ), &lt;br /&gt;
== No session at all ==&lt;br /&gt;
If your script doesn&#039;t actually need $SESSION in the first place then save even more processing and locks by declaring:&lt;br /&gt;
 define(&#039;NO_MOODLE_COOKIES&#039;, true);&lt;br /&gt;
== No config is needed ==&lt;br /&gt;
Going to the absolute extreme, if you do not even need the full moodle bootstrap to run then you can skip it via:&lt;br /&gt;
 define(&#039;ABORT_AFTER_CONFIG&#039;, true);&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61844</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61844"/>
		<updated>2022-03-16T02:34:03Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
When you create a normal moodle page and include config.php then by default a large amount of moodle bootstrapping runs and you will have the $SESSION global setup for you.&lt;br /&gt;
&lt;br /&gt;
== Session unlocking ==&lt;br /&gt;
By default core assumes that you might need to mutate the $SESSION object so it will hold a lock on the session until the page finished and a shutdown handler will release the session lock.&lt;br /&gt;
&lt;br /&gt;
If you are working on any page which is potentially long running, then you should cleanly separate logic which runs early which could mutate the session from the long running processin code and unlock the session.&lt;br /&gt;
 \core\session\manager::write_close&lt;br /&gt;
&lt;br /&gt;
== Read only session in pages ==&lt;br /&gt;
If you know ahead of time that you will never mutate the session, but you still need to be able to read it, then you can declare your page to be read only. This will mean your page will never block the session in another http request.&lt;br /&gt;
 define(&#039;READ_ONLY_SESSION&#039;, true);&lt;br /&gt;
&lt;br /&gt;
== Read only sessions in web services ==&lt;br /&gt;
The same is possible in web services. When you declare your web service you can specify it will not need a session lock:&lt;br /&gt;
     &#039;core_message_get_unread_conversations_count&#039; =&amp;gt; array(&lt;br /&gt;
         &#039;classname&#039; =&amp;gt; &#039;core_message_external&#039;,&lt;br /&gt;
         &#039;methodname&#039; =&amp;gt; &#039;get_unread_conversations_count&#039;,&lt;br /&gt;
         &#039;classpath&#039; =&amp;gt; &#039;message/externallib.php&#039;,&lt;br /&gt;
         &#039;description&#039; =&amp;gt; &#039;Retrieve the count of unread conversations for a given user&#039;,&lt;br /&gt;
         &#039;type&#039; =&amp;gt; &#039;read&#039;,&lt;br /&gt;
         &#039;ajax&#039; =&amp;gt; true,&lt;br /&gt;
         &#039;services&#039; =&amp;gt; array(MOODLE_OFFICIAL_MOBILE_SERVICE),&lt;br /&gt;
         &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&#039;readonlysession&#039; =&amp;gt; true, // We don&#039;t modify the session.&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
     ), &lt;br /&gt;
&lt;br /&gt;
== No session at all ==&lt;br /&gt;
If your script doesn&#039;t actually need $SESSION in the first place then save even more processing and locks by declaring:&lt;br /&gt;
 define(&#039;NO_MOODLE_COOKIES&#039;, true);&lt;br /&gt;
&lt;br /&gt;
== No config is needed ==&lt;br /&gt;
Going to the absolute extreme, if you do not even need the full moodle bootstrap to run then you can skip it via:&lt;br /&gt;
 define(&#039;ABORT_AFTER_CONFIG&#039;, true);&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61842</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61842"/>
		<updated>2022-03-16T02:21:36Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
When you create a new page by default you will have the $SESSION global setup for you.&lt;br /&gt;
&lt;br /&gt;
\core\session\manager::write_close&lt;br /&gt;
&lt;br /&gt;
READ_ONLY_SESSION&lt;br /&gt;
&lt;br /&gt;
NO_MOODLE_COOKIES&lt;br /&gt;
&lt;br /&gt;
ABORT_AFTER_CONFIG&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61841</id>
		<title>Session locks</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Session_locks&amp;diff=61841"/>
		<updated>2022-03-16T02:20:57Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: Created page with &amp;quot; When you create a new page by default you will have the $SESSION global setup for you.  \core\session\manager::write_close  READ_ONLY_SESSION  NO_MOODLE_COOKIES&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
When you create a new page by default you will have the $SESSION global setup for you.&lt;br /&gt;
&lt;br /&gt;
\core\session\manager::write_close&lt;br /&gt;
&lt;br /&gt;
READ_ONLY_SESSION&lt;br /&gt;
&lt;br /&gt;
NO_MOODLE_COOKIES&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Check_API&amp;diff=61840</id>
		<title>Check API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Check_API&amp;diff=61840"/>
		<updated>2022-03-15T22:35:48Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Status checks (status) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
{{Moodle 3.9}}&lt;br /&gt;
== Checks ==&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-67818&lt;br /&gt;
&lt;br /&gt;
A Check is a runtime test to make sure something is working well. You can think of Checks as similar and complimentary to the [[PHPUnit]] and [[Acceptance_testing]] but the next layer around them, and done at run time rather than dev time or build time. Like other forms of testing the tests themselves should easy to read and reason about and confirm as valid. As many types of runtime checks can&#039;t be unit tested, the checks &#039;&#039;&#039;are&#039;&#039;&#039; the test.&lt;br /&gt;
&lt;br /&gt;
Checks can be used for a variety of purposes including:&lt;br /&gt;
* configuration checks&lt;br /&gt;
* security checks&lt;br /&gt;
* status checks&lt;br /&gt;
* performance checks&lt;br /&gt;
* health checks&lt;br /&gt;
Moodle has had various types of checks and reports for a long time but they were inconsistent and not machine readable. In Moodle 3.9 they were unified under a single Check API which also enabled plugins to cleanly define their own additional checks. Some examples include:&lt;br /&gt;
* a password policy plugin could add a security check&lt;br /&gt;
* a custom authentication plugin can add a check that the upstream identity system can be connected to&lt;br /&gt;
* a MUC store plugin could add a performance check&lt;br /&gt;
Having these centralized and with a consistent contract makes it much easier to ensure the whole system is running smoothly. This makes it possible for an external integration with monitoring systems such as Nagios / Icinga. The Check API is expose as an NPRE compliance cli script:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
php admin/cli/checks.php&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Result states of a check ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Status&lt;br /&gt;
! Meaning&lt;br /&gt;
! Example&lt;br /&gt;
|-&lt;br /&gt;
| N/A&lt;br /&gt;
| This check doesn&#039;t apply - but we may still want to expose the check&lt;br /&gt;
| secure cookies setting is disabled because site is not https&lt;br /&gt;
|-&lt;br /&gt;
| Ok&lt;br /&gt;
| A component is configured, working and fast.&lt;br /&gt;
| ldap can bind and return value with low latency&lt;br /&gt;
|-&lt;br /&gt;
| Info&lt;br /&gt;
| A component is OK, and we may want to alert the admin to something non urgent such as a deprecation, or something which needs to be checked manually.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Unknown&lt;br /&gt;
| We don&#039;t yet know the state. eg it may be very expensive so it is run using the Task API and we are waiting for the answer. NOTE: unknown is generally a bad thing and is semantically treated as an error. It is better to have a result of Unknown until the first result happens, and from then on it is Ok, or perhaps Warning or Error if the last known result is getting stale. If you are caching or showing a stale result you should expose the time of this in the result summary text.&lt;br /&gt;
| A complex user security report is still running for the first time.&lt;br /&gt;
|-&lt;br /&gt;
| Warning&lt;br /&gt;
| Something is not ideal and should be addressed, eg usability or the speed of the site may be affected, but it may self heal (eg a load spike)&lt;br /&gt;
| auth_ldap could bind but was slower than normal&lt;br /&gt;
|-&lt;br /&gt;
| Error&lt;br /&gt;
| Something is wrong with a component and a feature is not working&lt;br /&gt;
| auth_ldap could not connect, so users cannot start new sessions&lt;br /&gt;
|-&lt;br /&gt;
| Critical&lt;br /&gt;
| An error which is affecting everyone in a major way&lt;br /&gt;
| Cannot read sitedata or the database, the whole site is down&lt;br /&gt;
|}&lt;br /&gt;
How the various states are then leveraged is a local decision. A typical policy might be that health checks with a status of &#039;Error&#039; or &#039;Critical&#039; will page a system administrator 24/7, while &#039;Warning&#039; only pages during business hours.&lt;br /&gt;
== Check types and reports ==&lt;br /&gt;
Checks are broken down into types, which roughly map to a step life cycle of your Moodle System&lt;br /&gt;
=== Environmental checks (?) ===&lt;br /&gt;
/admin/environment.php&lt;br /&gt;
&lt;br /&gt;
These are environmental checks to make sure a Moodle instance is fully setup. This page is potential candidate to move to the new Check API but it slightly more complex than the other checks so hasn&#039;t been tackled yet. It would be a deeper change and this is intrinsically part of the install and upgrade system. It is not as critical to refactor as it is already possible for a plugin to declare its own checks, via either declarative [[Environment_checking]] or programmatically with a custom check: &lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/Environment_checking#Custom_checks&lt;br /&gt;
=== Configuration checks (?) ===&lt;br /&gt;
/admin/index.php?cache=1&lt;br /&gt;
&lt;br /&gt;
The Admin notifications page performs a mixture of checks, including security, status, and performance but none are as exhaustive as the checks in the reports below. It also does checks such as whether the web services for the Moodle Mobile App are turned on, and whether the site has been registered. Long term this page could show a summary of all the other check reports, or perhaps just the checks which are not in an OK state.&lt;br /&gt;
=== Security checks (security) ===&lt;br /&gt;
These are checks to make sure a Moodle instance is hardened correctly for you needs.&lt;br /&gt;
&lt;br /&gt;
/report/security/index.php&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-67776&lt;br /&gt;
=== Status checks (status) ===&lt;br /&gt;
/report/status/index.php&lt;br /&gt;
&lt;br /&gt;
A status check is an &#039;in the moment&#039; test and covers operational tests such as &#039;can moodle connect to ldap&#039;. The main core status checks are that cron is running regularly and there has been no failed tasks.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT:&#039;&#039;&#039; It is critical to understand that Status checks are conceptually defined at the level off the application and not at a lower host level such as a docker container or node in a cluster. Checks should be defined so that whichever instance you ask you should get a consistent answer. DO NOT use the Status Checks to detect containers which need reaped and restarted. If you do, any status error will mean all containers will simultaneously be marked for reaping.&lt;br /&gt;
&lt;br /&gt;
An additional status check is likely the most common type of check a plugin would define. Especially a plugin that connects to a 3rd party service. If the concept of &#039;OK&#039; requires some sort of threshold, eg network response within 500ms, then that threshold should be managed by the plugin and optionally exposed as a admin setting. The plugin may choose to have different thresholds for Warning / Error / Critical. When designing a new Status Check be mindful that it needs to be actionable, for instance if you are asserting that a remote domain is available and it goes down, which then alerts your infrastructure team, there isn&#039;t much they can do about it if it isn&#039;t their domain. If it is borderline then make things like this configurable so that each site has to option of tune their own policies of what should be considered an issue or not.&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-47271&lt;br /&gt;
=== Performance checks (performance) ===&lt;br /&gt;
/report/performance/index.php&lt;br /&gt;
&lt;br /&gt;
Each check might simply check for certain settings which are known to slow things down, or it might actually do some sort of test like multiple reads and writes to the db or filesystem to get a performance metric.&lt;br /&gt;
=== Health checks (health) ===&lt;br /&gt;
/admin/tool/health/&lt;br /&gt;
&lt;br /&gt;
The &#039;health center&#039; is currently unsupported and contains some very old code. It is conceptually similar to &#039;status&#039; checks except larger more long terms issues, such as detecting corrupt records. Ideally it is improved and converted to the Check API, see https://tracker.moodle.org/browse/MDL-67228&lt;br /&gt;
== Implementing a new check ==&lt;br /&gt;
=== A check class ===&lt;br /&gt;
And make a new check class in mod/myplugin/classes/check/foobar.php and the only mandatory method is get_result(). By default it will use a set language string but you can override the get_name() method to reuse an existing string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;checkfoobar&#039;] = &#039;Check the foos to make sure they are barred&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
namespace mod_myplugin\check;&lt;br /&gt;
use core\check\check;&lt;br /&gt;
 &lt;br /&gt;
class foobar extends check {&lt;br /&gt;
&lt;br /&gt;
    public function get_action_link(): ?\action_link {&lt;br /&gt;
        $url = new \moodle_url(&#039;/mod/myplugin/dosomething.php&#039;),&lt;br /&gt;
        return new \action_link($url, get_string(&#039;sitepolicies&#039;, &#039;admin&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_result() : result {&lt;br /&gt;
        if (some_check()) {&lt;br /&gt;
            $status = result::ERROR;&lt;br /&gt;
            $summary = get_string(&#039;check_foobar_error&#039;, &#039;mod_myplugin&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $status = result::OK;&lt;br /&gt;
            $summary = get_string(&#039;check_foobar_ok&#039;, &#039;mod_myplugin&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        $details = get_string(&#039;check_details&#039;, &#039;mod_myplugin&#039;);&lt;br /&gt;
        return new result($status, $summary, $details);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== The result summary ===&lt;br /&gt;
The summary could change depending on the result of the check but for a simple check might be a fixed string, not html. Try to keep the summary to 1 line as this might typically be the thing which gets passed through to a paging system and could be truncated.&lt;br /&gt;
=== The action link ===&lt;br /&gt;
The action link is the place to go to help fix the issue. It should be as specific as possibly, such as a deep link into an admin settings page, and can include hash anchors.&lt;br /&gt;
=== lib.php callback ===&lt;br /&gt;
First decide if and when your new check(s) should be shown. Should it be present if your plugin is disabled? If you do not want it show if disabled then do not return it in callback below. If you do want it to show when disabled, but the check doesn&#039;t much sense then you can return a value of NA.&lt;br /&gt;
&lt;br /&gt;
Next decide on what type of check it should be which determines what report it will be included in. Some checks might make sense to be reused with more than one report, eg it could be in both Status and Performance.&lt;br /&gt;
&lt;br /&gt;
Implement the right callback in lib.php for the report you want to add it to, and return an array (usually with only 1 item) of check objects:&lt;br /&gt;
&lt;br /&gt;
/mod/myplugin/lib.php&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function mod_myplugin_security_checks() {&lt;br /&gt;
    return [new mod_myplugin\check\foobar()];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Multiple instances of checks ===&lt;br /&gt;
Checks have been designed to be dynamic so you can return different checks depending on configuration, so auth_ldap would not return a check if the plugin is not enabled. Hypothetically if auth_ldap could be configured with 5 ldap servers then you could return 5 independent checks for each remote connection, each with different labels and information.&lt;br /&gt;
&lt;br /&gt;
If you plan to return multiple instances of a check class, make sure that each instance has a unique id.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
function mod_myplugin_security_checks() {&lt;br /&gt;
    return [&lt;br /&gt;
        new mod_myplugin\check\foobar(&#039;one&#039;),&lt;br /&gt;
        new mod_myplugin\check\foobar(&#039;two&#039;),&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Set the internal id in a way which is unique across all instances in your components namespace:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php&lt;br /&gt;
namespace mod_myplugin\check;&lt;br /&gt;
use core\check\check;&lt;br /&gt;
 &lt;br /&gt;
class foobar extends check {&lt;br /&gt;
    protected $id = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    public function __construct($id) {&lt;br /&gt;
        $this-&amp;gt;id = &#039;foobar&#039; . $id;&lt;br /&gt;
    }&lt;br /&gt;
    public function get_id(): string {&lt;br /&gt;
        return $this-&amp;gt;id;&lt;br /&gt;
    }&lt;br /&gt;
    ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Make checks as fast as practical ===&lt;br /&gt;
As many checks will be run and compiled into a report we want the checks themselves to be simple and as fast as possible. For instance an auth_ldap check while authenticating an end user could have a timeout of 60 seconds, and the check could warn if it takes more than 2 seconds. But the check could have a hard timeout of say 5 seconds and have a result status of ERROR for 5 or more seconds.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Lazy loading expensive result details ===&lt;br /&gt;
Checks can provide details on a check, such as the complete list of bad records. Generally this type of information might be expensive to produce so you can defer this lookup until get_details() is called specifically rather than setting this in the constructor. It will only be loaded on demand and shown when you drill down into the check details page.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
namespace mod_myplugin\check;&lt;br /&gt;
use core\check\check;&lt;br /&gt;
 &lt;br /&gt;
class foobar extends check {&lt;br /&gt;
    public function get_result(): result {&lt;br /&gt;
        return new foobar_result();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
class foobar_result extends \core\check\result {&lt;br /&gt;
    ...&lt;br /&gt;
    public function get_details(): string {&lt;br /&gt;
        // Do expensive lookups in here.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
For a real example see:&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodle/moodle/blob/master/lib/classes/check/access/riskxss_result.php&lt;br /&gt;
=== Asynchronous checks ===&lt;br /&gt;
Some checks are by their nature asynchronous. For instance having moodle send an email to itself and then having it processed by the inbound mail handler to make it&#039;s properly configured (see https://tracker.moodle.org/browse/MDL-48800). In cases like these please make sure the age or staleness of the check is shown in the summary, and you should also consider turning the result status into a warning if the result is too old. If appropriate make the threshold a configurable admin setting.&lt;br /&gt;
==See also==&lt;br /&gt;
* [[:en:Performance overview|Performance overview]] user docs&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61722</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61722"/>
		<updated>2022-02-09T11:35:56Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Frontend leaking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like below and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Backend leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this generally cannot be addressed by a developer in the code of moodle or your plugin, instead it is best addressed at the infrastructure level for example by stripping it out these params from urls before they are logged in your server configuration. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Frontend leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user in a way that could then leak to a 3rd party. An example might be accidentally disclosing it during a screen sharing session or even at a desktop being watched or filmed. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the sesskey check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param in a GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61721</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61721"/>
		<updated>2022-02-09T11:33:46Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Frontend leaking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like below and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Backend leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this generally cannot be addressed by a developer in the code of moodle or your plugin, instead it is best addressed at the infrastructure level for example by stripping it out these params from urls before they are logged in your server configuration. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Frontend leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user in a way that could then leak to a 3rd party. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the sesskey check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param in a GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61719</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61719"/>
		<updated>2022-02-09T05:53:47Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Session key */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like below and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Backend leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this generally cannot be addressed by a developer in the code of moodle or your plugin, instead it is best addressed at the infrastructure level for example by stripping it out these params from urls before they are logged in your server configuration. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Frontend leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the sesskey check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param in a GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61718</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61718"/>
		<updated>2022-02-09T05:50:51Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Guidelines for removing the sesskey from visible urls */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Backend leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this generally cannot be addressed by a developer in the code of moodle or your plugin, instead it is best addressed at the infrastructure level for example by stripping it out these params from urls before they are logged in your server configuration. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Frontend leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the sesskey check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param in a GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61717</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61717"/>
		<updated>2022-02-09T05:49:08Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Ensure your code does not expose the sesskey inadvertently */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Backend leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this generally cannot be addressed by a developer in the code of moodle or your plugin, instead it is best addressed at the infrastructure level for example by stripping it out these params from urls before they are logged in your server configuration. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Frontend leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param ina GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61716</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61716"/>
		<updated>2022-02-09T05:48:36Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Back end leaking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this generally cannot be addressed by a developer in the code of moodle or your plugin, instead it is best addressed at the infrastructure level for example by stripping it out these params from urls before they are logged in your server configuration. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param ina GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61715</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61715"/>
		<updated>2022-02-09T05:47:08Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Ensure your code does not expose the sesskey inadvertently */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of risks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin., it can only be address at the infrastructure level for example by stripping it out these params from urls before they are logged. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param ina GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61714</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61714"/>
		<updated>2022-02-09T05:46:21Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Back end leaking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin., it can only be address at the infrastructure level for example by stripping it out these params from urls before they are logged. A similar level of care needs to be taken when logging other things, for example logging the cookies in your access logs has a risk of allowing a session take over by anyone who has access to those logs.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param ina GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61713</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61713"/>
		<updated>2022-02-09T05:41:50Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Guidelines for removing the sesskey from visible urls */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# A sesskey param ina GET request then it is ok as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead. If it takes a long time then it runs the risk of the url being visible and lengthens the window of opportunity for a leak.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61712</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61712"/>
		<updated>2022-02-09T01:29:23Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Guidelines for removing the sesskey from visible urls */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## This page doesn&#039;t load any sub resources on another domain and where the sesskey could leak through the referer header&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61711</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61711"/>
		<updated>2022-02-09T01:27:20Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Guidelines for removing the sesskey from visible urls */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param. For example any urls for pluginfile.php should not have a sesskey param.&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61710</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61710"/>
		<updated>2022-02-09T01:23:20Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Guidelines for removing the sesskey from visible urls */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
&lt;br /&gt;
=== Examples of sesskey fixed in core ===&lt;br /&gt;
Some examples to help guide how to fix these issues:&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-68292&lt;br /&gt;
&lt;br /&gt;
https://tracker.moodle.org/browse/MDL-73295&lt;br /&gt;
&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61709</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61709"/>
		<updated>2022-02-09T01:19:44Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Ensure your code does not expose the sesskey inadvertently */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important hole is leaking the sesskey as part of the url in the referrer header when linking or interacting from another domain.&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61708</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61708"/>
		<updated>2022-02-09T01:18:42Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Back end leaking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be addressed in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61707</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61707"/>
		<updated>2022-02-09T01:18:08Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* See also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be address in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61706</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61706"/>
		<updated>2022-02-09T01:13:31Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Back end leaking */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern. Either way this cannot be address in the source of moodle or your plugin.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61705</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61705"/>
		<updated>2022-02-09T01:12:44Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Ensure your code does not expose the sesskey inadvertently */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be otherwise be mitigated by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern.&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61704</id>
		<title>Security:Cross-site request forgery</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Security:Cross-site_request_forgery&amp;diff=61704"/>
		<updated>2022-02-09T01:08:17Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* What you need to do as an administrator */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page forms part of the [[Security|Moodle security guidelines]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==What is the danger?==&lt;br /&gt;
When you put a web application on the internet, you are making it available so that anyone can send requests to it, and any request can be simply encoded as a URL.&lt;br /&gt;
&lt;br /&gt;
Suppose that in Moodle, the way for an Administrator to delete a user was to click a &#039;&#039;&#039;Delete&#039;&#039;&#039; button in their user profile, and then click &#039;&#039;&#039;Yes&#039;&#039;&#039; on an confirmation page. Suppose that as a result of that, the Administrator&#039;s web browser sends a POST request to &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php&amp;lt;/nowiki&amp;gt;, with post data ?id=123&amp;amp;confirm=1.&lt;br /&gt;
&lt;br /&gt;
Now suppose that Evil Hacker knows this, and wants to trick the administrator into deleting another user. (If Evil Hacker makes this request himself, he will see a permission denied error.)&lt;br /&gt;
&lt;br /&gt;
All the Hacker Needs to do is to put the link &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt; somewhere where the administrator will click on it. For example, they could send the Administrator an email with a link saying &amp;quot;Look at this cool YouTube video&amp;quot; but where the link actually goes to the delete URL. The Administrator may click on the link without checking where it goes, and when the Administrator clicks that link, user 123 really will be deleted.&lt;br /&gt;
&lt;br /&gt;
Or, more seriously, the student could put a post in a forum that Administrators will read, and in the forum post, put an &amp;lt;img src=&amp;quot;&amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;lt;/nowiki&amp;gt;&amp;quot; /&amp;gt;. That way, the moment the Administrator reads the forum, user 123 will be deleted.&lt;br /&gt;
&lt;br /&gt;
It is also possible to fake POST requests, you can simple put the form on external site and post a link pointing to that site on your Moodle server, it is also possible to use external flash instead of forms.&lt;br /&gt;
&lt;br /&gt;
It may be a bit surprising, but this type of attack may be used against servers behind firewall on private network. It is not important where is the exploiting code, you can attack any server users may access from their browsers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==How Moodle avoids this problem==&lt;br /&gt;
===Session key===&lt;br /&gt;
The most important protection is the concept of a &#039;&#039;&#039;sesskey&#039;&#039;&#039;, short for session key.&lt;br /&gt;
&lt;br /&gt;
When you log in, Moodle adds a random string to your session. Whever it prints a link or a button to perform a significant action, it adds the sesskey value to the submitted data. Before performing the action, it checks the sesskey value in the request with the one in the session, and the action is only performed if the two match.&lt;br /&gt;
&lt;br /&gt;
Therefore, the request to delete a user is actually something like &amp;lt;nowiki&amp;gt;http://example.com/moodle/user/delete.php?id=123&amp;amp;confirm=1&amp;amp;sesskey=E8i5BCxLJR&amp;lt;/nowiki&amp;gt;, and there is no way for Evil Hacker to know what the sesskey is, so they cannot construct an URL that tricks the admin into deleting a user.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Use HTTP correctly===&lt;br /&gt;
Web applications use HTTP to encode requests from the user. In HTTP, there are various types of request. The two most important are GET and POST.&lt;br /&gt;
&lt;br /&gt;
GET requests should be used for getting information. So, for example, viewing a user&#039;s profile should be a GET request.&lt;br /&gt;
&lt;br /&gt;
POST requests should be used for changing things in the application. For example deleting a user should be a POST request.&lt;br /&gt;
&lt;br /&gt;
When you click a link or load an image, it is always a GET request. When you submit a form, it is either a POST or a GET request, depending on the form.&lt;br /&gt;
&lt;br /&gt;
Moodle should only process changes in response to a POST request. If that is the case, then it does Evil Hacker no good to trick a user into clicking on a link or viewing an embedded image. They have to trick a user into clicking a form submit button, which is harder.&lt;br /&gt;
==What you need to do in your code==&lt;br /&gt;
Use the [[Form API]] whenever possible for handling HTML forms. This automatically checks the sesskey and request method for you.&lt;br /&gt;
&lt;br /&gt;
There are valid cases when using forms is not appropriate and you need to perform an action based on a parameter submitted via GET request - such as various action links. In this case, you have to manually include the sesskey among submitted parameters, and then make sure the submitted sesskey value is valid.&lt;br /&gt;
&lt;br /&gt;
Include the sesskey among the submitted parameters:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$action = new moodle_url(&#039;/admin/tool/do/something.php&#039;, [&#039;delete&#039; =&amp;gt; $id, &#039;sesskey&#039; =&amp;gt; sesskey()]);&lt;br /&gt;
echo html_writer::link($action, get_string(&#039;delete&#039;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the target script, make sure to check the submitted sesskey is correct before executing the operation:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$delete = optional_param(&#039;delete&#039;, null, PARAM_INT);&lt;br /&gt;
&lt;br /&gt;
if ($delete) {&lt;br /&gt;
    require_sesskey();&lt;br /&gt;
    // Do whatever you need to, like $DB-&amp;gt;delete_records(...) etc.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that when using standard elements like $OUTPUT-&amp;gt;continue_button() and other elements based on the single_button widget submitted via POST method, the sesskey can be implicitly added to submitted parameters. Still, it is your duty to explicitly check the submitted value is valid.&lt;br /&gt;
&lt;br /&gt;
== Ensure your code does not expose the sesskey inadvertently ==&lt;br /&gt;
There are various ways that the sesskey could be leaked, and if this happens then it opens the door to types of attacks that would be prevented by the sesskey.&lt;br /&gt;
&lt;br /&gt;
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&lt;br /&gt;
&lt;br /&gt;
There are broadly two ways it could be leaked, in the frontend and in the backend.&lt;br /&gt;
&lt;br /&gt;
=== Back end leaking ===&lt;br /&gt;
This is things such as the sesskey appearing in various server logs. If your infrastructure is locked down well this should not be a major concern.&lt;br /&gt;
&lt;br /&gt;
=== Front end leaking ===&lt;br /&gt;
The frontend includes ever showing the sesskey in the browser url bar or anywhere else visible to the end user. An example might be accidentally disclosing it during a screen sharing session. Another important&lt;br /&gt;
&lt;br /&gt;
=== Guidelines for removing the sesskey from visible urls ===&lt;br /&gt;
&lt;br /&gt;
# First don&#039;t remove the requirement for checking the sesskey if it is actually needed&lt;br /&gt;
# If the page doesn&#039;t change any state on the server then the check can be removed along with the query param&lt;br /&gt;
# If it does change state and is not a GET request, eg a post then it&#039;s ok as is&lt;br /&gt;
# If it is a GET request then it is ok as is as long as:&lt;br /&gt;
## It is not the primary http call, ie it is an ajax call or a sub request like an iframe&lt;br /&gt;
## If the request will ALWAYS do something very quickly and then redirect away. But generally speaking these should be a http post instead.&lt;br /&gt;
&lt;br /&gt;
==What you need to do as an administrator==&lt;br /&gt;
* This is really only a code issue, but try not to fall for Evil Hacker&#039;s tricks ;-).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Security]]&lt;br /&gt;
* [[Coding]]&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61671</id>
		<title>Serving files</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61671"/>
		<updated>2022-01-31T04:30:14Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Icons / pix */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are a lot of ways that files of various types can be served in Moodle and various API to help do it correctly. This page aims to link together all the various ways in one place with the pros and cons and best practice of each way.&lt;br /&gt;
== Factors to consider ==&lt;br /&gt;
=== Security ===&lt;br /&gt;
If your files must only be visible to people with certain roles then that is a clear indication that it should use the pluginfile API.&lt;br /&gt;
=== Performance and caching ===&lt;br /&gt;
One of the driving reasons for this page is making sure that any public files which can be cached are done so in the correct way so that they can be offloaded to a CDN like Cloudflare or Cloudfront or reverse proxy such as Varnish or nginx. &lt;br /&gt;
=== Maintenance ===&lt;br /&gt;
Some ways of serving files have a lower maintenance cost than others so it is better to use the best practice way of doing things below.&lt;br /&gt;
== Ways of serving files ==&lt;br /&gt;
=== Javascript files ===&lt;br /&gt;
For all javascript files whether files you have written yourself or 3rd party bundle libraries see: [[Javascript Modules]]&lt;br /&gt;
=== CSS files ===&lt;br /&gt;
[[Themes overview#CSS]]&lt;br /&gt;
=== Font files ===&lt;br /&gt;
[[Themes overview#Adding custom fonts]]&lt;br /&gt;
=== Icons / pix inside a theme ===&lt;br /&gt;
[[Themes overview#Making use of images]]&lt;br /&gt;
&lt;br /&gt;
=== Images in a plugin ===&lt;br /&gt;
TBA&lt;br /&gt;
&lt;br /&gt;
=== File API and pluginfile.php ===&lt;br /&gt;
For any files which have been uploaded into the Moodle File API and need to be served see [[File API#Serving files to users]]&lt;br /&gt;
&lt;br /&gt;
Note that this API can also be used for files which are not in the File API, such as files on disk, or files which are generated on demand.&lt;br /&gt;
=== Data format API ===&lt;br /&gt;
If you need to generate a structured file on demand such as a CSV or Excel it is generally best to use the [[Data formats]] API. The Data format API can also be used in conjunction with the pluginfile API.&lt;br /&gt;
=== RSS and Calendar export ===&lt;br /&gt;
There are dedicated API&#039;s for serving [[RSS API]] and [[Calendar API]] data.&lt;br /&gt;
=== Custom download.php endpoint ===&lt;br /&gt;
It is easy and temping to implement your own custom script which downloads or serves a file. In the majority of cases the same functionality could be done via the pluginfile.php API so you can remove a lot of boilerlate and get a lot of functionality for free by avoiding this approach.&lt;br /&gt;
=== File with a .well-known or preset url structure ===&lt;br /&gt;
Often to implement some sort of protocol which another service with interact with, the url you serve on must have a specific location. For example:&lt;br /&gt;
 /.well-known/password-change&lt;br /&gt;
Moodle currently does not yet have any proper generic routing framework to accept &#039;clean urls&#039; and divert them to the appropriate plugin for handling.&lt;br /&gt;
=== Direct from Apache / nginx ===&lt;br /&gt;
This is only included for completeness and in general you should almost never serve files of any type directly from your web server. As a rule of thumb you should be able to comment out the native file serving directives in your nginx config and everything should still work because all files are served through php rather than directly. This may seem like it would be worse for performance in some cases, but it is actually better as Moodle is designed to sit behind a reverse proxy caching server or a CDN like Cloudflare, Cloudfront etc.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61670</id>
		<title>Serving files</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61670"/>
		<updated>2022-01-31T04:14:47Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Custom download.php endpoint */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are a lot of ways that files of various types can be served in Moodle and various API to help do it correctly. This page aims to link together all the various ways in one place with the pros and cons and best practice of each way.&lt;br /&gt;
== Factors to consider ==&lt;br /&gt;
=== Security ===&lt;br /&gt;
If your files must only be visible to people with certain roles then that is a clear indication that it should use the pluginfile API.&lt;br /&gt;
=== Performance and caching ===&lt;br /&gt;
One of the driving reasons for this page is making sure that any public files which can be cached are done so in the correct way so that they can be offloaded to a CDN like Cloudflare or Cloudfront or reverse proxy such as Varnish or nginx. &lt;br /&gt;
=== Maintenance ===&lt;br /&gt;
Some ways of serving files have a lower maintenance cost than others so it is better to use the best practice way of doing things below.&lt;br /&gt;
== Ways of serving files ==&lt;br /&gt;
=== Javascript files ===&lt;br /&gt;
For all javascript files whether files you have written yourself or 3rd party bundle libraries see: [[Javascript Modules]]&lt;br /&gt;
=== CSS files ===&lt;br /&gt;
[[Themes overview#CSS]]&lt;br /&gt;
=== Font files ===&lt;br /&gt;
[[Themes overview#Adding custom fonts]]&lt;br /&gt;
=== Icons / pix ===&lt;br /&gt;
[[Themes overview#Making use of images]]&lt;br /&gt;
=== File API and pluginfile.php ===&lt;br /&gt;
For any files which have been uploaded into the Moodle File API and need to be served see [[File API#Serving files to users]]&lt;br /&gt;
&lt;br /&gt;
Note that this API can also be used for files which are not in the File API, such as files on disk, or files which are generated on demand.&lt;br /&gt;
=== Data format API ===&lt;br /&gt;
If you need to generate a structured file on demand such as a CSV or Excel it is generally best to use the [[Data formats]] API. The Data format API can also be used in conjunction with the pluginfile API.&lt;br /&gt;
=== RSS and Calendar export ===&lt;br /&gt;
There are dedicated API&#039;s for serving [[RSS API]] and [[Calendar API]] data.&lt;br /&gt;
=== Custom download.php endpoint ===&lt;br /&gt;
It is easy and temping to implement your own custom script which downloads or serves a file. In the majority of cases the same functionality could be done via the pluginfile.php API so you can remove a lot of boilerlate and get a lot of functionality for free by avoiding this approach.&lt;br /&gt;
&lt;br /&gt;
=== File with a .well-known or preset url structure ===&lt;br /&gt;
Often to implement some sort of protocol which another service with interact with, the url you serve on must have a specific location. For example:&lt;br /&gt;
 /.well-known/password-change&lt;br /&gt;
Moodle currently does not yet have any proper generic routing framework to accept &#039;clean urls&#039; and divert them to the appropriate plugin for handling.&lt;br /&gt;
&lt;br /&gt;
=== Direct from Apache / nginx ===&lt;br /&gt;
This is only included for completeness and in general you should almost never serve files of any type directly from your web server. As a rule of thumb you should be able to comment out the native file serving directives in your nginx config and everything should still work because all files are served through php rather than directly. This may seem like it would be worse for performance in some cases, but it is actually better as Moodle is designed to sit behind a reverse proxy caching server or a CDN like Cloudflare, Cloudfront etc.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61669</id>
		<title>Serving files</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61669"/>
		<updated>2022-01-31T04:11:09Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Performance and caching */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are a lot of ways that files of various types can be served in Moodle and various API to help do it correctly. This page aims to link together all the various ways in one place with the pros and cons and best practice of each way.&lt;br /&gt;
== Factors to consider ==&lt;br /&gt;
=== Security ===&lt;br /&gt;
If your files must only be visible to people with certain roles then that is a clear indication that it should use the pluginfile API.&lt;br /&gt;
=== Performance and caching ===&lt;br /&gt;
One of the driving reasons for this page is making sure that any public files which can be cached are done so in the correct way so that they can be offloaded to a CDN like Cloudflare or Cloudfront or reverse proxy such as Varnish or nginx. &lt;br /&gt;
&lt;br /&gt;
=== Maintenance ===&lt;br /&gt;
Some ways of serving files have a lower maintenance cost than others so it is better to use the best practice way of doing things below.&lt;br /&gt;
&lt;br /&gt;
== Ways of serving files ==&lt;br /&gt;
=== Javascript files ===&lt;br /&gt;
For all javascript files whether files you have written yourself or 3rd party bundle libraries see: [[Javascript Modules]]&lt;br /&gt;
=== CSS files ===&lt;br /&gt;
[[Themes overview#CSS]]&lt;br /&gt;
=== Font files ===&lt;br /&gt;
[[Themes overview#Adding custom fonts]]&lt;br /&gt;
=== Icons / pix ===&lt;br /&gt;
[[Themes overview#Making use of images]]&lt;br /&gt;
=== File API and pluginfile.php ===&lt;br /&gt;
For any files which have been uploaded into the Moodle File API and need to be served see [[File API#Serving files to users]]&lt;br /&gt;
&lt;br /&gt;
Note that this API can also be used for files which are not in the File API, such as files on disk, or files which are generated on demand.&lt;br /&gt;
=== Data format API ===&lt;br /&gt;
If you need to generate a structured file on demand such as a CSV or Excel it is generally best to use the [[Data formats]] API. The Data format API can also be used in conjunction with the pluginfile API.&lt;br /&gt;
=== RSS and Calendar export ===&lt;br /&gt;
There are dedicated API&#039;s for serving [[RSS API]] and [[Calendar API]] data.&lt;br /&gt;
=== Custom download.php endpoint ===&lt;br /&gt;
It is easy and temping to implement your own custom script which downloads or serves a file. In the majority of cases the same functionality could be done via the pluginfile.php API so you can remove a lot of boilerlate and get a lot of functionality for free by avoiding this approach.&lt;br /&gt;
=== Direct from Apache / nginx ===&lt;br /&gt;
This is only included for completeness and in general you should almost never serve files of any type directly from your web server. As a rule of thumb you should be able to comment out the native file serving directives in your nginx config and everything should still work because all files are served through php rather than directly. This may seem like it would be worse for performance in some cases, but it is actually better as Moodle is designed to sit behind a reverse proxy caching server or a CDN like Cloudflare, Cloudfront etc.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61668</id>
		<title>Serving files</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61668"/>
		<updated>2022-01-31T04:08:23Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Factors to consider */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are a lot of ways that files of various types can be served in Moodle and various API to help do it correctly. This page aims to link together all the various ways in one place with the pros and cons and best practice of each way.&lt;br /&gt;
== Factors to consider ==&lt;br /&gt;
=== Security ===&lt;br /&gt;
If your files must only be visible to people with certain roles then that is a clear indication that it should use the pluginfile API.&lt;br /&gt;
&lt;br /&gt;
=== Performance and caching ===&lt;br /&gt;
One of the driving reasons for this page is making sure that any public files which can be cached are done so in the correct way so that they can be offloaded to a CDN like Cloudflare or Cloudfront or reverse proxy such as Varnish or nginx. &lt;br /&gt;
&lt;br /&gt;
== Ways of serving files ==&lt;br /&gt;
=== Javascript files ===&lt;br /&gt;
For all javascript files whether files you have written yourself or 3rd party bundle libraries see: [[Javascript Modules]]&lt;br /&gt;
=== CSS files ===&lt;br /&gt;
[[Themes overview#CSS]]&lt;br /&gt;
=== Font files ===&lt;br /&gt;
[[Themes overview#Adding custom fonts]]&lt;br /&gt;
=== Icons / pix ===&lt;br /&gt;
[[Themes overview#Making use of images]]&lt;br /&gt;
=== File API and pluginfile.php ===&lt;br /&gt;
For any files which have been uploaded into the Moodle File API and need to be served see [[File API#Serving files to users]]&lt;br /&gt;
&lt;br /&gt;
Note that this API can also be used for files which are not in the File API, such as files on disk, or files which are generated on demand.&lt;br /&gt;
=== Data format API ===&lt;br /&gt;
If you need to generate a structured file on demand such as a CSV or Excel it is generally best to use the [[Data formats]] API. The Data format API can also be used in conjunction with the pluginfile API.&lt;br /&gt;
=== RSS and Calendar export ===&lt;br /&gt;
There are dedicated API&#039;s for serving [[RSS API]] and [[Calendar API]] data.&lt;br /&gt;
=== Custom download.php endpoint ===&lt;br /&gt;
It is easy and temping to implement your own custom script which downloads or serves a file. In the majority of cases the same functionality could be done via the pluginfile.php API so you can remove a lot of boilerlate and get a lot of functionality for free by avoiding this approach.&lt;br /&gt;
=== Direct from Apache / nginx ===&lt;br /&gt;
This is only included for completeness and in general you should almost never serve files of any type directly from your web server. As a rule of thumb you should be able to comment out the native file serving directives in your nginx config and everything should still work because all files are served through php rather than directly. This may seem like it would be worse for performance in some cases, but it is actually better as Moodle is designed to sit behind a reverse proxy caching server or a CDN like Cloudflare, Cloudfront etc.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61665</id>
		<title>Serving files</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61665"/>
		<updated>2022-01-31T04:02:55Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Custom download.php endpoint */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are a lot of ways that files of various types can be served in Moodle and various API to help do it correctly. This page aims to link together all the various ways in one place with the pros and cons and best practice of each way.&lt;br /&gt;
== Factors to consider ==&lt;br /&gt;
=== Security ===&lt;br /&gt;
=== Performance and caching ===&lt;br /&gt;
One of the driving&lt;br /&gt;
== Ways of serving files ==&lt;br /&gt;
=== Javascript files ===&lt;br /&gt;
For all javascript files whether files you have written yourself or 3rd party bundle libraries see: [[Javascript Modules]]&lt;br /&gt;
=== CSS files ===&lt;br /&gt;
[[Themes overview#CSS]]&lt;br /&gt;
=== Font files ===&lt;br /&gt;
[[Themes overview#Adding custom fonts]]&lt;br /&gt;
=== Icons / pix ===&lt;br /&gt;
[[Themes overview#Making use of images]]&lt;br /&gt;
=== File API and pluginfile.php ===&lt;br /&gt;
For any files which have been uploaded into the Moodle File API and need to be served see [[File API#Serving files to users]]&lt;br /&gt;
&lt;br /&gt;
Note that this API can also be used for files which are not in the File API, such as files on disk, or files which are generated on demand.&lt;br /&gt;
=== Data format API ===&lt;br /&gt;
If you need to generate a structured file on demand such as a CSV or Excel it is generally best to use the [[Data formats]] API. The Data format API can also be used in conjunction with the pluginfile API.&lt;br /&gt;
=== RSS and Calendar export ===&lt;br /&gt;
There are dedicated API&#039;s for serving [[RSS API]] and [[Calendar API]] data.&lt;br /&gt;
=== Custom download.php endpoint ===&lt;br /&gt;
It is easy and temping to implement your own custom script which downloads or serves a file. In the majority of cases the same functionality could be done via the pluginfile.php API so you can remove a lot of boilerlate and get a lot of functionality for free by avoiding this approach.&lt;br /&gt;
=== Direct from Apache / nginx ===&lt;br /&gt;
This is only included for completeness and in general you should almost never serve files of any type directly from your web server. As a rule of thumb you should be able to comment out the native file serving directives in your nginx config and everything should still work because all files are served through php rather than directly. This may seem like it would be worse for performance in some cases, but it is actually better as Moodle is designed to sit behind a reverse proxy caching server or a CDN like Cloudflare, Cloudfront etc.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61664</id>
		<title>Serving files</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Serving_files&amp;diff=61664"/>
		<updated>2022-01-31T03:58:33Z</updated>

		<summary type="html">&lt;p&gt;Brendanheywood: /* Custom download.php endpoint */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are a lot of ways that files of various types can be served in Moodle and various API to help do it correctly. This page aims to link together all the various ways in one place with the pros and cons and best practice of each way.&lt;br /&gt;
== Factors to consider ==&lt;br /&gt;
=== Security ===&lt;br /&gt;
=== Performance and caching ===&lt;br /&gt;
One of the driving&lt;br /&gt;
== Ways of serving files ==&lt;br /&gt;
=== Javascript files ===&lt;br /&gt;
For all javascript files whether files you have written yourself or 3rd party bundle libraries see: [[Javascript Modules]]&lt;br /&gt;
=== CSS files ===&lt;br /&gt;
[[Themes overview#CSS]]&lt;br /&gt;
=== Font files ===&lt;br /&gt;
[[Themes overview#Adding custom fonts]]&lt;br /&gt;
=== Icons / pix ===&lt;br /&gt;
[[Themes overview#Making use of images]]&lt;br /&gt;
=== File API and pluginfile.php ===&lt;br /&gt;
For any files which have been uploaded into the Moodle File API and need to be served see [[File API#Serving files to users]]&lt;br /&gt;
&lt;br /&gt;
Note that this API can also be used for files which are not in the File API, such as files on disk, or files which are generated on demand.&lt;br /&gt;
=== Data format API ===&lt;br /&gt;
If you need to generate a structured file on demand such as a CSV or Excel it is generally best to use the [[Data formats]] API. The Data format API can also be used in conjunction with the pluginfile API.&lt;br /&gt;
&lt;br /&gt;
=== RSS and Calendar export ===&lt;br /&gt;
See the [[RSS API]] and [[Calendar API]]&#039;s.&lt;br /&gt;
&lt;br /&gt;
=== Custom download.php endpoint ===&lt;br /&gt;
It is easy and temping to implement your own custom file which downloads or serves a file. In the majority of cases the same functionality could be done via the pluginfile.php API so you can remove a lot of boilerlate and get a lot of functionality for free by avoiding this approach.&lt;br /&gt;
=== Direct from Apache / nginx ===&lt;br /&gt;
This is only included for completeness and in general you should almost never serve files of any type directly from your web server. As a rule of thumb you should be able to comment out the native file serving directives in your nginx config and everything should still work because all files are served through php rather than directly. This may seem like it would be worse for performance in some cases, but it is actually better as Moodle is designed to sit behind a reverse proxy caching server or a CDN like Cloudflare, Cloudfront etc.&lt;/div&gt;</summary>
		<author><name>Brendanheywood</name></author>
	</entry>
</feed>