<?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=Jaked</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=Jaked"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Jaked"/>
	<updated>2026-07-16T19:41:00Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=MoodleNet_Integration&amp;diff=58469</id>
		<title>MoodleNet Integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=MoodleNet_Integration&amp;diff=58469"/>
		<updated>2021-03-10T06:57:46Z</updated>

		<summary type="html">&lt;p&gt;Jaked: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Project status ==&lt;br /&gt;
This project is complete and is included in the Moodle 3.9 release.&lt;br /&gt;
&lt;br /&gt;
== Project goals ==&lt;br /&gt;
This project aims to provide a workflow in Moodle core, allowing teachers to create course content by browsing and importing content from a MoodleNet instance into their Moodle site. &lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
MoodleNet is a content sharing platform Users can upload content and curate collections of content. A piece of content can be either a file or a link. Instructors using Moodle, and who have access to MoodleNet, should be able to use these resources in their Moodle courses easily. &lt;br /&gt;
&lt;br /&gt;
Users should be able to begin their journey in either the Moodle course (when logged in) or from MoodleNet (when browsing content there) but should always be taken back to Moodle to finalise the content creation process. In cases where the user started their journey in MoodleNet, they should be asked to select which course they want to import the resource into.&lt;br /&gt;
&lt;br /&gt;
There are several ways we’d like to be able to use these resources and links in Moodle:&lt;br /&gt;
# Import a resource or link into Moodle, from MoodleNet, and have the respective course modules (resource, url, others) handle the import by creating instances of themselves based on the received content.&lt;br /&gt;
# Specifically when importing Moodle backups, we’d like to be able to hook into the restore process, and restore the backup into our Moodle site.&lt;br /&gt;
&lt;br /&gt;
Neither of these workflows are supported in Moodle 3.8, via repositories, or via backup and restore, nor are the variants of the workflow. The concept of importing files and links into a course is currently implemented by course drag and drop, but this isn’t an API that can be used by other components. The project aims to achieve the same kind of workflow as course drag and drop, however.&lt;br /&gt;
&lt;br /&gt;
== Solution overview ==&lt;br /&gt;
* 3.9: Create a new admin_tool called ‘tool_moodlenet’ to handle the import workflow.&lt;br /&gt;
* 3.9: Create a way to extend the activity chooser footer, allowing plugins (the tool_moodlenet plugin in this case) to add html to the footer.&lt;br /&gt;
* 3.7 and 3.8: In addition to tool_moodlenet, provide a mod_moodlenet plugin, as a way to add MoodleNet into the legacy activity chooser.&lt;br /&gt;
&lt;br /&gt;
== Front end ==&lt;br /&gt;
Several pages need to be added to support the workflow:&lt;br /&gt;
- Confirmation page (to declare the start of the import and to ask the user to confirm their intent to continue).&lt;br /&gt;
- Course selection page (for when the user starts in MoodleNet and not in a Moodle course)&lt;br /&gt;
- Module handler selection page (where the user chooses what course module they’d like to use to handle the incoming content)&lt;br /&gt;
&lt;br /&gt;
The activity chooser should be modified to allow for plugins to extend the footer, via a new hook. This allows us to place the link to MoodleNet there, from within the admin tool, without violating any plugin communications guidelines. This will only apply to 3.9. See section below on 3.7 and 3.8 backend changes which explains how the MoodleNet activity is created in 3.7 and 3.8.&lt;br /&gt;
&lt;br /&gt;
== Back end ==&lt;br /&gt;
&lt;br /&gt;
The code in tool_moodlenet (and, for 3.7 and 3.8, mod_moodlenet) should reveal more detail on the concepts discussed here, so please see there if required. &lt;br /&gt;
&lt;br /&gt;
=== Changes for 3.9+: ===&lt;br /&gt;
==== Profile field ====&lt;br /&gt;
A user must enter their MoodleNet profile url as part of their Moodle profile. A profile field has been added to facilitate this.&lt;br /&gt;
&lt;br /&gt;
Sending users to MoodleNet from a course:&lt;br /&gt;
Sending the user to MoodleNet is handled by tool_moodlenet in the following way:&lt;br /&gt;
* The tool creates the MoodleNet endpoint URL to send the user to, based on the MoodleNet profile URL for that user. This takes the domain component of the user’s MoodleNet profile URL and appends ‘/lms/moodle/search/‘ (the default MoodleNet endpoint path) to it. It sets ‘site’, ‘course’, and ‘section’ params so that MoodleNet can temporarily store these and use them when the user selects a resource and wants to come back to Moodle.&lt;br /&gt;
&lt;br /&gt;
The resulting URL must be structured as follows (i.e. MoodleNet expects this):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
https://instance.moodle.net/lms/moodle/search?site=X&amp;amp;course=42&amp;amp;section=7&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ‘X’ must be the url-encoded site address.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
https://example.com/mymoodle&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
becomes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
https%3A%2F%2Fexample.com%2Fmymoodle&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See ‘generate_mnet_endpoint()’ for more information.&lt;br /&gt;
&lt;br /&gt;
* This endpoint redirection URL is supplied to core via the hook ‘custom_chooser_footer’ (i.e. tool_moodlenet_custom_chooser_footer), which is a hook allowing the  footer section of the activity chooser to be extended. This hook, as implemented by the tool, returns HTML containing this link. This is added to the activity chooser footer.&lt;br /&gt;
* When the user clicks the ‘MoodleNet’ icon in the footer, they are redirected to MoodleNet to resource selection.&lt;br /&gt;
&lt;br /&gt;
==== The import endpoint ====&lt;br /&gt;
Receiving the user, along with a selected resource back into Moodle is also handled by the admin tool code. An endpoint &#039;&#039;admin/tool/moodlenet/import.php&#039;&#039; exists within the tool, to handle incoming post requests from MoodleNet. This endpoint is public, and does not require a user be authenticated. Its purpose is to handle the POST request only, and redirect to a secured page (login required) via a GET call, leveraging the &#039;&#039;$wantsurl&#039;&#039; functionality of the login process. See Authentication for more details.&lt;br /&gt;
&lt;br /&gt;
This endpoint REQUIRES the following parameters from MoodleNet:&lt;br /&gt;
* &#039;&#039;resourceurl&#039;&#039;: the URL for the resource&lt;br /&gt;
* &#039;&#039;resourceinfo&#039;&#039;: a JSON string containing ‘name’ and ‘summary’ information about the resource.&lt;br /&gt;
&lt;br /&gt;
The following parameters can OPTIONALLY be sent:&lt;br /&gt;
* &#039;&#039;type&#039;&#039;: string telling Moodle how the resource should be handled. Supported options are ‘file’ and ‘link’. If not provided, Moodle will assume ‘link’ for all resources.&lt;br /&gt;
* &#039;&#039;course&#039;&#039;: integer id of the course to import into (if the user began the import process in a Moodle course)&lt;br /&gt;
* &#039;&#039;section&#039;&#039;: integer representing the course section num to import the resource into (if the user began the import process in a Moodle course)&lt;br /&gt;
&lt;br /&gt;
An example of the POST payload to this endpoint is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
resourceurl=https%3A%2F%2Finstance.moodle.net%2Fresource%2F64&amp;amp;resourceinfo=%7B%22name%22%3A%22name%22%2C%22summary%22%3A%22some+file+description%22%7D&amp;amp;type=file&amp;amp;course=1&amp;amp;section=4&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where:&lt;br /&gt;
* &#039;&#039;resourceurl&#039;&#039; corresponds to &amp;lt;code php&amp;gt;&lt;br /&gt;
https://instance.moodle.net/resource/64&amp;lt;/code&amp;gt; (just an example, not a real MoodleNet URL)&lt;br /&gt;
* &#039;&#039;resourceinfo&#039;&#039; corresponds to the JSON string:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{“name”:”name”,”summary”:”some file description”}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authentication ====&lt;br /&gt;
MoodleNet does not require users to be signed in to browse resources. Moodle requires users to both be signed in and to confirm their intent to import before any resource creation will occur. The POST is converted to a GET at the import endpoint, and a redirect issued to &#039;&#039;admin/tool/moodlenet/index.php&#039;&#039;, where the confirmation of import occurs. If a user is logged in to Moodle when commencing the import, they will be asked to confirm their intent on this page. If a user is not logged in to Moodle when starting the import, they will be presented will the login page, after which a redirect to the index page will occur, at which point they will need to confirm their intent to import. &lt;br /&gt;
&lt;br /&gt;
==== Content import handlers ====&lt;br /&gt;
The &#039;&#039;index.php&#039;&#039; authenticated entry point decides whether or not the content is going to be handled as a backup, or as a file or link resource. &lt;br /&gt;
&lt;br /&gt;
In the case of backups, the code just prepares the relevant information, and passed the user off to the restore process.&lt;br /&gt;
&lt;br /&gt;
In the case of files and links, creation of a course module is achieved by leveraging the existing course drag and drop hooks. These hooks allow course module plugins to register their intent to handle a certain file type, or link, during a drag and drop scenario. Please note: The ways these hooks are called directly from the tool_moodlenet breaks the plugin communication guidelines and is not recommended. Ideally, a formal import workflow (a separate workflow, similar to the way restore works) would be available for plugins to leverage. &lt;br /&gt;
&lt;br /&gt;
When the import is initiated, MoodleNet can provide a &#039;&#039;type&#039;&#039; parameter to Moodle, telling Moodle to treat the resource accordingly (see section above on the import endpoint). This dictates the import strategy and can be either file and link. Depending on the strategy set, the admin tool generates a list of course module plugins that support that strategy (file or link handlers). Once a module has been selected by the user during import, the code delegates how the content is displayed in the course to that specific plugin, via the &#039;&#039;dndupload_handle&#039;&#039; hook - the same functions used by drag and drop. This is all visible in admin/tool/moodlenet/classes/local/import_processor::process().&lt;br /&gt;
&lt;br /&gt;
=== Changes for 3.7 and 3.8: ===&lt;br /&gt;
The workflow is mostly the same as with 3.9, except in one area. In addition to the admin tool, 3.7 and 3.8 versions include mod_moodlenet, which presents a dummy MoodleNet activity in the activity chooser. This module overrides the mod edit form process, and instead redirects the user to the MoodleNet endpoint. The activity chooser footer extension hook is only available in 3.9, so this approach is a workaround for that limitation.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=MoodleNet_Integration&amp;diff=58468</id>
		<title>MoodleNet Integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=MoodleNet_Integration&amp;diff=58468"/>
		<updated>2021-03-10T06:03:54Z</updated>

		<summary type="html">&lt;p&gt;Jaked: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Project status ==&lt;br /&gt;
This project is complete and is included in the Moodle 3.9 release.&lt;br /&gt;
&lt;br /&gt;
== Project goals ==&lt;br /&gt;
This project aims to provide a workflow in Moodle core, allowing teachers to create course content by browsing and importing content from a MoodleNet instance into their Moodle site. &lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
MoodleNet is a content sharing platform Users can upload content and curate collections of content. A piece of content can be either a file or a link. Instructors using Moodle, and who have access to MoodleNet, should be able to use these resources in their Moodle courses easily. &lt;br /&gt;
&lt;br /&gt;
Users should be able to begin their journey in either the Moodle course (when logged in) or from MoodleNet (when browsing content there) but should always be taken back to Moodle to finalise the content creation process. In cases where the user started their journey in MoodleNet, they should be asked to select which course they want to import the resource into.&lt;br /&gt;
&lt;br /&gt;
There are several ways we’d like to be able to use these resources and links in Moodle:&lt;br /&gt;
# Import a resource or link into Moodle, from MoodleNet, and have the respective course modules (resource, url, others) handle the import by creating instances of themselves based on the received content.&lt;br /&gt;
# Specifically when importing Moodle backups, we’d like to be able to hook into the restore process, and restore the backup into our Moodle site.&lt;br /&gt;
&lt;br /&gt;
Neither of these workflows are supported in Moodle 3.8, via repositories, or via backup and restore, nor are the variants of the workflow. The concept of importing files and links into a course is currently implemented by course drag and drop, but this isn’t an API that can be used by other components. The project aims to achieve the same kind of workflow as course drag and drop, however.&lt;br /&gt;
&lt;br /&gt;
== Solution overview ==&lt;br /&gt;
* 3.9: Create a new admin_tool called ‘tool_moodlenet’ to handle the import workflow.&lt;br /&gt;
* 3.9: Create a way to extend the activity chooser footer, allowing plugins (the tool_moodlenet plugin in this case) to add html to the footer.&lt;br /&gt;
* 3.7 and 3.8: In addition to tool_moodlenet, provide a mod_moodlenet plugin, as a way to add MoodleNet into the legacy activity chooser.&lt;br /&gt;
&lt;br /&gt;
== Front end ==&lt;br /&gt;
Several pages need to be added to support the workflow:&lt;br /&gt;
- Confirmation page (to declare the start of the import and to ask the user to confirm their intent to continue).&lt;br /&gt;
- Course selection page (for when the user starts in MoodleNet and not in a Moodle course)&lt;br /&gt;
- Module handler selection page (where the user chooses what course module they’d like to use to handle the incoming content)&lt;br /&gt;
&lt;br /&gt;
The activity chooser should be modified to allow for plugins to extend the footer, via a new hook. This allows us to place the link to MoodleNet there, from within the admin tool, without violating any plugin communications guidelines. This will only apply to 3.9. See section below on 3.7 and 3.8 backend changes which explains how the MoodleNet activity is created in 3.7 and 3.8.&lt;br /&gt;
&lt;br /&gt;
== Back end ==&lt;br /&gt;
&lt;br /&gt;
The code in tool_moodlenet (and, for 3.7 and 3.8, mod_moodlenet) should reveal more detail on the concepts discussed here, so please see there if required. &lt;br /&gt;
&lt;br /&gt;
=== Changes for 3.9+: ===&lt;br /&gt;
==== Profile field ====&lt;br /&gt;
A user must enter their MoodleNet profile url as part of their Moodle profile. A profile field has been added to facilitate this.&lt;br /&gt;
&lt;br /&gt;
Sending users to MoodleNet from a course:&lt;br /&gt;
Sending the user to MoodleNet is handled by tool_moodlenet in the following way:&lt;br /&gt;
* The tool creates the MoodleNet endpoint URL to send the user to, based on the MoodleNet profile URL for that user. This takes the domain component of the user’s MoodleNet profile URL and appends ‘/lms/moodle/search/‘ (the default MoodleNet endpoint path) to it. It sets ‘site’, ‘course’, and ‘section’ params so that MoodleNet can temporarily store these and use them when the user selects a resource and wants to come back to Moodle.&lt;br /&gt;
&lt;br /&gt;
The resulting URL must be structured as follows (i.e. MoodleNet expects this):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
https://instance.moodle.net/lms/moodle/search?site=X&amp;amp;course=42&amp;amp;section=7&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where ‘X’ must be the url-encoded site address.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
https://example.com/mymoodle&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
becomes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
https%3A%2F%2Fexample.com%2Fmymoodle&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See ‘generate_mnet_endpoint()’ for more information.&lt;br /&gt;
&lt;br /&gt;
* This endpoint redirection URL is supplied to core via the hook ‘custom_chooser_footer’ (i.e. tool_moodlenet_custom_chooser_footer), which is a hook allowing the  footer section of the activity chooser to be extended. This hook, as implemented by the tool, returns HTML containing this link. This is added to the activity chooser footer.&lt;br /&gt;
* When the user clicks the ‘MoodleNet’ icon in the footer, they are redirected to MoodleNet to resource selection.&lt;br /&gt;
&lt;br /&gt;
==== The import endpoint ====&lt;br /&gt;
Receiving the user, along with a selected resource back into Moodle is also handled by the admin tool code. An endpoint &#039;&#039;admin/tool/moodlenet/import.php&#039;&#039; exists within the tool, to handle incoming post requests from MoodleNet. This endpoint is public, and does not require a user be authenticated. Its purpose is to handle the POST request only, and redirect to a secured page (login required) via a GET call, leveraging the &#039;&#039;$wantsurl&#039;&#039; functionality of the login process. See Authentication for more details.&lt;br /&gt;
&lt;br /&gt;
This endpoint REQUIRES the following parameters from MoodleNet:&lt;br /&gt;
* &#039;&#039;resourceurl&#039;&#039;: the URL for the resource&lt;br /&gt;
* &#039;&#039;resourceinfo&#039;&#039;: a JSON string containing ‘name’ and ‘summary’ information about the resource.&lt;br /&gt;
&lt;br /&gt;
The following parameters can OPTIONALLY be sent:&lt;br /&gt;
* &#039;&#039;type&#039;&#039;: string telling Moodle how the resource should be handled. Supported options are ‘file’ and ‘link’. If not provided, Moodle will assume ‘link’ for all resources.&lt;br /&gt;
* &#039;&#039;course&#039;&#039;: integer id of the course to import into (if the user began the import process in a Moodle course)&lt;br /&gt;
* &#039;&#039;section&#039;&#039;: integer representing the course section num to import the resource into (if the user began the import process in a Moodle course)&lt;br /&gt;
&lt;br /&gt;
An example of the POST payload to this endpoint is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
resourceurl=https%3A%2F%2Finstance.moodle.net%2Fresource%2F64&amp;amp;resourceinfo=%7B%22name%22%3A%22name%22%2C%22summary%22%3A%22some+file+description%22%7D&amp;amp;type=file&amp;amp;course=1&amp;amp;section=4&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where:&lt;br /&gt;
* &#039;&#039;resourceurl&#039;&#039; corresponds to &amp;lt;code php&amp;gt;&lt;br /&gt;
https://instance.moodle.net/resource/64&amp;lt;/code&amp;gt; (just an example, not a real MoodleNet URL)&lt;br /&gt;
* &#039;&#039;resource info&#039;&#039; corresponds to the JSON string:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{“name”:”name”,”summary”:”some file description”}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authentication ====&lt;br /&gt;
MoodleNet does not require users to be signed in to browse resources. Moodle requires users to both be signed in and to confirm their intent to import before any resource creation will occur. The POST is converted to a GET at the import endpoint, and a redirect issued to &#039;&#039;admin/tool/moodlenet/index.php&#039;&#039;, where the confirmation of import occurs. If a user is logged in to Moodle when commencing the import, they will be asked to confirm their intent on this page. If a user is not logged in to Moodle when starting the import, they will be presented will the login page, after which a redirect to the index page will occur, at which point they will need to confirm their intent to import. &lt;br /&gt;
&lt;br /&gt;
==== Content import handlers ====&lt;br /&gt;
The &#039;&#039;index.php&#039;&#039; authenticated entry point decides whether or not the content is going to be handled as a backup, or as a file or link resource. &lt;br /&gt;
&lt;br /&gt;
In the case of backups, the code just prepares the relevant information, and passed the user off to the restore process.&lt;br /&gt;
&lt;br /&gt;
In the case of files and links, creation of a course module is achieved by leveraging the existing course drag and drop hooks. These hooks allow course module plugins to register their intent to handle a certain file type, or link, during a drag and drop scenario. Please note: The ways these hooks are called directly from the tool_moodlenet breaks the plugin communication guidelines and is not recommended. Ideally, a formal import workflow (a separate workflow, similar to the way restore works) would be available for plugins to leverage. &lt;br /&gt;
&lt;br /&gt;
When the import is initiated, MoodleNet can provide a &#039;&#039;type&#039;&#039; parameter to Moodle, telling Moodle to treat the resource accordingly (see section above on the import endpoint). This dictates the import strategy and can be either file and link. Depending on the strategy set, the admin tool generates a list of course module plugins that support that strategy (file or link handlers). Once a module has been selected by the user during import, the code delegates how the content is displayed in the course to that specific plugin, via the &#039;&#039;dndupload_handle&#039;&#039; hook - the same functions used by drag and drop. This is all visible in admin/tool/moodlenet/classes/local/import_processor::process().&lt;br /&gt;
&lt;br /&gt;
=== Changes for 3.7 and 3.8: ===&lt;br /&gt;
The workflow is mostly the same as with 3.9, except in one area. In addition to the admin tool, 3.7 and 3.8 versions include mod_moodlenet, which presents a dummy MoodleNet activity in the activity chooser. This module overrides the mod edit form process, and instead redirects the user to the MoodleNet endpoint. The activity chooser footer extension hook is only available in 3.9, so this approach is a workaround for that limitation.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=MoodleNet_Integration&amp;diff=58464</id>
		<title>MoodleNet Integration</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=MoodleNet_Integration&amp;diff=58464"/>
		<updated>2021-03-10T05:47:04Z</updated>

		<summary type="html">&lt;p&gt;Jaked: Created page with &amp;quot;== Project status == This project is complete and is included in the Moodle 3.9 release.  == Project goals == This project aims to provide a workflow in Moodle core, allowing...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Project status ==&lt;br /&gt;
This project is complete and is included in the Moodle 3.9 release.&lt;br /&gt;
&lt;br /&gt;
== Project goals ==&lt;br /&gt;
This project aims to provide a workflow in Moodle core, allowing teachers to create course content by browsing and importing content from a MoodleNet instance into their Moodle site. &lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
MoodleNet is a content sharing platform Users can upload content and curate collections of content. A piece of content can be either a file or a link. Instructors using Moodle, and who have access to MoodleNet, should be able to use these resources in their Moodle courses easily. &lt;br /&gt;
&lt;br /&gt;
Users should be able to begin their journey in either the Moodle course (when logged in) or from MoodleNet (when browsing content there) but should always be taken back to Moodle to finalise the content creation process. In cases where the user started their journey in MoodleNet, they should be asked to select which course they want to import the resource into.&lt;br /&gt;
&lt;br /&gt;
There are several ways we’d like to be able to use these resources and links in Moodle:&lt;br /&gt;
# Import a resource or link into Moodle, from MoodleNet, and have the respective course modules (resource, url, others) handle the import by creating instances of themselves based on the received content.&lt;br /&gt;
# Specifically when importing Moodle backups, we’d like to be able to hook into the restore process, and restore the backup into our Moodle site.&lt;br /&gt;
&lt;br /&gt;
Neither of these workflows are supported in Moodle 3.8, via repositories, or via backup and restore, nor are the variants of the workflow. The concept of importing files and links into a course is currently implemented by course drag and drop, but this isn’t an API that can be used by other components. The project aims to achieve the same kind of workflow as course drag and drop, however.&lt;br /&gt;
&lt;br /&gt;
== Solution overview ==&lt;br /&gt;
* 3.9: Create a new admin_tool called ‘tool_moodlenet’ to handle the import workflow.&lt;br /&gt;
* 3.9: Create a way to extend the activity chooser footer, allowing plugins (the tool_moodlenet plugin in this case) to add html to the footer.&lt;br /&gt;
* 3.7 and 3.8: In addition to tool_moodlenet, provide a mod_moodlenet plugin, as a way to add MoodleNet into the legacy activity chooser.&lt;br /&gt;
&lt;br /&gt;
== Front end ==&lt;br /&gt;
Several pages need to be added to support the workflow:&lt;br /&gt;
- Confirmation page (to declare the start of the import and to ask the user to confirm their intent to continue).&lt;br /&gt;
- Course selection page (for when the user starts in MoodleNet and not in a Moodle course)&lt;br /&gt;
- Module handler selection page (where the user chooses what course module they’d like to use to handle the incoming content)&lt;br /&gt;
&lt;br /&gt;
The activity chooser should be modified to allow for plugins to extend the footer, via a new hook. This allows us to place the link to MoodleNet there, from within the admin tool, without violating any plugin communications guidelines. This will only apply to 3.9. See section below on 3.7 and 3.8 backend changes which explains how the MoodleNet activity is created in 3.7 and 3.8.&lt;br /&gt;
&lt;br /&gt;
== Back end ==&lt;br /&gt;
&lt;br /&gt;
The code in tool_moodlenet (and, for 3.7 and 3.8, mod_moodlenet) should reveal more detail on the concepts discussed here, so please see there if required. &lt;br /&gt;
&lt;br /&gt;
=== Changes for 3.9+: ===&lt;br /&gt;
==== Profile field ====&lt;br /&gt;
A user must enter their MoodleNet profile url as part of their Moodle profile. A profile field has been added to facilitate this.&lt;br /&gt;
&lt;br /&gt;
Sending users to MoodleNet from a course:&lt;br /&gt;
Sending the user to MoodleNet is handled by tool_moodlenet in the following way:&lt;br /&gt;
* The tool creates the MoodleNet endpoint URL to send the user to, based on the MoodleNet profile URL for that user. This takes the domain component of the user’s MoodleNet profile URL and appends ‘/lms/moodle/search/‘ (the default MoodleNet endpoint path) to it. It sets ‘site’, ‘course’, and ‘section’ params so that MoodleNet can temporarily store these and use them when the user selects a resource and wants to come back to Moodle.&lt;br /&gt;
&lt;br /&gt;
The resulting URL must be structured as follows (i.e. MoodleNet expects this):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;https://instance.moodle.net/lms/moodle/search?site=X&amp;amp;course=42&amp;amp;section=7&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Where ‘X’ must be the url-encoded site address.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&#039;&#039;https://example.com/mymoodle&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
becomes:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;https%3A%2F%2Fexample.com%2Fmymoodle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
See ‘generate_mnet_endpoint()’ for more information.&lt;br /&gt;
&lt;br /&gt;
* This endpoint redirection URL is supplied to core via the hook ‘custom_chooser_footer’ (i.e. tool_moodlenet_custom_chooser_footer), which is a hook allowing the  footer section of the activity chooser to be extended. This hook, as implemented by the tool, returns HTML containing this link. This is added to the activity chooser footer.&lt;br /&gt;
* When the user clicks the ‘MoodleNet’ icon in the footer, they are redirected to MoodleNet to resource selection.&lt;br /&gt;
&lt;br /&gt;
==== The import endpoint ====&lt;br /&gt;
Receiving the user, along with a selected resource back into Moodle is also handled by the admin tool code. An endpoint &#039;&#039;admin/tool/moodlenet/import.php&#039;&#039; exists within the tool, to handle incoming post requests from MoodleNet. This endpoint is public, and does not require a user be authenticated. Its purpose is to handle the POST request only, and redirect to a secured page (login required) via a GET call, leveraging the &#039;&#039;$wantsurl&#039;&#039; functionality of the login process. See Authentication for more details.&lt;br /&gt;
&lt;br /&gt;
This endpoint REQUIRES the following parameters from MoodleNet:&lt;br /&gt;
* &#039;&#039;resourceurl&#039;&#039;: the URL for the resource&lt;br /&gt;
* &#039;&#039;resourceinfo&#039;&#039;: a JSON string containing ‘name’ and ‘summary’ information about the resource.&lt;br /&gt;
&lt;br /&gt;
The following parameters can OPTIONALLY be sent:&lt;br /&gt;
* &#039;&#039;type&#039;&#039;: string telling Moodle how the resource should be handled. Supported options are ‘file’ and ‘link’. If not provided, Moodle will assume ‘link’ for all resources.&lt;br /&gt;
* &#039;&#039;course&#039;&#039;: integer id of the course to import into (if the user began the import process in a Moodle course)&lt;br /&gt;
* &#039;&#039;section&#039;&#039;: integer representing the course section num to import the resource into (if the user began the import process in a Moodle course)&lt;br /&gt;
&lt;br /&gt;
An example of the POST payload to this endpoint is as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;resourceurl=https%3A%2F%2Finstance.moodle.net%2Fresource%2F64&amp;amp;resourceinfo=%7B%22name%22%3A%22name%22%2C%22summary%22%3A%22some+file+description%22%7D&amp;amp;type=file&amp;amp;course=1&amp;amp;section=4&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Where:&lt;br /&gt;
* &#039;&#039;resourceurl&#039;&#039; corresponds to https://instance.moodle.net/resource/64 (just an example, not a real MoodleNet URL)&lt;br /&gt;
* &#039;&#039;resource info&#039;&#039; corresponds to the JSON string:&lt;br /&gt;
&#039;&#039;{“name”:”name”,”summary”:”some file description”}&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Authentication ====&lt;br /&gt;
MoodleNet does not require users to be signed in to browse resources. Moodle requires users to both be signed in and to confirm their intent to import before any resource creation will occur. The POST is converted to a GET at the import endpoint, and a redirect issued to &#039;&#039;admin/tool/moodlenet/index.php&#039;&#039;, where the confirmation of import occurs. If a user is logged in to Moodle when commencing the import, they will be asked to confirm their intent on this page. If a user is not logged in to Moodle when starting the import, they will be presented will the login page, after which a redirect to the index page will occur, at which point they will need to confirm their intent to import. &lt;br /&gt;
&lt;br /&gt;
==== Content import handlers ====&lt;br /&gt;
The &#039;&#039;index.php&#039;&#039; authenticated entry point decides whether or not the content is going to be handled as a backup, or as a file or link resource. &lt;br /&gt;
&lt;br /&gt;
In the case of backups, the code just prepares the relevant information, and passed the user off to the restore process.&lt;br /&gt;
&lt;br /&gt;
In the case of files and links, creation of a course module is achieved by leveraging the existing course drag and drop hooks. These hooks allow course module plugins to register their intent to handle a certain file type, or link, during a drag and drop scenario. Please note: The ways these hooks are called directly from the tool_moodlenet breaks the plugin communication guidelines and is not recommended. Ideally, a formal import workflow (a separate workflow, similar to the way restore works) would be available for plugins to leverage. &lt;br /&gt;
&lt;br /&gt;
When the import is initiated, MoodleNet can provide a &#039;&#039;type&#039;&#039; parameter to Moodle, telling Moodle to treat the resource accordingly (see section above on the import endpoint). This dictates the import strategy and can be either file and link. Depending on the strategy set, the admin tool generates a list of course module plugins that support that strategy (file or link handlers). Once a module has been selected by the user during import, the code delegates how the content is displayed in the course to that specific plugin, via the &#039;&#039;dndupload_handle&#039;&#039; hook - the same functions used by drag and drop. This is all visible in admin/tool/moodlenet/classes/local/import_processor::process().&lt;br /&gt;
&lt;br /&gt;
=== Changes for 3.7 and 3.8: ===&lt;br /&gt;
The workflow is mostly the same as with 3.9, except in one area. In addition to the admin tool, 3.7 and 3.8 versions include mod_moodlenet, which presents a dummy MoodleNet activity in the activity chooser. This module overrides the mod edit form process, and instead redirects the user to the MoodleNet endpoint. The activity chooser footer extension hook is only available in 3.9, so this approach is a workaround for that limitation.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Deprecation&amp;diff=58175</id>
		<title>Deprecation</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Deprecation&amp;diff=58175"/>
		<updated>2020-12-16T02:13:35Z</updated>

		<summary type="html">&lt;p&gt;Jaked: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== What is deprecation? ==&lt;br /&gt;
&lt;br /&gt;
[http://en.wikipedia.org/wiki/Deprecation Deprecation], in its programming sense, is the process of taking older code and marking it as no longer being useful within the codebase, usually because it has been superseded by newer code. The deprecated code is not immediately removed from the codebase because doing so may cause regression errors.&lt;br /&gt;
&lt;br /&gt;
== Why is deprecation needed? ==&lt;br /&gt;
&lt;br /&gt;
In an open source project, the end use of the codebase varies. People may have customisations and plugins that depend on a function that has been targeted for deprecation. Rather than simply removing a function, we must gracefully deprecate the function over a period covered by a number of released versions.&lt;br /&gt;
&lt;br /&gt;
== What is Moodle&#039;s deprecation policy? ==&lt;br /&gt;
&lt;br /&gt;
* Deprecations should only be on master, not on stables (exceptions may be made for some external service integrations)&lt;br /&gt;
* Deprecations apply to all public APIs, classes, and files.&lt;br /&gt;
* Removal of a function, class, or file may only be considered after a minimum of 4 major releases since the deprecation. Example: anything deprecated in 3.2 means that it will be removed in 3.6&lt;br /&gt;
* All deprecations should emit debugging notices where possible&lt;br /&gt;
* All deprecations should be noted in the relevant upgrade.txt&lt;br /&gt;
&lt;br /&gt;
== Moodle Core deprecation process ==&lt;br /&gt;
&lt;br /&gt;
Once it is decided that a function should be deprecated, a two-step process should be followed.&lt;br /&gt;
&lt;br /&gt;
Note that both steps should always happen as earlier as possible in the 6-months period  between major releases, so all developers will have time to adjust their code and ensure it will work in the next release. Obviously, no changes will be allowed after code freeze (the APIs must remain 100% unmodified after it).&lt;br /&gt;
&lt;br /&gt;
=== Step 1. Immediate action ===&lt;br /&gt;
&lt;br /&gt;
Deprecation affects only the current master version, in other words, the deprecation only becomes effective after the next [[Releases|major release]].&lt;br /&gt;
&lt;br /&gt;
* If the function is not a member of a class (in other words, it is an independent function), it should be moved, with its PHPDoc and all comments, to &#039;&#039;lib/deprecatedlib.php&#039;&#039;, which is included everywhere. If the function is a class member, it will need to be deprecated in its current location.&lt;br /&gt;
** Deprecated behat step definitions should be moved to lib/tests/behat/behat_deprecated.php, including a call to behat_deprecated::deprecated_message() proposing an alternative to the deprecated method.&lt;br /&gt;
* A debugging message should be added to the function so that, when [[:en:Debugging|developer debugging mode]] is on, attention is drawn to the deprecation. The message should state that the function being called has been deprecated. The message should help a developer whose code currently calls the function that has gone. Tell them what they should do instead.&lt;br /&gt;
&lt;br /&gt;
 debugging(&#039;foobar() is deprecated. Please use foobar::blah() instead.&#039;, DEBUG_DEVELOPER);&lt;br /&gt;
&lt;br /&gt;
* If the deprecated function has been replaced with a new function, ideally the new function should be called from the deprecated function, so that the new functionality is used. This will make maintenance easier moving forward.&lt;br /&gt;
* A &amp;lt;tt&amp;gt;@deprecated&amp;lt;/tt&amp;gt; tag should be added to the PHPDoc for the function description so that IDEs describing the function will note that it is deprecated, documenting which version it was deprecated in and the MDL issue associated with it. See the guidelines in [[Coding_style#.40deprecated_.28and_.40todo.29]].&lt;br /&gt;
* If the function is an external function, then an additional deprecation-specific method needs to be created and set to return true. See the docs on that process here: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Deprecation. You should continue to add the &amp;lt;tt&amp;gt;@deprecated since x.x&amp;lt;/tt&amp;gt; tag to the docs of all three of the relevant external methods (parameters, main method, returns) to make it clear to IDEs that the function is deprecated.&lt;br /&gt;
* There will need to be an issue associated with the initial part of the deprecation. A second issue needs to be created to finish the job. The first issue will be linked to second issue. The second issue needs to be a sub-task of an appropriate [https://tracker.moodle.org/issues/?jql=%28summary%20~%20%22meta%22%20or%20type%20%3D%20Epic%29%20AND%20summary%20~%20%22together%20deprecated%22%20order%20by%20created&amp;amp;runQuery=true&amp;amp;clear=true deprecation META]. For example, if the current version is 3.1.2, the function will be marked as deprecated in 3.2 and should normally be removed for 3.6, so the second issue should be an issue in a deprecation epic for the 3.6 version (MDL-54740). This second issue should include instructions on how to remove the function so that when it comes time to do so, the task is trivial for any developer.&lt;br /&gt;
* A &amp;lt;tt&amp;gt;@todo&amp;lt;/tt&amp;gt; tag can be added linking to the issues created for further action. (optional)&lt;br /&gt;
* A &amp;lt;tt&amp;gt;@see&amp;lt;/tt&amp;gt; tag can be added to point to the new apis that can be used. (optional)&lt;br /&gt;
* Check the body of the function being deprecated and look for additional function calls which have no other non-deprecated uses and may also be considered for deprecation. If they belong to the same code area they can be deprecated in the same issue.&lt;br /&gt;
* Last but not least, every deprecation should be documented in the corresponding upgrade.txt files &#039;&#039;&#039;at least&#039;&#039;&#039; once but, &#039;&#039;&#039;ideally&#039;&#039;&#039;,  both on this initial/immediate deprecation and also on the final deprecation/removal.&lt;br /&gt;
&lt;br /&gt;
Longer deprecation periods can be considered for functions that are widely used.&lt;br /&gt;
&lt;br /&gt;
=== Step 2. Final deprecation ===&lt;br /&gt;
&lt;br /&gt;
* If a function has been marked as deprecated for 3.x (eg. 3.1) and set for removal at 3.x+4 (eg. 3.5), soon after the release of 3.x+3.1 (eg. 3.4.1), the 3.x+4 deprecation META will be processed. This means that the deprecated function will undergo final deprecation before 3.x+4, but only in the master version. This allows any potential regressions caused by the final deprecation of the function to be exposed as soon as possible.&lt;br /&gt;
* When a function undergoes final deprecation, all content of the function should be removed. In the skeleton that remains, an error statement should be included that indicates that the function cannot be used anymore. You can also direct developers to the new function(s) in this message.&lt;br /&gt;
&lt;br /&gt;
 throw new coding_exception(&#039;check_potential_filename() can not be used any more, please use new file API&#039;);&lt;br /&gt;
&lt;br /&gt;
* All function parameters should be removed&lt;br /&gt;
* The content of the PHPDoc should be removed, leaving only the &amp;lt;tt&amp;gt;@deprecated&amp;lt;/tt&amp;gt; tag with the notice and, optionally, the replacement information. This includes all &amp;lt;tt&amp;gt;@param&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;@return&amp;lt;/tt&amp;gt;, and other tags, as well as the description.&lt;br /&gt;
* External functions deprecation process is different from the standard deprecation and functions should be completely removed.&lt;br /&gt;
* Last but not least, every deprecation should be documented in the corresponding upgrade.txt files &#039;&#039;&#039;at least&#039;&#039;&#039; once but, &#039;&#039;&#039;ideally&#039;&#039;&#039;,  both on the initial/immediate deprecation and also on this final deprecation/removal.&lt;br /&gt;
&lt;br /&gt;
== See also... ==&lt;br /&gt;
&lt;br /&gt;
* [[String deprecation]]&lt;br /&gt;
* [[Process]]&lt;br /&gt;
* [[Release process]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Processes]]&lt;br /&gt;
[[Category:Core development]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Activity_chooser&amp;diff=57888</id>
		<title>Activity chooser</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Activity_chooser&amp;diff=57888"/>
		<updated>2020-09-29T06:29:44Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Third party plugin developers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|state = In progress&lt;br /&gt;
|name = Activity chooser&lt;br /&gt;
|tracker = MDL-67255 (epic)&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=346664&lt;br /&gt;
|assignee = Team Alpha&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Through our road-map creation process of looking at highly voted tracker issues and relevant forum posts, as well as MUA interaction, an update to the activity chooser to simplify and make less intimidating, was chosen.&lt;br /&gt;
&lt;br /&gt;
MDL-57828 was created and worked on, but unfortunately stalled, and did not complete its way through the integration process. There is a substantial forum post [https://moodle.org/mod/forum/discuss.php?d=346664], with many suggestions and current pain points with the activity chooser. The MUA created a proposal issue (MDL-61511) to tackle the same issue.&lt;br /&gt;
&lt;br /&gt;
We have recently been analysing how course creation is achieved. Two main ideas were tested with focus groups to try and find the best away to approach course creation. With this information we are confident that we have a user focused design that will improve the activity chooser for everyone.&lt;br /&gt;
&lt;br /&gt;
We would like to invite everyone to express their opinion on this improvement. Course creation is a Moodle activity that is fundamental to teaching a course online, and we would like to ensure that the process is as easy and intuitive as possible.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The following are changes that we are planning on making in this project. We have a demo that can be viewed and interacted with.&lt;br /&gt;
[https://projects.invisionapp.com/share/SVSREPYNBYG#/screens/388682478 Invisio mockup of the activity chooser].&lt;br /&gt;
&lt;br /&gt;
Our current work can be viewed at [https://activitychooser.prototype.moodledemo.net/ activity chooser prototype]. Please take a look.&lt;br /&gt;
&lt;br /&gt;
=== Larger display area ===&lt;br /&gt;
&lt;br /&gt;
The activity chooser will be wider and have the activities in a grid format. This allows for more activities to be seen at once.&lt;br /&gt;
&lt;br /&gt;
=== Activities and resources are now merged ===&lt;br /&gt;
&lt;br /&gt;
Our research found that the distinction been activities and resources was not useful to teachers and so now these two categories have been merged together.&lt;br /&gt;
&lt;br /&gt;
=== Starred / Favourites tab ===&lt;br /&gt;
&lt;br /&gt;
The user can now select activities to be added to the Starred tab. The starred tab is shown by default to users when pulling up the activity chooser.&lt;br /&gt;
&lt;br /&gt;
[[File:activity-chooser-starred.png|The starred tab]]&lt;br /&gt;
&lt;br /&gt;
=== Recommended tab ===&lt;br /&gt;
&lt;br /&gt;
Site administrators will now be able to set a selection of activities as recommended. These recommended activities will show up in a tab in the activity chooser for the course creator to view. If no recommendations are made then this tab will not be displayed.&lt;br /&gt;
&lt;br /&gt;
[[File:activity-chooser-recommend.png|The recommended tab]]&lt;br /&gt;
&lt;br /&gt;
=== Smart search bar ===&lt;br /&gt;
&lt;br /&gt;
To help find activities from the activity chooser, we will be adding a search bar, that will search through both the names of the activities, and also the information text, to try and find relevant activities that the user may want.&lt;br /&gt;
&lt;br /&gt;
=== Other activity types ===&lt;br /&gt;
&lt;br /&gt;
Other activity types such as LTI will be able to be added to the activity chooser for the user to select.&lt;br /&gt;
&lt;br /&gt;
=== Activity information hidden ===&lt;br /&gt;
&lt;br /&gt;
The information about an activity will be accessible through the &#039;i&#039; icon. Clicking the link will show additional information about the activity. This will free up space for other activities to be shown rather than always taking up half of the activity chooser.&lt;br /&gt;
&lt;br /&gt;
[[File:activity-chooser-info.png|Additional information about an activity]]&lt;br /&gt;
&lt;br /&gt;
== Third party plugin developers == &lt;br /&gt;
Course module plugins can add items to the activity chooser by implementing the &#039;&#039;&#039;{plugin}_get_content_items()&#039;&#039;&#039; callback in their plugin lib (lib.php). This callback replaces the now deprecated &#039;&#039;&#039;{plugin}_get_shortcuts()&#039;&#039;&#039; method.&lt;br /&gt;
&lt;br /&gt;
In order for activity starring and recommendations to work, each content_item has an ID which is subject to some additional rules. Each ID:&lt;br /&gt;
* Must be unique to your component.&lt;br /&gt;
* Must not change.&lt;br /&gt;
* Must be of type integer.&lt;br /&gt;
&lt;br /&gt;
See &#039;&#039;&#039;lti_get_course_content_items()&#039;&#039;&#039; for an example implementation in core.&lt;br /&gt;
&lt;br /&gt;
Additionally, for recommendations to be made, plugins must implement the &#039;&#039;&#039;{plugin}_get_all_content_items()&#039;&#039;&#039; callback in their lib.php. This method must return a list of all content items that can be added across all courses.&lt;br /&gt;
&lt;br /&gt;
Developers who are currently using the deprecated &#039;&#039;&#039;{plugin}_get_shortcuts()&#039;&#039;&#039; callback should implement the new callback in their plugins as soon as possible. Whilst legacy items are still included (in cases where the new callback has yet to be implemented in the plugin), these items can not be starred, nor recommended. Eventually all support for the deprecated method will be removed, as per normal deprecation policy.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Activity_chooser&amp;diff=57622</id>
		<title>Activity chooser</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Activity_chooser&amp;diff=57622"/>
		<updated>2020-06-17T08:23:13Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Third party plugin developers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|state = In progress&lt;br /&gt;
|name = Activity chooser&lt;br /&gt;
|tracker = MDL-67255 (epic)&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=346664&lt;br /&gt;
|assignee = Team Alpha&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Through our road-map creation process of looking at highly voted tracker issues and relevant forum posts, as well as MUA interaction, an update to the activity chooser to simplify and make less intimidating, was chosen.&lt;br /&gt;
&lt;br /&gt;
MDL-57828 was created and worked on, but unfortunately stalled, and did not complete its way through the integration process. There is a substantial forum post [https://moodle.org/mod/forum/discuss.php?d=346664], with many suggestions and current pain points with the activity chooser. The MUA created a proposal issue (MDL-61511) to tackle the same issue.&lt;br /&gt;
&lt;br /&gt;
We have recently been analysing how course creation is achieved. Two main ideas were tested with focus groups to try and find the best away to approach course creation. With this information we are confident that we have a user focused design that will improve the activity chooser for everyone.&lt;br /&gt;
&lt;br /&gt;
We would like to invite everyone to express their opinion on this improvement. Course creation is a Moodle activity that is fundamental to teaching a course online, and we would like to ensure that the process is as easy and intuitive as possible.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The following are changes that we are planning on making in this project. We have a demo that can be viewed and interacted with.&lt;br /&gt;
[https://projects.invisionapp.com/share/SVSREPYNBYG#/screens/388682478 Invisio mockup of the activity chooser].&lt;br /&gt;
&lt;br /&gt;
Our current work can be viewed at [https://activitychooser.prototype.moodledemo.net/ activity chooser prototype]. Please take a look.&lt;br /&gt;
&lt;br /&gt;
=== Larger display area ===&lt;br /&gt;
&lt;br /&gt;
The activity chooser will be wider and have the activities in a grid format. This allows for more activities to be seen at once.&lt;br /&gt;
&lt;br /&gt;
=== Activities and resources are now merged ===&lt;br /&gt;
&lt;br /&gt;
Our research found that the distinction been activities and resources was not useful to teachers and so now these two categories have been merged together.&lt;br /&gt;
&lt;br /&gt;
=== Starred / Favourites tab ===&lt;br /&gt;
&lt;br /&gt;
The user can now select activities to be added to the Starred tab. The starred tab is shown by default to users when pulling up the activity chooser.&lt;br /&gt;
&lt;br /&gt;
[[File:activity-chooser-starred.png|The starred tab]]&lt;br /&gt;
&lt;br /&gt;
=== Recommended tab ===&lt;br /&gt;
&lt;br /&gt;
Site administrators will now be able to set a selection of activities as recommended. These recommended activities will show up in a tab in the activity chooser for the course creator to view. If no recommendations are made then this tab will not be displayed.&lt;br /&gt;
&lt;br /&gt;
[[File:activity-chooser-recommend.png|The recommended tab]]&lt;br /&gt;
&lt;br /&gt;
=== Smart search bar ===&lt;br /&gt;
&lt;br /&gt;
To help find activities from the activity chooser, we will be adding a search bar, that will search through both the names of the activities, and also the information text, to try and find relevant activities that the user may want.&lt;br /&gt;
&lt;br /&gt;
=== Other activity types ===&lt;br /&gt;
&lt;br /&gt;
Other activity types such as LTI will be able to be added to the activity chooser for the user to select.&lt;br /&gt;
&lt;br /&gt;
=== Activity information hidden ===&lt;br /&gt;
&lt;br /&gt;
The information about an activity will be accessible through the &#039;i&#039; icon. Clicking the link will show additional information about the activity. This will free up space for other activities to be shown rather than always taking up half of the activity chooser.&lt;br /&gt;
&lt;br /&gt;
[[File:activity-chooser-info.png|Additional information about an activity]]&lt;br /&gt;
&lt;br /&gt;
== Third party plugin developers == &lt;br /&gt;
Course module plugins can add items to the activity chooser by implementing the &#039;&#039;&#039;{plugin}_get_content_items()&#039;&#039;&#039; callback in their plugin lib (lib.php). This callback replaces the now deprecated &#039;&#039;&#039;{plugin}_get_shortcuts()&#039;&#039;&#039; method.&lt;br /&gt;
&lt;br /&gt;
In order for activity starring and recommendations to work, each content_item has an ID which is subject to some additional rules. Each ID:&lt;br /&gt;
* Must be unique to your component.&lt;br /&gt;
* Must not change.&lt;br /&gt;
* Must be of type integer.&lt;br /&gt;
&lt;br /&gt;
See &#039;&#039;&#039;lti_get_course_content_items()&#039;&#039;&#039; for an example implementation in core.&lt;br /&gt;
&lt;br /&gt;
Developers who are currently using the deprecated &#039;&#039;&#039;{plugin}_get_shortcuts()&#039;&#039;&#039; callback should implement the new callback in their plugins as soon as possible. Whilst legacy items are still included (in cases where the new callback has yet to be implemented in the plugin), these items can not be starred, nor recommended. Eventually all support for the deprecated method will be removed, as per normal deprecation policy.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=55964</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=55964"/>
		<updated>2019-04-23T07:59:25Z</updated>

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

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

		<summary type="html">&lt;p&gt;Jaked: /* Other General APIs */&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;
&lt;br /&gt;
These APIs are critical and will be used by nearly every Moodle plugin.&lt;br /&gt;
&lt;br /&gt;
=== Access API (access) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Data manipulation API (dml) ===&lt;br /&gt;
&lt;br /&gt;
The [[Data manipulation API]] allows you to read/write to databases in a consistent and safe way.&lt;br /&gt;
&lt;br /&gt;
=== File API (files) ===&lt;br /&gt;
&lt;br /&gt;
The [[File API]] controls the storage of files in connection to various plugins.&lt;br /&gt;
&lt;br /&gt;
=== Form API (form) ===&lt;br /&gt;
&lt;br /&gt;
The [[Form API]] defines and handles user data via web forms.&lt;br /&gt;
&lt;br /&gt;
=== Logging API (log) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] API allows you to log events in Moodle, while [[Logging 2]] describes how logs are stored and retrieved.&lt;br /&gt;
&lt;br /&gt;
=== Navigation API (navigation) ===&lt;br /&gt;
&lt;br /&gt;
The [[Navigation API]] allows you to manipulate the navigation tree to add and remove items as you wish.&lt;br /&gt;
&lt;br /&gt;
=== Page API (page) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Output API (output) ===&lt;br /&gt;
&lt;br /&gt;
The [[Output API]] is used to render the HTML for all parts of the page.&lt;br /&gt;
&lt;br /&gt;
=== String API (string) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Upgrade API (upgrade) ===&lt;br /&gt;
&lt;br /&gt;
The [[Upgrade API]] is how your module installs and upgrades itself, by keeping track of its own version.&lt;br /&gt;
&lt;br /&gt;
=== Moodlelib API (core) ===&lt;br /&gt;
&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;
&lt;br /&gt;
==Other General APIs==&lt;br /&gt;
&lt;br /&gt;
=== Admin settings (admin) ===&lt;br /&gt;
&lt;br /&gt;
The [[Admin settings]] API deals with providing configuration options for each plugin and Moodle core.&lt;br /&gt;
&lt;br /&gt;
=== Analytics API (analytics) ===&lt;br /&gt;
&lt;br /&gt;
The [[Analytics API]] allow you to create prediction models and generate insights.&lt;br /&gt;
&lt;br /&gt;
=== Availability (availability) ===&lt;br /&gt;
&lt;br /&gt;
The [[Availability API]] controls access to activities and sections.&lt;br /&gt;
&lt;br /&gt;
=== Backup API (backup) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Cache API (cache) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Calendar API (calendar) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Comment API (comment) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Competency API (competency) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Data definition API (ddl) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Editor API ===&lt;br /&gt;
&lt;br /&gt;
The [[Editor API]] is used to control HTML text editors.&lt;br /&gt;
&lt;br /&gt;
=== Enrolment API (enrol) ===&lt;br /&gt;
&lt;br /&gt;
The [[Enrolment API]] deals with course participants.&lt;br /&gt;
&lt;br /&gt;
=== Events API (event) ===&lt;br /&gt;
&lt;br /&gt;
The [[Event 2]] 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;
&lt;br /&gt;
=== External functions API (external) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Favourites API ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Lock API (lock) ===&lt;br /&gt;
&lt;br /&gt;
The [[Lock API]] lets you synchronise processing between multiple requests, even for separate nodes in a cluster.&lt;br /&gt;
&lt;br /&gt;
=== Message API (message) ===&lt;br /&gt;
&lt;br /&gt;
The [[Message API]] lets you post messages to users.  They decide how they want to receive them.&lt;br /&gt;
&lt;br /&gt;
=== Media API (media) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== My profile API ===&lt;br /&gt;
&lt;br /&gt;
The [[My profile API]] is used to add things to the profile page.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2 API (oauth2) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Preference API (preference) ===&lt;br /&gt;
&lt;br /&gt;
The [[Preference API]] is a simple way to store and retrieve preferences for individual users.&lt;br /&gt;
&lt;br /&gt;
=== Portfolio API (portfolio) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Privacy API (privacy) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Rating API (rating) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== RSS API (rss) ===&lt;br /&gt;
&lt;br /&gt;
The [[RSS API]] allows you to create secure RSS feeds of data in your module.&lt;br /&gt;
&lt;br /&gt;
=== Search API (search) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Tag API (tag) ===&lt;br /&gt;
&lt;br /&gt;
The [[Tag API]] allows you to store tags (and a tag cloud) to items in your module.&lt;br /&gt;
&lt;br /&gt;
=== Task API (task) ===&lt;br /&gt;
&lt;br /&gt;
The [[Task API]] lets you run jobs in the background. Either once off, or on a regular schedule.&lt;br /&gt;
&lt;br /&gt;
=== Time API (time) ===&lt;br /&gt;
&lt;br /&gt;
The [[Time API]] takes care of translating and displaying times between users in the site.&lt;br /&gt;
&lt;br /&gt;
=== Testing API (test) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== User-related APIs (user) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Web services API (webservice) ===&lt;br /&gt;
&lt;br /&gt;
The [[Web services API]] allows you to expose particular functions (usually external functions) as web services.&lt;br /&gt;
&lt;br /&gt;
=== Badges API (OpenBadges) ===&lt;br /&gt;
&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;
&lt;br /&gt;
== Activity module APIs ==&lt;br /&gt;
&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;
&lt;br /&gt;
=== Activity completion API (completion) ===&lt;br /&gt;
&lt;br /&gt;
The [[Activity completion API]] is to indicate to the system how activities are completed.&lt;br /&gt;
&lt;br /&gt;
=== Advanced grading API (grading) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Conditional activities API (condition) - deprecated in 2.7 ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Groups API (group) ===&lt;br /&gt;
&lt;br /&gt;
The [[Groups API]] allows you to check the current activity group mode and set the current group.&lt;br /&gt;
&lt;br /&gt;
=== Gradebook API (grade) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Plagiarism API (plagiarism) ===&lt;br /&gt;
&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;
&lt;br /&gt;
=== Question API (question) ===&lt;br /&gt;
&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;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55132</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55132"/>
		<updated>2018-11-29T03:29:38Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
===What is a favourite?===&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
Assuming you have a user context, you can get a service scoped to a single user with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Most of the time, you should ask the service to find favourite items for you. Sometimes, however, rather than fetching the favourites from the service, you&#039;ll just want to include the relevant information in those records from an existing query. You might want to do this if dealing with performance sensitive code where additional queries are undesirable.&lt;br /&gt;
&lt;br /&gt;
The service lets you do this too, by providing the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;);&lt;br /&gt;
&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55131</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55131"/>
		<updated>2018-11-29T03:27:28Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
===What is a favourite?===&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
Assuming you have a user context, you can get a service scoped to a single user with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Most of the time, you should ask the service to find favourite items for you. Sometimes, however, rather than fetching the favourites from the service, you&#039;ll just want to include the relevant information in those records from an existing query. You might want to do this if dealing with performance sensitive code where additional queries are undesirable.&lt;br /&gt;
&lt;br /&gt;
The service lets you do this too, by providing the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55130</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55130"/>
		<updated>2018-11-29T03:27:07Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
===What is a favourite?===&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
Assuming you have a user context, you can get a service scoped to a single user with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Most of the time, you should ask the service to find favourite items for you. Sometimes, however, rather than fetching the favourites from the service, you&#039;ll just want to include the relevant information in those records from an existing query. You might want to do this if dealing with performance sensitive code where additional queries would be unwelcome.&lt;br /&gt;
&lt;br /&gt;
The service lets you do this too, by providing the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55129</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55129"/>
		<updated>2018-11-29T03:25:23Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
===What is a favourite?===&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
Assuming you have a user context, you can get a service scoped to a single user with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Most of the time, you should ask the service to find favourite items for you. Sometimes, however, rather than fetching the favourites from the service, you&#039;ll just want to include the relevant information in those records from an existing query. The service lets you do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55128</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55128"/>
		<updated>2018-11-29T03:22:46Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
===What is a favourite?===&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
Assuming you have a user context, you can get a service scoped to a single user with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Sometimes, rather than fetching the favourites via the above methods, you&#039;ll just want to include the relevant information against records in an existing query. You can do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55127</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55127"/>
		<updated>2018-11-29T03:20:59Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Getting a service object */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
Assuming you have a user context, you can get a service scoped to a single user with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Sometimes, rather than fetching the favourites via the above methods, you&#039;ll just want to include the relevant information against records in an existing query. You can do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55126</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55126"/>
		<updated>2018-11-29T03:17:09Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Sometimes, rather than fetching the favourites via the above methods, you&#039;ll just want to include the relevant information against records in an existing query. You can do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning the ids and names of all courses within a given course category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, c.name, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND favouriteid IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55125</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55125"/>
		<updated>2018-11-29T03:15:41Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Sometimes, rather than fetching the favourites via the above methods, you&#039;ll just want to include the relevant information against records in an existing query. You can do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning a list of courses in a given category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
       $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND fav.id IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55124</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55124"/>
		<updated>2018-11-29T03:15:20Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Including favourites in external queries */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Sometimes, rather than fetching the favourites via the above methods, you&#039;ll just want to include the relevant information against records in an existing query. You can do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning a list of courses in a given category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id, fav.id as favouriteid&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
               $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.&lt;br /&gt;
&lt;br /&gt;
If you wish to select ONLY favourites, adding &amp;quot;AND fav.id IS NOT NULL&amp;quot; to the query will achieve this.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55123</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55123"/>
		<updated>2018-11-29T03:10:24Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Using the API */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Including favourites in external queries===&lt;br /&gt;
Sometimes, rather than fetching the favourites via the above methods, you&#039;ll just want to include the relevant information against records in an existing query. You can do this too.&lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
which can be used in such cases.&lt;br /&gt;
&lt;br /&gt;
For example, and for simplicity, let&#039;s say we have a query returning a list of courses in a given category:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT c.id&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3];&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
we can then modify this using the get_join_sql_by_type() result to include favourite information.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
list($favsql, $favparams) = $ufservice-&amp;gt;get_join_sql_by_type(&#039;core_course&#039;, &#039;courses&#039;, &#039;favalias&#039;, &#039;c.id&#039;)&lt;br /&gt;
$sql = &amp;quot;SELECT c.id&lt;br /&gt;
          FROM {course} c&lt;br /&gt;
               $favsql&lt;br /&gt;
         WHERE c.category = :category&amp;quot;;&lt;br /&gt;
$params = [&#039;category&#039; =&amp;gt; 3] + $favparams;&lt;br /&gt;
$courses = $DB-&amp;gt;get_records_sql($sql, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55122</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55122"/>
		<updated>2018-11-29T02:45:48Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Deleting a favourite */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55121</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55121"/>
		<updated>2018-11-29T02:43:17Z</updated>

		<summary type="html">&lt;p&gt;Jaked: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting a favourite===&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can remove the favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
$ufservice-&amp;gt;delete_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55120</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55120"/>
		<updated>2018-11-29T02:30:46Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Getting a service */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service object===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55119</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55119"/>
		<updated>2018-11-29T02:30:25Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55118</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55118"/>
		<updated>2018-11-29T02:29:49Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Reading favourites */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;br /&gt;
There are several read actions supported by the service object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;&lt;br /&gt;
public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;&lt;br /&gt;
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;&lt;br /&gt;
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55117</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55117"/>
		<updated>2018-11-29T01:12:36Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Creating a favourite */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
Let&#039;s say we want to set a course as a favourite. Note: In core, this is done by using the favourite &#039;&#039;type&#039;&#039; {&#039;core_course&#039;, &#039;courses&#039;}. &lt;br /&gt;
&lt;br /&gt;
The service provides the method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, int $ordering = null) : favourite;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
So, assuming we have the courseid and course context, we can create our favourite with:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$favourite = $ufservice-&amp;gt;create_favourite(&#039;core_course&#039;, &#039;courses&#039;, $course-&amp;gt;id, $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The returned $favourite is an object of type \core_favourites\local\entity\favourite.&lt;br /&gt;
&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55116</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55116"/>
		<updated>2018-11-29T01:00:46Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Getting a service */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object. The returned $ufservice is an object of type \core_favourites\local\service\user_favourite_service.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55115</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55115"/>
		<updated>2018-11-29T00:50:32Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* High level overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===Getting a service===&lt;br /&gt;
Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.&lt;br /&gt;
&lt;br /&gt;
To get a service scoped to a single user:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where $usercontext is the \context_user object.&lt;br /&gt;
&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55114</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55114"/>
		<updated>2018-11-29T00:09:44Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Identifying items */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===High level overview===&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55113</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55113"/>
		<updated>2018-11-29T00:04:21Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Identifying items */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain &#039;&#039;type&#039;&#039; (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===High level overview===&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55112</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55112"/>
		<updated>2018-11-28T23:55:52Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Identifying items */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.&lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain type (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===High level overview===&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55111</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55111"/>
		<updated>2018-11-28T23:53:35Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Identifying items */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;. You will see these in a range of API calls.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or a conversation id, or the id of any entity in Moodle. &lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain type (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===High level overview===&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55110</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55110"/>
		<updated>2018-11-28T23:39:04Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Identifying items */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
The &#039;&#039;&#039;itemid&#039;&#039;&#039; is a unique integer identifier of the item itself. This might be a course id, or a conversation id, or the id of any entity in Moodle. &lt;br /&gt;
&lt;br /&gt;
The two fields &#039;&#039;&#039;component&#039;&#039;&#039; and &#039;&#039;&#039;itemtype&#039;&#039;&#039; make up a pairing representing the &#039;&#039;type&#039;&#039; of each favourite. Within this pair, the &#039;&#039;&#039;component&#039;&#039;&#039; must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The &#039;&#039;&#039;itemtype&#039;&#039;&#039; can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;contextid&#039;&#039;&#039; is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain type (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===High level overview===&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55107</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55107"/>
		<updated>2018-11-28T05:56:08Z</updated>

		<summary type="html">&lt;p&gt;Jaked: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
The itemid is a unique integer identifier of the item itself. This might be a course id, or a conversation id, or the id of any entity in Moodle. &lt;br /&gt;
&lt;br /&gt;
The two fields component and itemtype make up a pairing representing the &#039;type&#039; of each favourite. Within this pair, the component must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The itemtype can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The contextid is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain type (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;br /&gt;
&lt;br /&gt;
==Using the API==&lt;br /&gt;
===High level overview===&lt;br /&gt;
===Creating a favourite===&lt;br /&gt;
===Reading favourites===&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55106</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55106"/>
		<updated>2018-11-28T05:54:04Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Favourites API usage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
The itemid is a unique integer identifier of the item itself. This might be a course id, or a conversation id, or the id of any entity in Moodle. &lt;br /&gt;
&lt;br /&gt;
The two fields component and itemtype make up a pairing representing the &#039;type&#039; of each favourite. Within this pair, the component must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The itemtype can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The contextid is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain type (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55105</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55105"/>
		<updated>2018-11-28T05:53:24Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Favourites API usage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
==Favourites API usage==&lt;br /&gt;
===What can be marked as a favourite?===&lt;br /&gt;
Almost any &#039;item&#039; can be marked as a favourite, provided it is something which can be identified by a unique integer id.&lt;br /&gt;
===Identifying items===&lt;br /&gt;
In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: &#039;&#039;&#039;component&#039;&#039;&#039;, &#039;&#039;&#039;itemtype&#039;&#039;&#039;, &#039;&#039;&#039;itemid&#039;&#039;&#039; and &#039;&#039;&#039;contextid&#039;&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
The itemid is a unique integer identifier of the item itself. This might be a course id, or a conversation id, or the id of any entity in Moodle. &lt;br /&gt;
&lt;br /&gt;
The two fields component and itemtype make up a pairing representing the &#039;type&#039; of each favourite. Within this pair, the component must be a valid [[Frankenstyle|frankenstyle]] component name and is the name of the component wishing to set/unset the item as a favourite. The itemtype can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. &lt;br /&gt;
&lt;br /&gt;
The contextid is the id of the context in which the item is being marked as a favourite. For example, a user&#039;s course might be marked as a favourite at the course context, whereas a user&#039;s conversation with another user might be marked as a favourite at the user context. It&#039;s also possible that items of a certain type (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case where we have a group conversation which is linked to a course group, and an individual conversation. Marking the group conversation as a favourite would require a course context to be used, whereas doing the same for the individual conversation would require a user context. This is entirely up to the component storing the favourite to manage.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55104</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55104"/>
		<updated>2018-11-28T03:31:08Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourite items, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
&lt;br /&gt;
==Favourites API usage==&lt;br /&gt;
An item can be anything and is identified by a {component, itemtype} pair. The component must be a valid frankenstyle component name and should be the name of the component wishing to store the favourite. The itemtype can be any identifying string, provided it is unique within the respective component.&lt;br /&gt;
E.g. &lt;br /&gt;
{&#039;core_course&#039;, &#039;courses&#039;} is used in core to as a key to identify those courses which have been starred for a given user.&lt;br /&gt;
&lt;br /&gt;
Additionally, the API supports the marking of items as favourites in different contexts, using a &#039;contextid&#039; parameter.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55089</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55089"/>
		<updated>2018-11-27T06:21:44Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API allows you to favourite items for a given user, a notion akin to adding a web page to favourites (or bookmarks) in your browser, or marking someone in your contacts as a favourite. The API provides a means to create, read, update and delete favourites, allowing any component to favourite arbitrary items as they see fit.&lt;br /&gt;
==Favourites API usage==&lt;br /&gt;
An item can be anything and is identified by a {component, itemtype} pair. The component must be a valid frankenstyle component name and should be the name of the component wishing to store the favourite. The itemtype can be any identifying string, provided it is unique within the respective component.&lt;br /&gt;
E.g. &lt;br /&gt;
{&#039;core_course&#039;, &#039;courses&#039;} is used in core to as a key to identify those courses which have been starred for a given user.&lt;br /&gt;
&lt;br /&gt;
Additionally, the API supports the marking of items as favourites in different contexts, using a &#039;contextid&#039; parameter.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55084</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55084"/>
		<updated>2018-11-27T05:27:11Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API provides a means to create, read, update and delete favourite items for users.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55083</id>
		<title>Favourites API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Favourites_API&amp;diff=55083"/>
		<updated>2018-11-27T05:26:07Z</updated>

		<summary type="html">&lt;p&gt;Jaked: Created page with &amp;quot;==Overview== The favourites API provides a means to create, read, update and delete favourites items for users.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The favourites API provides a means to create, read, update and delete favourites items for users.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Frankenstyle&amp;diff=55081</id>
		<title>Frankenstyle</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Frankenstyle&amp;diff=55081"/>
		<updated>2018-11-27T03:45:18Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Core subsystems */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;Frankenstyle component names&#039; refers to the naming convention that is used to uniquely identify a Moodle plugin based on the type of plugin and its name.  They are used throughout the Moodle code (with a notable exception being the css class names in the themes).&lt;br /&gt;
&lt;br /&gt;
Martin Dougiamas invented the word &#039;frankenstyle&#039; to describe this naming system which was invented by Petr Škoda.&lt;br /&gt;
&lt;br /&gt;
== Format ==&lt;br /&gt;
&lt;br /&gt;
Frankenstyle component names have a prefix and then a folder name, separated by an underscore. &lt;br /&gt;
&lt;br /&gt;
# The prefix is determined by the type of plugin. For example, the prefix for an activity module is &#039;&#039;&#039;mod&#039;&#039;&#039;.&lt;br /&gt;
# The name is the folder name of the plugin, always lower case.  For example, the name for Quiz is &#039;&#039;&#039;quiz&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
So the frankenstyle component name for the quiz module is &#039;&#039;&#039;mod_quiz&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Plugin types ==&lt;br /&gt;
&lt;br /&gt;
See [[Plugin types]] page for the list of all supported plugin types in Moodle and their frankenstyle prefix.&lt;br /&gt;
&lt;br /&gt;
To get a definitive list in your version of Moodle 2.x, use a small Moodle script with &amp;lt;tt&amp;gt;print_object(get_plugin_types());&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Core subsystems ==&lt;br /&gt;
&lt;br /&gt;
Subsystems in Moodle are not plugins themselves but can be referred to using &#039;&#039;&#039;core_xxx&#039;&#039;&#039; where xxx is the subsystem name as defined in get_core_subsystems().   &lt;br /&gt;
&lt;br /&gt;
You can see these being used in the @package parameter in phpdocs, or in the [[Web_services_Roadmap|webservice function names]]. Core subsystems can provide own strings via a file stored in &amp;lt;tt&amp;gt;lang/en/{subsystemname}.php&amp;lt;/tt&amp;gt;. Some of them have a dedicated location with libraries, autoloaded classes and other resources.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Core subsystem&lt;br /&gt;
! Frankenstyle component name&lt;br /&gt;
! Location&lt;br /&gt;
|-&lt;br /&gt;
| Access&lt;br /&gt;
| core_access&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Administration&lt;br /&gt;
| core_admin&lt;br /&gt;
| /admin&lt;br /&gt;
|-&lt;br /&gt;
| Antivirus&lt;br /&gt;
| core_antivirus&lt;br /&gt;
| /lib/antivirus&lt;br /&gt;
|-&lt;br /&gt;
| Authentication&lt;br /&gt;
| core_auth&lt;br /&gt;
| /auth&lt;br /&gt;
|-&lt;br /&gt;
| Conditional availability&lt;br /&gt;
| core_availability&lt;br /&gt;
| /availability&lt;br /&gt;
|-&lt;br /&gt;
| Backup and restore&lt;br /&gt;
| core_backup&lt;br /&gt;
| /backup/util/ui&lt;br /&gt;
|-&lt;br /&gt;
| Badges&lt;br /&gt;
| core_badges&lt;br /&gt;
| /badges&lt;br /&gt;
|-&lt;br /&gt;
| Blocks&lt;br /&gt;
| core_block&lt;br /&gt;
| /blocks&lt;br /&gt;
|-&lt;br /&gt;
| Blogging&lt;br /&gt;
| core_blog&lt;br /&gt;
| /blog&lt;br /&gt;
|-&lt;br /&gt;
| Bulk users operations&lt;br /&gt;
| core_bulkusers&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Caching&lt;br /&gt;
| core_cache&lt;br /&gt;
| /cache&lt;br /&gt;
|-&lt;br /&gt;
| Calendar&lt;br /&gt;
| core_calendar&lt;br /&gt;
| /calendar&lt;br /&gt;
|-&lt;br /&gt;
| Cohorts&lt;br /&gt;
| core_cohort&lt;br /&gt;
| /cohort&lt;br /&gt;
|-&lt;br /&gt;
| Comment&lt;br /&gt;
| core_comment&lt;br /&gt;
| /comment&lt;br /&gt;
|-&lt;br /&gt;
| Competency based education&lt;br /&gt;
| core_competency&lt;br /&gt;
| /competency&lt;br /&gt;
|-&lt;br /&gt;
| Completion&lt;br /&gt;
| core_completion&lt;br /&gt;
| /completion&lt;br /&gt;
|-&lt;br /&gt;
| Countries&lt;br /&gt;
| core_countries&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Course&lt;br /&gt;
| core_course&lt;br /&gt;
| /course&lt;br /&gt;
|-&lt;br /&gt;
| Currencies&lt;br /&gt;
| core_currencies&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Database transfer&lt;br /&gt;
| core_dbtransfer&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Debugging&lt;br /&gt;
| core_debug&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Text editors&lt;br /&gt;
| core_editor&lt;br /&gt;
| /lib/editor&lt;br /&gt;
|-&lt;br /&gt;
| Education fields&lt;br /&gt;
| core_edufields&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Enrol&lt;br /&gt;
| core_enrol&lt;br /&gt;
| /enrol&lt;br /&gt;
|-&lt;br /&gt;
| Error reporting&lt;br /&gt;
| core_error&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Favourites&lt;br /&gt;
| core_favourites&lt;br /&gt;
| /favourites&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
| File picker&lt;br /&gt;
| core_filepicker&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Files management&lt;br /&gt;
| core_files&lt;br /&gt;
| /files&lt;br /&gt;
|-&lt;br /&gt;
| User filtering&lt;br /&gt;
| core_filters&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Forms&lt;br /&gt;
| core_form&lt;br /&gt;
| /lib/form&lt;br /&gt;
|-&lt;br /&gt;
| Grades&lt;br /&gt;
| core_grades&lt;br /&gt;
| /grade&lt;br /&gt;
|-&lt;br /&gt;
| Advanced grading&lt;br /&gt;
| core_grading&lt;br /&gt;
| /grade/grading&lt;br /&gt;
|-&lt;br /&gt;
| Groups&lt;br /&gt;
| core_group&lt;br /&gt;
| /group&lt;br /&gt;
|-&lt;br /&gt;
| Help&lt;br /&gt;
| core_help&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Hub&lt;br /&gt;
| core_hub&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| IMS CC&lt;br /&gt;
| core_imscc&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Installer&lt;br /&gt;
| core_install&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| ISO 6392&lt;br /&gt;
| core_iso6392&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Language pack configuration&lt;br /&gt;
| core_langconfig&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| License&lt;br /&gt;
| core_license&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Maths library&lt;br /&gt;
| core_mathslib&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Media&lt;br /&gt;
| core_media&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Messaging&lt;br /&gt;
| core_message&lt;br /&gt;
| /message&lt;br /&gt;
|-&lt;br /&gt;
| MIME types&lt;br /&gt;
| core_mimetypes&lt;br /&gt;
|-&lt;br /&gt;
| MNet&lt;br /&gt;
| core_mnet&lt;br /&gt;
| /mnet&lt;br /&gt;
|-&lt;br /&gt;
| Dashboard&lt;br /&gt;
| core_my&lt;br /&gt;
| /my&lt;br /&gt;
|-&lt;br /&gt;
| User notes&lt;br /&gt;
| core_notes&lt;br /&gt;
| /notes&lt;br /&gt;
|-&lt;br /&gt;
| Page types&lt;br /&gt;
| core_pagetype&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Pictures and icons&lt;br /&gt;
| core_pix&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Plagiarism&lt;br /&gt;
| core_plagiarism&lt;br /&gt;
| /plagiarism&lt;br /&gt;
|-&lt;br /&gt;
| Plugins management&lt;br /&gt;
| core_plugin&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Portfolio&lt;br /&gt;
| core_portfolio&lt;br /&gt;
| /portfolio&lt;br /&gt;
|-&lt;br /&gt;
| Privacy&lt;br /&gt;
| core_privacy&lt;br /&gt;
|/privacy&lt;br /&gt;
|-&lt;br /&gt;
| Course publishing&lt;br /&gt;
| core_publish&lt;br /&gt;
| /course/publish&lt;br /&gt;
|-&lt;br /&gt;
| Question bank engine&lt;br /&gt;
| core_question&lt;br /&gt;
| /question&lt;br /&gt;
|-&lt;br /&gt;
| Ratings&lt;br /&gt;
| core_rating&lt;br /&gt;
| /rating&lt;br /&gt;
|-&lt;br /&gt;
| Site registration&lt;br /&gt;
| core_register&lt;br /&gt;
| /admin/registration&lt;br /&gt;
|-&lt;br /&gt;
| Repository&lt;br /&gt;
| core_repository&lt;br /&gt;
| /repository&lt;br /&gt;
|-&lt;br /&gt;
| RSS&lt;br /&gt;
| core_rss&lt;br /&gt;
| /rss&lt;br /&gt;
|-&lt;br /&gt;
| Roles&lt;br /&gt;
| core_role&lt;br /&gt;
| /admin/roles&lt;br /&gt;
|-&lt;br /&gt;
| Global search&lt;br /&gt;
| core_search&lt;br /&gt;
| /search&lt;br /&gt;
|-&lt;br /&gt;
| Tabular data display/download (deprecated)&lt;br /&gt;
| core_table&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Tagging&lt;br /&gt;
| core_tag&lt;br /&gt;
| /tag&lt;br /&gt;
|-&lt;br /&gt;
| Timezones&lt;br /&gt;
| core_timezones&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| User&lt;br /&gt;
| core_user&lt;br /&gt;
| /user&lt;br /&gt;
|-&lt;br /&gt;
| User key&lt;br /&gt;
| core_userkey&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Web service&lt;br /&gt;
| core_webservice&lt;br /&gt;
| /webservice&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
&lt;br /&gt;
Frankenstyle component names are used in:&lt;br /&gt;
&lt;br /&gt;
=== Function names ===&lt;br /&gt;
&lt;br /&gt;
All plugin functions must start with full frankenstyle prefix. For backwards compatibility modules may also use &#039;&#039;&#039;modulename_&#039;&#039;&#039; as prefix.&lt;br /&gt;
&lt;br /&gt;
=== Class names ===&lt;br /&gt;
&lt;br /&gt;
All plugin classes must start frankenstyle prefix, ex: mod_forum_some_class.&lt;br /&gt;
&lt;br /&gt;
=== Constants ===&lt;br /&gt;
&lt;br /&gt;
All plugin constants must start with uppercase frankenstyle prefix, example MOD_FORUM_XXXX.&lt;br /&gt;
&lt;br /&gt;
=== Table names ===&lt;br /&gt;
&lt;br /&gt;
All table names for a plugin must begin with its frankenstyle name (after the standard Moodle table prefix).  &lt;br /&gt;
&lt;br /&gt;
(The exception to this rule is Moodle activities which (for historical reasons) do not have mod_ in front of the plugin name)&lt;br /&gt;
&lt;br /&gt;
Examples: mdl_&#039;&#039;&#039;local_coolreport&#039;&#039;&#039;, mdl_&#039;&#039;&#039;local_coolreport&#039;&#039;&#039;_users&lt;br /&gt;
&lt;br /&gt;
=== Plugin configuration table ===&lt;br /&gt;
&lt;br /&gt;
In the &#039;&#039;&#039;config_plugins&#039;&#039;&#039; table, column &#039;&#039;&#039;plugin&#039;&#039;&#039;, the frankenstyle name is used.&lt;br /&gt;
&lt;br /&gt;
=== Capabilities ===&lt;br /&gt;
&lt;br /&gt;
All capabilities for a plugin use the frankenstyle name, except with a / instead of a _.&lt;br /&gt;
&lt;br /&gt;
Example: &#039;&#039;&#039;mod/quiz&#039;&#039;&#039;:viewattempt&lt;br /&gt;
&lt;br /&gt;
=== Language files===&lt;br /&gt;
&lt;br /&gt;
The main language file for each plugin (with the notable exception of activity modules) is the frankenstyle component name. &lt;br /&gt;
&lt;br /&gt;
Examples:  /blocks/participants/lang/en/&#039;&#039;&#039;block_participants&#039;&#039;&#039;.php&lt;br /&gt;
&lt;br /&gt;
=== Renderers ===&lt;br /&gt;
&lt;br /&gt;
=== Module Subplugins ===&lt;br /&gt;
&lt;br /&gt;
It is possible to extend modules without having to change the basic module&#039;s code. See [[Subplugins]] for details.&lt;br /&gt;
&lt;br /&gt;
=== Other places (TODO) ===&lt;br /&gt;
&lt;br /&gt;
* @package declarations in phpdocs, see [[Coding_style#PHPDoc]]&lt;br /&gt;
* [[Web_services_Roadmap|web service function names]]&lt;br /&gt;
* [http://moodle.org Moodle Plugins database]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Please add more as they come up.&lt;br /&gt;
&lt;br /&gt;
== Naming theme variants ==&lt;br /&gt;
&lt;br /&gt;
Themes that are principally derivatives of some other theme, should be named in a way that clearly implies they are variants, not upgrades, of the parent theme. The format of the name should follow and extend the standard component naming format. So for example themes based on Boost, if they want to mention the word &amp;quot;boost&amp;quot; in the component name, should be named like&lt;br /&gt;
&lt;br /&gt;
 theme_boost_something&lt;br /&gt;
&lt;br /&gt;
for example theme_boost_spring, theme_boost_summer, theme_boost_shiny etc. Indeed they can be named just like&lt;br /&gt;
&lt;br /&gt;
 theme_something&lt;br /&gt;
&lt;br /&gt;
as there is no need to repeat the parent name (e.g. theme_spring, theme_summer, theme_shiny). The point and reasoning behind this is to avoid confusion - for example a theme_boost2 looks like a new version of the official Boost theme and as such is not a good name for a contributed theme.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Plugins]]&lt;br /&gt;
* [[Subplugins]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [[Automatic class loading]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Automated_Manipulation_of_Strings_2.0&amp;diff=54423</id>
		<title>Automated Manipulation of Strings 2.0</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Automated_Manipulation_of_Strings_2.0&amp;diff=54423"/>
		<updated>2018-06-20T06:43:44Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* AMOS script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Automated manipulation of strings&lt;br /&gt;
|state = Production&lt;br /&gt;
|tracker = MDL-18797&lt;br /&gt;
|discussion = [http://moodle.org/mod/forum/discuss.php?d=118707#p542197]&lt;br /&gt;
|assignee = [[User:David Mudrak|David Mudrak]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;AMOS stands for Automated Manipulation of Strings. AMOS is a central repository of Moodle strings and their history. It tracks the addition of English strings into Moodle code, gathers translations, handles common translation tasks and generates language packages to be deployed on Moodle servers.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The name was chosen in honour of [http://en.wikipedia.org/wiki/John_Amos_Comenius John Amos Comenius], the author of &#039;&#039;Janua linguarum reserata&#039;&#039; (Gate to Languages Unlocked), who may be considered the father of modern education.&lt;br /&gt;
&lt;br /&gt;
== AMOS design ==&lt;br /&gt;
&lt;br /&gt;
This part of the document was the original specification used for development.&lt;br /&gt;
&lt;br /&gt;
=== Overall picture ===&lt;br /&gt;
&lt;br /&gt;
[[Image:lang20amosflow.png]]&lt;br /&gt;
&lt;br /&gt;
# Developers add new string by adding them into appropriate English $strings array definition file (eg /mod/workshop/lang/en/workshop.php). This file is committed into Moodle main CVS repository as a part of the code.&lt;br /&gt;
# CVS repository is mirrored automatically on the fly in a git repository. This git repository is used for further processing because parsing the strings file and tracking their history is much simpler in this system. The whole history is present in the git clone so there is no need to ask CVS server for anything once it is fetched.&lt;br /&gt;
# Git repository is regularly checked for any changes in string definition files. Once a modification is detected, the file is parsed and any addition, modification or removal of a string is recorded in an English strings database, together with a meta-information about the author of the change, timestamp, branch, commit identification (git commit hash) etc.&lt;br /&gt;
# Translators use the strings definition stored in the English strings database as a reference for their translation. Therefore the information about the origin (the branch and the revision) of the translated English string can be stored as meta-info together with the translation. Every translated string is linked with a certain revision of the English source so we can easily find strings there were modified in English to be re-checked etc.&lt;br /&gt;
# So called translation stage (or cache) is used during the translation. This is similar to the session cache when working with [[XMLDB]]. Once the translator is happy with the work, she/he commits (submits) the translation into the database of the translated strings.&lt;br /&gt;
# Non-English strings database contains the history of the translation of all Moodle strings in all supported languages. This database is used as a source to generate the up-to-date language package in various formats (ZIP to be deployed at the servers, XML to be used by an external translation tools etc).&lt;br /&gt;
# Moodle site administrators update their installed language packs by downloading the ZIP files generated from the database (or, in the future, they can fetch the pack in other format)&lt;br /&gt;
&lt;br /&gt;
=== AMOS processes ===&lt;br /&gt;
&lt;br /&gt;
[[Image:lang20amosflow2.png]]&lt;br /&gt;
&lt;br /&gt;
;Tracking CVS commits : run as a cron job. Looks for new/modified/removed strings in Moodle source code (core and contrib) and registers these changes in AMOS database.&lt;br /&gt;
;Uploading strings : both English and translated strings may be registered from uploaded files. This way, 3rd modules not tracked by AMOS automatically (because they are not in our contrib) can be processed in AMOS.&lt;br /&gt;
;Translating strings via web : AMOS provides an interface for translating stored strings (MDL-21691).&lt;br /&gt;
;Staging : Strings from various sources end in a [http://en.wikipedia.org/wiki/Staging_(data) staging area]. They are stored here temporarily before they are committed into the main strings table.&lt;br /&gt;
;Committing : A set of strings in the stage is committed into the main strings table.&lt;br /&gt;
&lt;br /&gt;
Thanks to this design, we have a single interface to get data from stage into the main strings repository. For every supported format/way to get strings, just a class implementing &#039;stageable&#039; interface is needed to convert the input format into the staging area.&lt;br /&gt;
&lt;br /&gt;
Hierarchy of classes is expected to be available for input processing. For example, the process that tracks commits history in CVS prepares a PHP file with the checkout. So we have a class that is able to convert array $string[] defined in PHP file into staging area. Once we have such class, it can be used to process PHP files uploaded by developers/translators, too.&lt;br /&gt;
&lt;br /&gt;
=== Implementation plan ===&lt;br /&gt;
&lt;br /&gt;
The implementation proposal evolved from the idea by Petr Skoda [http://moodle.org/mod/forum/discuss.php?d=118707#p542197 discussed at moodle.org]. The key point is that translators do not have direct access to the source code repository (CVS) any more. There is a central tool (known as AMOS nowadays) that looks after proper branching and keeping history of the language packs. The current proposal follows.&lt;br /&gt;
&lt;br /&gt;
# There a separate Moodle 2.0 site at http://lang.moodle.org MNet&#039;ed with http://moodle.org. This site is intended for our developers, translators and other community members interested in the translation process. Current Languages forum at Using Moodle can be eventually moved into this new languages portal.&lt;br /&gt;
# AMOS is implemented as a local plugin /local/amos installed at http://lang.moodle.org. Because this is the only Moodle site with this plugin, using /local plugin mechanism is a natural way to implement, develop and maintain it.&lt;br /&gt;
# There is a course &amp;quot;Moodle Translation&amp;quot; in this portal containing (among other useful things) a clear link to the /local/amos/view.php page.&lt;br /&gt;
# During Moodle 2.0 beta period, translator use AMOS portal to prepare the translations of the new Moodle release.&lt;br /&gt;
# AMOS installation at http://lang.moodle.org uses its own git clone of our official git mirror to have access to the English strings. Keeping the git mirror up-to-date and synced is a prerequisition for the proper AMOS functionality.&lt;br /&gt;
&lt;br /&gt;
=== Use cases ===&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;Developers&#039;&#039; write the code and commits it into CVS. They can create or modify English strings as needed in the current way of direct modification of the strings definition file.&lt;br /&gt;
# &#039;&#039;Translators&#039;&#039; come to http://lang.moodle.org to translate Moodle. No other way is possible yet.&lt;br /&gt;
## translators can choose the Moodle version (1.8, 1.9 or 2.0) to translate&lt;br /&gt;
## translators can display the list of missing strings to translate&lt;br /&gt;
## translators can display the list of English strings that were modified since their last translation so they should be re-checked&lt;br /&gt;
## translators can display the history of string wording, authors of the change, commit messages explaining the change&lt;br /&gt;
## other useful tools and filters are available, like displaying all strings containing a given phrase etc. See MDL-18797 for details&lt;br /&gt;
## after providing new or modified translations, translators &amp;quot;commit&amp;quot; their changes through the web interface, providing a commit message&lt;br /&gt;
# &#039;&#039;Administrators&#039;&#039; can download language packages as ZIP files from http://download.moodle.org or let them update automatically from the Moodle&lt;br /&gt;
## packages are regenerated automatically as they are at the moment, with the only difference that the database and not CVS is used as their source&lt;br /&gt;
# &#039;&#039;Contributors&#039;&#039; [must think about this yet] - their plugins in CONTRIB can be mirrored into git (one day this will happend anyway ;-)) and then AMOS can process them easily. Or we could add a feature that the contributor can upload the file with English strings definition manually and &amp;quot;register&amp;quot; the strings this way.&lt;br /&gt;
&lt;br /&gt;
== Database structure ==&lt;br /&gt;
&lt;br /&gt;
The core of the whole AMOS system is a single table containing the history of all changes of all strings from all components in all languages. This one is called amos_repository. All other operations, like committing a translation, getting the current snapshot etc., are based on this table. After an initial import of CVS history, the table contains around 3.6 millions of records.&lt;br /&gt;
&lt;br /&gt;
There is yet another table where the permissions to translate a language are stored, which is not so important and is trivial (therefore not documented here).&lt;br /&gt;
&lt;br /&gt;
=== amos_repository ===&lt;br /&gt;
Contains all Moodle strings and their history&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| id&lt;br /&gt;
| int (10) unsigned not null seq&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| branch&lt;br /&gt;
| int (10) unsigned not null&lt;br /&gt;
| The code of the branch this string is valid for&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| lang&lt;br /&gt;
| char (20) not null &lt;br /&gt;
| The code of the language this string belongs to. Like en, cs or es&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| component&lt;br /&gt;
| char (255) not null &lt;br /&gt;
| The name of the component this string belongs to. Like moodle or workshopform_accumulative&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| stringid&lt;br /&gt;
| char (255) not null&lt;br /&gt;
| The code of the string&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| text&lt;br /&gt;
| text (big) not null&lt;br /&gt;
| The localized string text&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| timemodified&lt;br /&gt;
| int (10) unsigned not null&lt;br /&gt;
| The timestamp of the commit&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| commitmsg&lt;br /&gt;
| text (big)&lt;br /&gt;
| Commit message&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| commithash&lt;br /&gt;
| char (40)&lt;br /&gt;
| The git commit hash that introduced this string&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| source&lt;br /&gt;
| char (255)&lt;br /&gt;
| The source of this string - git, email etc.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| deleted&lt;br /&gt;
| int (2) unsigned default 0&lt;br /&gt;
| Is the string deleted? If not, it will be generated into the lang packs&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| userid&lt;br /&gt;
| int (10) unsigned&lt;br /&gt;
| If the author is known in the local user table, store their id here&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| userinfo&lt;br /&gt;
| char (255)&lt;br /&gt;
| Helps to identify the author of the change, for example a name from CVS commit&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
==== Keys ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Name&lt;br /&gt;
! Type&lt;br /&gt;
! Field(s)&lt;br /&gt;
! Reference&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| primary&lt;br /&gt;
| primary&lt;br /&gt;
| id&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| fk_user&lt;br /&gt;
| foreign&lt;br /&gt;
| userid&lt;br /&gt;
| user (id)&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
==== Indexes ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Name&lt;br /&gt;
! Type&lt;br /&gt;
! Field(s)&lt;br /&gt;
! Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| ix_snapshot&lt;br /&gt;
| Not unique&lt;br /&gt;
| component, lang, branch&lt;br /&gt;
| Optimised for getting a snapshot of all current strings in one component&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| ix_lang&lt;br /&gt;
| Not unique&lt;br /&gt;
| lang&lt;br /&gt;
| For getting a list of all known components. In some cases, we need to filter English records only&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| ix_timemodified&lt;br /&gt;
| Not unique&lt;br /&gt;
| timemodified&lt;br /&gt;
| This index allows to search for the recent records in the log output&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
=== Tracking the changes in the English strings ===&lt;br /&gt;
&lt;br /&gt;
Implemented in: /local/amos/cli/parse-core.php&lt;br /&gt;
&lt;br /&gt;
AMOS uses its own git clone of Moodle repository. It runs &#039;git whatchanged&#039; to see what files were affected by every single commit ever. Once it detects a change in a valid English string file, it checks out that revision of the file and compares its content with the current snapshot of the strings database. New record is added into the strings table for every new, modified or removed string in the checked out file. The commit hash of the last fully processed commit is stored in $CFG-&amp;gt;dataroot/amos/var/MOODLE_xx.startat so that next time AMOS analyzes just new commits.&lt;br /&gt;
&lt;br /&gt;
=== AMOS script ===&lt;br /&gt;
&lt;br /&gt;
AMOS script allows us to propagate changes in the English language pack into other languages.&lt;br /&gt;
&lt;br /&gt;
Sometimes we want to reorganize the English language pack - for example split a component into subcomponents (as happened with auth.php), rename string identifiers, fork a string according the meaning (e.g. &#039;fullname&#039; may be different when talking about a human and about a course) etc. Such a change can be easily done in English by direct editing and committing the lang/en/*.php files. But the translation would get lost and our poor translators would have to translate such strings again.&lt;br /&gt;
&lt;br /&gt;
There is a way to instruct AMOS to propagate a change in the English lang pack into all other languages at the given branch. We call that AMOS script (or amosbler for the syntax similarity with the assembly language - assembler). Such a script can be uploaded or pasted into a page at AMOS portal and it will just follow the instructions provided. Or - which is more interesting - such a script can be put directly into the commit message of the commit that does the change in the English language pack. In that case, AMOS will automatically run the script right after it process the commit.&lt;br /&gt;
&lt;br /&gt;
Here is an example of a script that instruct AMOS to process a set of bulk operations over language packages:&lt;br /&gt;
&lt;br /&gt;
 AMOS BEGIN&lt;br /&gt;
  MOV [description,mod_workshop],[intro,mod_workshop]&lt;br /&gt;
  CPY [submission,mod_assignment],[submission,mod_workshop]&lt;br /&gt;
  HLP forum/forumtype.html,[forumtype_hlp,mod_forum]&lt;br /&gt;
 AMOS END&lt;br /&gt;
&lt;br /&gt;
In this example, there are three instuctions to be done. The line with MOV (&#039;move&#039;) command instructs AMOS to rename the string &#039;description&#039; defined in workshop to the new identifier &#039;intro&#039;. The second command CPY (&#039;copy&#039;) orders to create new string in the workshop module with the identifier &#039;submission&#039; and the value of that string shall be taken from the $string[&#039;submission&#039;] in the module assignment. If such string already exists in any language, CPY will not replace it. The third command is used for migrating legacy HTML help files into ordinary strings. It tells AMOS to add new $string[&#039;forumtype_hlp&#039;] in every language, using the content of the help file &#039;forum/forumtype.html&#039; as the initial value.&lt;br /&gt;
&lt;br /&gt;
The script syntax is defined as follows. Note that amosbler keywords are case sensitive so must be upper-case. In pseudo-regexp, the valid AMOS script is defined as:&lt;br /&gt;
&lt;br /&gt;
 ^[:blank:]*AMOS BEGIN[:blank:]*$&lt;br /&gt;
 ^[:blank:]*[A-Z]{3}[:blank:]+(param1),[:blank:]*(param2), ...,[:blank:]*(paramn)[:blank:]$&lt;br /&gt;
 ...&lt;br /&gt;
 ^[:blank:]*AMOS END[:blank:]*$&lt;br /&gt;
&lt;br /&gt;
In human language, this roughly means: the script is a block of lines starting with &#039;AMOS BEGIN&#039; or &#039;AMOS START&#039; and ending with &#039;AMOS END&#039; lines. Every instruction is on its own line. Instruction has its name (three capital letters like MOV, CPY, HLP, RPL, SMS or GRR) followed by comma-separated parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Beware:&#039;&#039;&#039; Every string is referenced as [stringid,component] &#039;&#039;&#039;but the component is different from what we use in get_string()&#039;&#039;&#039;. All components use fully normalized plugintype_pluginname syntax (see normalize_component() function in moodlelib). If plugintype === core and pluginname is empty (component &#039;core&#039;), the strings are stored in moodle.php.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! String identification in get_string()&lt;br /&gt;
! String identification in AMOS&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| get_string(&#039;edit&#039;, &#039;moodle&#039;)&lt;br /&gt;
| [edit,core]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| get_string(&#039;submit&#039;, &#039;assignment&#039;)&lt;br /&gt;
| [submit,mod_assignment]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| get_string(&#039;grade_help&#039;, &#039;grades&#039;)&lt;br /&gt;
| [grade_help,core_grades]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| get_string(&#039;send&#039;, &#039;message&#039;)&lt;br /&gt;
| [send,core_message]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| get_string(&#039;hello&#039;, &#039;block_greetings&#039;)&lt;br /&gt;
| [hello,block_greetings]&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Currently planned/implemented AMOS script instructions are:&lt;br /&gt;
&lt;br /&gt;
; MOV [source],[target] : Move the string. If the source stringid is already defined in the target component, it is not replaced.&lt;br /&gt;
; CPY [source],[target] : Copy the string. Works as MOV but the source string is not touched.&lt;br /&gt;
; HLP component/helpfile.html,[string] : Convert legacy HTML help file to the string&lt;br /&gt;
; REM text : Allows to put a remark (comment), for example to describe a required operation that can&#039;t be achieved by current instruction set.&lt;br /&gt;
&lt;br /&gt;
Ideas for other future instructions: RPL for replace (forced MOV), SMS for sending a message, GRR for something unknown yet but such instruction just must be there ;-)&lt;br /&gt;
&lt;br /&gt;
Note there are no instructions DEL or ADD. AMOS automatically recognized new strings as well as their removal from the commit diffs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Summary of using AMOS script in commit messages:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
# The commit must modify some string file, AMOS would ignore the commit completely otherwise&lt;br /&gt;
# The script must be properly formatted as block of lines&lt;br /&gt;
# The strings must be identified in normalized syntax - the main difference is using core for moodle.php and core_* prefix for components in lang/en/*.php&lt;br /&gt;
# Note that it may take up to an hour that your commit is mirrored into git and then processed by AMOS&lt;br /&gt;
&lt;br /&gt;
Note: Strings can be removed on the master branch easily by removing them from the strings file. No AMOS command is needed. Just make sure the string is not use elsewhere and do not remove the string from stable branches.&lt;br /&gt;
&lt;br /&gt;
More information about commit message format: https://docs.moodle.org/dev/Commit_cheat_sheet#Include_AMOS_script_in_the_commit_if_needed&lt;br /&gt;
&lt;br /&gt;
=== Generating installer files ===&lt;br /&gt;
&lt;br /&gt;
Implemented, not automated yet&lt;br /&gt;
&lt;br /&gt;
See cli/export-installer.php.&lt;br /&gt;
&lt;br /&gt;
== Deployment settings ==&lt;br /&gt;
&lt;br /&gt;
 # crontab -l&lt;br /&gt;
 0,30 01-23 * * * /usr/local/bin/amos-update &amp;gt; /tmp/amos-update.log&lt;br /&gt;
 0    0     * * * /usr/bin/php /var/www/htdocs/moodle-amos/local/amos/cli/rev-clean.php --full &amp;gt; /tmp/amos-full-rev-clean.log&lt;br /&gt;
&lt;br /&gt;
 #!/bin/bash&lt;br /&gt;
 &lt;br /&gt;
 # /usr/local/bin/amos-update&lt;br /&gt;
 # Updates AMOS working repositories and registers changes&lt;br /&gt;
 # To be run regularly after git sync&lt;br /&gt;
 &lt;br /&gt;
 AMOSCLIDIR=/var/www/htdocs/moodle-amos/local/amos/cli&lt;br /&gt;
 AMOSREPOCORE=/var/www/data/moodle-amos/amos/repos/moodle&lt;br /&gt;
 AMOSREPOLANG=/var/www/data/moodle-amos/amos/repos/moodle-lang&lt;br /&gt;
 PHP=/usr/bin/php&lt;br /&gt;
 &lt;br /&gt;
 cd $AMOSREPOCORE &amp;amp;&amp;amp; /usr/local/bin/git pull --quiet&lt;br /&gt;
 cd $AMOSREPOLANG &amp;amp;&amp;amp; /usr/local/bin/git pull --quiet&lt;br /&gt;
 &lt;br /&gt;
 $PHP $AMOSCLIDIR/parse-core.php &amp;amp;&amp;amp; $PHP $AMOSCLIDIR/parse-lang.php &amp;amp;&amp;amp; $PHP $AMOSCLIDIR/rev-clean.php&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Language]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.4_release_notes&amp;diff=53369</id>
		<title>Moodle 3.4 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.4_release_notes&amp;diff=53369"/>
		<updated>2017-11-20T05:27:06Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* For developers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
Release date: 13 November 2017&lt;br /&gt;
 &lt;br /&gt;
Here is [https://tracker.moodle.org/issues/?jql=project%20%3D%20mdl%20AND%20resolution%20%3D%20fixed%20AND%20fixVersion%20in%20(3.4)%20ORDER%20BY%20priority%20DESC the full list of fixed issues in 3.4].&lt;br /&gt;
&lt;br /&gt;
See our [https://docs.moodle.org/34/en/New_features New Features page] for a more user-friendly introduction to Moodle 3.4 with screenshots.&lt;br /&gt;
&lt;br /&gt;
If you are upgrading from previous version, make sure you read the [https://docs.moodle.org/34/en/Upgrading Upgrading] documentation. &lt;br /&gt;
&lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just the minimum supported versions. We recommend keeping all of your software up-to-date.&lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 3.0 or later (if upgrading from earlier versions, you must upgrade to 3.0.10 as a first step)&lt;br /&gt;
* PHP version: minimum PHP 7.0.0 &#039;&#039;Note: minimum PHP version has increased since Moodle 3.3&#039;&#039;. PHP 7.1.x and 7.2.x are supported too. PHP 7.x could have some [https://docs.moodle.org/dev/Moodle_and_PHP7#Can_I_use_PHP7_yet.3F engine limitations]. &lt;br /&gt;
* PHP extension &#039;&#039;&#039;intl&#039;&#039;&#039; is now required in Moodle 3.4 (it was recommended in 2.0 onwards) &lt;br /&gt;
&lt;br /&gt;
=== Database requirements ===&lt;br /&gt;
&lt;br /&gt;
Moodle supports the following database servers. Again, version numbers are just the minimum supported version. We recommend running the latest stable version of any software.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Database&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
| 9.3&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.microsoft.com/en-us/server-cloud/products/sql-server/ Microsoft SQL Server]&lt;br /&gt;
| 2008&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.oracle.com/us/products/database/overview/index.html Oracle Database]&lt;br /&gt;
| 10.2&lt;br /&gt;
| Latest&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Client requirements==&lt;br /&gt;
&lt;br /&gt;
=== Browser support ===&lt;br /&gt;
Moodle is compatible with any standards compliant web browser. We regularly test Moodle with the following browsers:&lt;br /&gt;
&lt;br /&gt;
Desktop:&lt;br /&gt;
* Chrome&lt;br /&gt;
* Firefox&lt;br /&gt;
* Safari&lt;br /&gt;
* Edge&lt;br /&gt;
* Internet Explorer&lt;br /&gt;
&lt;br /&gt;
Mobile:&lt;br /&gt;
* MobileSafari&lt;br /&gt;
* Google Chrome&lt;br /&gt;
&lt;br /&gt;
For the best experience and optimum security, we recommend that you keep your browser up to date. https://whatbrowser.org&lt;br /&gt;
&lt;br /&gt;
Note: Legacy browsers with known compatibility issues with Moodle 3.4:&lt;br /&gt;
* Internet Explorer 10 and below&lt;br /&gt;
* Safari 7 and below&lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===Calendar improvements===&lt;br /&gt;
&lt;br /&gt;
* MDL-59333 - Calendar Improvements&lt;br /&gt;
* MDL-1322 - Calendar entries in monthly view should include course shortname&lt;br /&gt;
* MDL-59382 - Create calendar event quick-add&lt;br /&gt;
* MDL-59390 - Add navigation of all calendar views without page reload&lt;br /&gt;
* MDL-59394 - Add support for drag and drop of calendar events&lt;br /&gt;
* MDL-59386 - Add support for creation and update of calendar events using a modal dialogue&lt;br /&gt;
* MDL-59890 - Add support for calendar events at the category level&lt;br /&gt;
&lt;br /&gt;
===Management of course participants===&lt;br /&gt;
&lt;br /&gt;
* MDL-59290 - Merge Course Participants and Enrolled Users pages&lt;br /&gt;
* MDL-59564 - Add bulk editing of enrolment status/dates for users in the course participants page&lt;br /&gt;
* MDL-59364 - Remove the &amp;quot;Brief / User Details&amp;quot; functionality from the participants page&lt;br /&gt;
* MDL-59365 - Enrol Users button on participants page&lt;br /&gt;
* MDL-59366 - Add filter controls to the participants page to allow custom filtering&lt;br /&gt;
* MDL-59367 - Add a roles column to participants page&lt;br /&gt;
* MDL-59368 - Add a groups column to the participants page&lt;br /&gt;
* MDL-59369 - Add a status column to the participants page&lt;br /&gt;
* MDL-59436 - Remove the columns from the participants page that are not in showuseridentity&lt;br /&gt;
* MDL-59821 - Add &amp;quot;Proceed to course content&amp;quot; to participants page&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-57791 - Implement analytics engine in Moodle&lt;br /&gt;
* MDL-59313 - Add links and a drop down to navigate between activities&lt;br /&gt;
* MDL-37361 - Allow teachers to mark activities as completed&lt;br /&gt;
&lt;br /&gt;
===Backup, restore and import===&lt;br /&gt;
&lt;br /&gt;
* MDL-35429 - Correct the permissions required to download and restore course automated backups&lt;br /&gt;
* MDL-9367 - Restore with roll forward changes dates for user data&lt;br /&gt;
* MDL-59518 - Restore date should not roll for user created data - Core components&lt;br /&gt;
&lt;br /&gt;
===Global search===&lt;br /&gt;
&lt;br /&gt;
* MDL-55356 - Index contents of the restored courses&lt;br /&gt;
* MDL-59523 - Course reset doesn&#039;t always shift dates&lt;br /&gt;
* MDL-58957 - Global search: Make it possible to search blocks&lt;br /&gt;
* MDL-59039 - Global search: Allow partial indexing (in scheduled task)&lt;br /&gt;
&lt;br /&gt;
===Authentication===&lt;br /&gt;
&lt;br /&gt;
* MDL-30634 - Assign arbitrary system roles via LDAP sync&lt;br /&gt;
* MDL-58544 - Add option to trust email of an OAuth provider&lt;br /&gt;
* MDL-59844 - Enable OAuth 2 token-based authentication for requests in webdav_client&lt;br /&gt;
* MDL-59459 - Global Search: Increase file indexing coverage&lt;br /&gt;
* MDL-59913 - Global search: Allow search of non-enrolled courses&lt;br /&gt;
&lt;br /&gt;
===Functional changes===&lt;br /&gt;
&lt;br /&gt;
* MDL-55358 - LIS Group Variables support in LTI&lt;br /&gt;
* MDL-36501 - Should have checkbox for extra credit when you add a grade item&lt;br /&gt;
* MDL-28574 - Web services: Manage tokens page should show tokens for all users&lt;br /&gt;
* MDL-26976 - Display space used in My Private Files&lt;br /&gt;
* MDL-35668 - Performance improvement in Server files repository&lt;br /&gt;
* MDL-49398 - Performance improvement due to Role definition caching &amp;amp; accesslib refactoring&lt;br /&gt;
* MDL-60002 - Assignment grading: Adding back &amp;quot;Save and show next&amp;quot;&lt;br /&gt;
* MDL-58889 - Make section titles and course titles more accessible in Boost&lt;br /&gt;
* MDL-57455 - Allow to tag database entries&lt;br /&gt;
* MDL-36985 - Assignment: automatically remove embedded files that are no longer linked from submission text. Reduce the size of &amp;quot;Download all submissions&amp;quot;&lt;br /&gt;
* MDL-59702 - Lesson overview report does not respect value of showuseridentity setting&lt;br /&gt;
* MDL-59460 - Forum: make Subscription mode setting configurable&lt;br /&gt;
&lt;br /&gt;
===For administrators===&lt;br /&gt;
&lt;br /&gt;
Please read carefully: [https://docs.moodle.org/34/en/Upgrading#Possible_issues_that_may_affect_you_in_Moodle_3.4 Possible issues that may affect you in Moodle 3.4]&lt;br /&gt;
&lt;br /&gt;
* MDL-42834 - Deprecate loginhttps. Sites that used to use this setting will now be served via https always&lt;br /&gt;
* MDL-46269 - Tool to [https://docs.moodle.org/34/en/HTTPS_conversion_tool convert http embedded content to https] where available&lt;br /&gt;
* MDL-58388 - Let the admin control if the course end date form field in course settings is enabled by default&lt;br /&gt;
* MDL-60211 - New filters for User Tours&lt;br /&gt;
* MDL-59123 - Compile SCSS files on the command-line&lt;br /&gt;
* MDL-58567 - Upgrade: Show upgrade times&lt;br /&gt;
* MDL-55652 - Missing index on (timemodified) in grade_items_history table and several other grade history tables. This will increase performance of various reports but may also slow down Moodle upgrade&lt;br /&gt;
* MDL-60094 - Add CLI script to kill all sessions&lt;br /&gt;
* MDL-59495 - Register and publish courses with moodle.net only, remove support for alternative hubs&lt;br /&gt;
* MDL-59206 - Trigger an event in add_to_config_log function&lt;br /&gt;
* MDL-57115 - Move &amp;quot;Messages&amp;quot; block out from the standard Moodle distribution&lt;br /&gt;
* MDL-57734 - SEO - Create admin setting to be able to enable or disable search engine indexing for sites with forcelogin&lt;br /&gt;
* MDL-60309 - Boost: Add a setting for background image&lt;br /&gt;
* MDL-56751 - Create new security setting to configure the expiration time of tokens created via login/token.php or tool/mobile/launch.php&lt;br /&gt;
&lt;br /&gt;
===For developers===&lt;br /&gt;
&lt;br /&gt;
* MDL-60611 - Upgrade PHPUnit to 6.4 to ensure compatibility with PHP 7.2 - [https://docs.moodle.org/dev/Writing_PHPUnit_tests#Upgrading_unit_tests_to_work_with_Moodle_3.4_and_up_.28PHPUnit_6.29 may require changes in unittests].&lt;br /&gt;
* MDL-58948 - Compatibility with chrome mink driver&lt;br /&gt;
* MDL-53169 - Provide a way to retrieve all courses a user can potentially access.&lt;br /&gt;
* MDL-59459 - Global Search: Increase file indexing coverage&lt;br /&gt;
* MDL-59544 - Implicit validation of the filemanager and filepicker form elements&lt;br /&gt;
* MDL-59277 - navigation_node doesn&#039;t support TYPE_CONTAINER in get_css_type()&lt;br /&gt;
* MDL-58957 - Global search: Make it possible to search blocks. See the new [https://docs.moodle.org/dev/Search_API#Base_class \core_search\base_block class].&lt;br /&gt;
* MDL-53240 - Form element and admin setting type to choose file types and type groups&lt;br /&gt;
* MDL-53848 - Formslib - add function to $mform that makes it possible to hide form elements dependent on selected values&lt;br /&gt;
* MDL-60234 - Add possibility to disable admin warning if a development libs directory exists&lt;br /&gt;
* MDL-57886 - Plagiarism: onlinetext submission should pass raw submissiontext to plagiarism get_links()&lt;br /&gt;
&lt;br /&gt;
==== Upgrading plugins ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1. Check for changes in core APIs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Read lib/upgrade.txt to check for the deprecations and core API changes, make sure you applied them to your plugin. Note that entries there are not sorted by priority but rather by integration time. Below is the list of upgrade.txt files that contain information about upgrading from Moodle 3.3 to Moodle 3.4 (note that if you upgrade from earlier versions there may be more files):&lt;br /&gt;
&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/lib/upgrade.txt lib/upgrade.txt] changes to various core APIs, deprecations, functions removal&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/calendar/upgrade.txt calendar/upgrade.txt] changes to Calendar API&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/search/upgrade.txt search/upgrade.txt] changes to Global search API&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/webservice/upgrade.txt webservice/upgrade.txt] changes to WebServices API&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2. Check for changes in the API of your plugin type&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below is the list of plugin types that had API changes between Moodle 3.3 and 3.4:&lt;br /&gt;
&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/blocks/upgrade.txt blocks/upgrade.txt] Block plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/dataformat/upgrade.txt dataformat/upgrade.txt] Dataformat plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/enrol/upgrade.txt enrol/upgrade.txt] Enrolment method plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/mod/upgrade.txt mod/upgrade.txt] Activity module plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/plagiarism/upgrade.txt plagiarism/upgrade.txt] Plagiarism plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/repository/upgrade.txt repository/upgrade.txt] Repository plugins&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/theme/upgrade.txt theme/upgrade.txt] Themes&lt;br /&gt;
* [https://raw.githubusercontent.com/moodle/moodle/master/user/profile/field/upgrade.txt user/profile/field/upgrade.txt] User profile fields&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3. Check for changes in the depended plugins&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If your plugin depends on another plugin or calls methods from another plugin, read upgrade.txt in this plugin directory (if it exists). Below is the list of standard plugins that had changes between Moodle 3.3 and 3.4:&lt;br /&gt;
&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/admin/tool/log/store/database/upgrade.txt logstore_database],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/admin/tool/mobile/upgrade.txt tool_mobile],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/auth/ldap/upgrade.txt auth_ldap],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/blocks/calendar_upcoming/upgrade.txt block_calendar_upcoming],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/lib/editor/atto/upgrade.txt editor_atto],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/assign/upgrade.txt mod_assign],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/data/upgrade.txt mod_data],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/forum/upgrade.txt mod_forum],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/glossary/upgrade.txt mod_glossary],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/lesson/upgrade.txt mod_lesson],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/lti/upgrade.txt mod_lti],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/mod/workshop/upgrade.txt mod_workshop],&lt;br /&gt;
[https://raw.githubusercontent.com/moodle/moodle/master/theme/boost/upgrade.txt theme_boost]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4. Do a smoke test of your plugin with developer debugging mode&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;5. Run all behat and phpunit tests&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
*[[Moodle 3.3 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.4]]&lt;br /&gt;
 &lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.4]]&lt;br /&gt;
[[es:Notas de Moodle 3.4]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53267</id>
		<title>Running acceptance test</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Running_acceptance_test&amp;diff=53267"/>
		<updated>2017-11-16T02:23:52Z</updated>

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

		<summary type="html">&lt;p&gt;Jaked: /* Base class */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The search API allows you to index contents in a search engine and query the search engine for results. Any Moodle component (all plugin types and all core subsystems) can define search areas for their contents.&lt;br /&gt;
&lt;br /&gt;
This is different from the internal search feature some components have.&lt;br /&gt;
&lt;br /&gt;
== Add a search area ==&lt;br /&gt;
&lt;br /&gt;
Note that I will be writing &#039;&#039;&#039;plugintype&#039;&#039;&#039; and &#039;&#039;&#039;pluginname&#039;&#039;&#039; to make it easier to understand for 3rd party developers not working in core, but it is also applicable for core subsystems, using &#039;&#039;&#039;core&#039;&#039;&#039; as plugintype and &#039;&#039;&#039;componentname&#039;&#039;&#039; as pluginname. I will be using &#039;&#039;&#039;areaname&#039;&#039;&#039; as the area you are defining.&lt;br /&gt;
&lt;br /&gt;
=== Easy case: Activity information ===&lt;br /&gt;
&lt;br /&gt;
If you just want to index your activity basic information, like the name and the description, you can skip all the documentation below. You can do it by extending &#039;&#039;&#039;\core_search\base_activity&#039;&#039;&#039; (\core_search\area\base_activity in Moodle 3.1). Copy &amp;amp; paste mod/book/classes/search/activity.php and replace &#039;book&#039; by your &#039;activityname&#039;. If you use different fields than &#039;&#039;&#039;name&#039;&#039;&#039; and &#039;&#039;&#039;intro&#039;&#039;&#039; (the defacto standard) or you want to index extra fields or files look at mod/page/classes/search/activity.php (extra fields) or mod/assign/classes/search/activity.php (files).&lt;br /&gt;
&lt;br /&gt;
To index other information your activity tables contain (e.g. glossary or journal entries, assignment submissions...) you need to extend \core_search\base_mod instead. Continue reading below please.&lt;br /&gt;
&lt;br /&gt;
=== Base class ===&lt;br /&gt;
All the search area stuff in contained in a single &#039;&#039;&#039;\plugintype_pluginname\search\areaname&#039;&#039;&#039; class and it should be extending one of the following classes:&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base (\core_search\area\base in Moodle 3.1)&#039;&#039;&#039;: Generic base class for a search area&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_mod (\core_search\area\base_mod in Moodle 3.1)&#039;&#039;&#039;: Base class for activities search areas ([[Activity_modules]])&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_activity (\core_search\area\base_activity in Moodle 3.1)&#039;&#039;&#039;: Base class also for activities, but more specific than &#039;&#039;&#039;base_mod&#039;&#039;&#039; as it is intended to be used to index Moodle activities basic data like the activity name and description. For other specific activity data to be indexed use &#039;&#039;&#039;base_mod&#039;&#039;&#039;. If you have doubts and you don&#039;t know which one you should use think of forum activities and forum posts, forum activities should use &#039;&#039;&#039;base_activity&#039;&#039;&#039;, but forum posts should use &#039;&#039;&#039;base_mod&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_block (Available in Moodle 3.4+)&#039;&#039;&#039;: Base class for block search areas ([[Blocks]]). Only blocks displayed on some areas of the site are supported for indexing. This is based on the &#039;pagetypepattern&#039; of the block_instance. Supported page types are as follows:&lt;br /&gt;
** Blocks on the site home page (pagetypepattern = &#039;site-index&#039;)&lt;br /&gt;
** Blocks on a course home page (pagetypepattern = &#039;course-view-*&#039;)&lt;br /&gt;
** Blocks on any course page (pagetypepattern = &#039;course-*&#039;)&lt;br /&gt;
** Blocks on any page (pagetypepattern = &#039;*&#039;)&lt;br /&gt;
: Note: Indexing is not supported for blocks configured for course-module-level display.&lt;br /&gt;
&lt;br /&gt;
: &#039;&#039;&#039;\core_search\base_block&#039;&#039;&#039; includes a default implementation for &#039;&#039;&#039;get_document_recordset&#039;&#039;&#039; which is suitable for blocks storing their data in the &#039;&#039;&#039;block_instances&#039;&#039;&#039; table. Blocks of this nature only need implement the &#039;&#039;&#039;get_document&#039;&#039;&#039; function to return the document. Blocks storing their data in other tables will need to override the &#039;&#039;&#039;get_document_recordset&#039;&#039;&#039; function. See &#039;&#039;&#039;blocks/html/classes/search/content.php&#039;&#039;&#039; for an example of a &#039;standard&#039; block indexing implementation.&lt;br /&gt;
&lt;br /&gt;
=== Name ===&lt;br /&gt;
You will need to set a visible name for the search area, for plugins this should be defined in the plugin&#039;s language strings file and for core subsystems in &#039;&#039;lang/en/search.php&#039;&#039;. The format to set this search area name that will be visible for Moodle users (on search page filters section or on admin search pages) is &#039;&#039;&#039;search:AREANAME&#039;&#039;&#039;, so for example for forum posts we have &#039;&#039;&#039;$string[&#039;search:posts&#039;] = &#039;Forum - posts&#039;&#039;&#039;&#039; in &#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Index data ===&lt;br /&gt;
&lt;br /&gt;
This function should return a recordset with all the stuff in your component that has been modified since &#039;&#039;&#039;$modifiedfrom&#039;&#039;&#039; timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_recordset_by_timestamp($modifiedfrom = 0) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    // The idea is to include here most (if not all) of the data you will need to index (see get_document below)&lt;br /&gt;
    $sql = &amp;quot;SELECT x.* FROM {xxxxx} WHERE x.timemodified &amp;gt;= ? ORDER BY x.timemodified ASC&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // Note that this is an example, you might have more params.&lt;br /&gt;
    return $DB-&amp;gt;get_recordset_sql($sql, array($modifiedfrom);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function receives one of the previous query results ($record) and an array of options. It should return a &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; object with all the data to be indexed.&lt;br /&gt;
&lt;br /&gt;
The options this function can receive are:&lt;br /&gt;
* &#039;&#039;&#039;indexfiles&#039;&#039;&#039;: Whether to index files or not. File indexing support also depends on the backend search engine, not all of them support file indexing, no need to set if the document is new if there is no filesindexing support&lt;br /&gt;
* &#039;&#039;&#039;lastindexedtime&#039;&#039;&#039;: Also related with files, see example below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_document($record, $options = array) {&lt;br /&gt;
&lt;br /&gt;
    // All wrapped in a try &amp;amp; catch as we should not stop the indexing process because of a legacy corrupted database.&lt;br /&gt;
    try {&lt;br /&gt;
        $context = \context_course::instance($record-&amp;gt;contextid);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document, not all required data is available: &#039; .&lt;br /&gt;
            $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document: &#039; . $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Prepare associative array with data from DB.&lt;br /&gt;
    $doc = \core_search\document_factory::instance($record-&amp;gt;id, $this-&amp;gt;componentname, $this-&amp;gt;areaname);&lt;br /&gt;
&lt;br /&gt;
    // Any content should be converted to plain text.&lt;br /&gt;
&lt;br /&gt;
    // If you just have a text string you need to call content_to_text() with the $contentformat param set to false.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;title&#039;, content_to_text($record-&amp;gt;something-that-describes-the-result));&lt;br /&gt;
&lt;br /&gt;
    // For a property named &#039;content&#039; where another property &#039;contentformat&#039; is also present the text should be&lt;br /&gt;
    // passed through to content_to_text() when declaring the document. This will ensure that HTML, Markdown, ...&lt;br /&gt;
    // formats are converted to plain text. Similar to what we do with format_text.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;content&#039;, content_to_text($record-&amp;gt;content, $record-&amp;gt;contentformat));&lt;br /&gt;
&lt;br /&gt;
    $doc-&amp;gt;set(&#039;contextid&#039;, $context-&amp;gt;id);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;type&#039;, \core_search\manager::TYPE_TEXT);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;courseid&#039;, $record-&amp;gt;courseid);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;modified&#039;, $record-&amp;gt;timemodified);&lt;br /&gt;
&lt;br /&gt;
    // Optional fields.&lt;br /&gt;
&lt;br /&gt;
    // The user that created the record. It is optional. In some cases like forum posts makes sense to have it available, but in some other cases like activities it does not help much.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;userid&#039;, $record-&amp;gt;userid);&lt;br /&gt;
&lt;br /&gt;
    // In case the indexed document should only be accessed by the user that created it replace NO_OWNER_ID constant by the owner user userid.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;owneruserid&#039;, \core_search\manager::NO_OWNER_ID);&lt;br /&gt;
&lt;br /&gt;
    // Extra contents associated to the document.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description1&#039;, content_to_text($record-&amp;gt;extracontent1, $record-&amp;gt;extracontent1format));&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description2&#039;, content_to_text($record-&amp;gt;extracontent2, $record-&amp;gt;extracontent2format));&lt;br /&gt;
&lt;br /&gt;
    // Not compulsory, but speeds up things when the search area includes files (see [[#Indexing files]])&lt;br /&gt;
    if (isset($options[&#039;lastindexedtime&#039;]) &amp;amp;&amp;amp; ($options[&#039;lastindexedtime&#039;] &amp;lt; $record-&amp;gt;created)) {&lt;br /&gt;
        // If the document was created after the last index time, it must be new.&lt;br /&gt;
        $doc-&amp;gt;set_is_new(true);&lt;br /&gt;
    }&lt;br /&gt;
    return $doc;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing files ====&lt;br /&gt;
&lt;br /&gt;
First declare that the search area is interested in indexing the files attached to its documents.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function uses_file_indexing() {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Define attach_files function, which receives a \core_search\document object and fills it with stored_file objects.&lt;br /&gt;
&lt;br /&gt;
This is a simplified example of one of the cases we can find in core.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function attach_files($document) {&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
    $context = \context_module::instance($document-&amp;gt;get(&#039;contextid&#039;));&lt;br /&gt;
&lt;br /&gt;
    $files = $fs-&amp;gt;get_area_files($context-&amp;gt;id, &#039;COMPONENTNAME&#039;, &#039;FILEAREA&#039;, $document-&amp;gt;itemid);&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $document-&amp;gt;add_stored_file($file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing performance ====&lt;br /&gt;
&lt;br /&gt;
The indexing process runs by cron incrementally, keeping track of the last indexing time... In big Moodle sites this process can be very heavy as millions of records can be indexed in the same PHP process, so performance is quite important. Think that any database query that you add in your &#039;&#039;&#039;get_document&#039;&#039;&#039; function to retrieve data that is part of your document will run for every document. If you search area can potentially contain a big number of records, you might be interested in adding static caches.&lt;br /&gt;
&lt;br /&gt;
=== Access control ===&lt;br /&gt;
&lt;br /&gt;
Moodle&#039;s global search limits the access to the indexed data in two different ways.&lt;br /&gt;
&lt;br /&gt;
==== Automatic context-based filtering ====&lt;br /&gt;
&lt;br /&gt;
This is done automatically by Moodle, the user performing a query will only have access to contents in contexts where the user have access. If your search area indexes contents that belong to a course the user will only see results that belong to the courses where they have access, if your search area belongs to a course module, only to visible activities in courses where you have access and there are no completion rules preventing them to be accessible. Similarly, if your search area belong to a user, only the current user and site admins will have access to your search area contents. &#039;&#039;Note that this last option is closely related to doc&#039;s &#039;owneruserid&#039; field, the main difference is that setting &#039;owneruserid&#039; to the user id will make the search area documents unavailable to admin users.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to specify to what context (or contexts) your search area contents belong to. Overwrite &#039;&#039;&#039;$levels&#039;&#039;&#039; static attribute in your class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected static $levels = [CONTEXT_COURSE]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Final access checking ====&lt;br /&gt;
&lt;br /&gt;
Your search area is the responsible to filter out the results that &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_access($id) {&lt;br /&gt;
    try {&lt;br /&gt;
        $myobject = $this-&amp;gt;get_xxxxx($id);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        // If the record does not exist anymore in Moodle we should return \core_search\manager::ACCESS_DELETED.&lt;br /&gt;
        return \core_search\manager::ACCESS_DELETED;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        // Skip results if there is any unexpected error.&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($myobject-&amp;gt;visible === false) {&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return \core_search\manager::ACCESS_GRANTED;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The automatic context-based filtering should get rid of most of the results where a user do not have access. If &#039;&#039;&#039;check_access&#039;&#039;&#039;&#039;s ratio of visible results vs non-visible results is high you might need to rethink about the context where your search area belongs or we might need to expand the APIs.&lt;br /&gt;
&lt;br /&gt;
=== Other required methods ===&lt;br /&gt;
&lt;br /&gt;
The link to the result. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_doc_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The link to the result context, in some cases it might be the same than the doc url. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_context_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component/context.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53243</id>
		<title>Search API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53243"/>
		<updated>2017-11-14T06:53:31Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Base class */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The search API allows you to index contents in a search engine and query the search engine for results. Any Moodle component (all plugin types and all core subsystems) can define search areas for their contents.&lt;br /&gt;
&lt;br /&gt;
This is different from the internal search feature some components have.&lt;br /&gt;
&lt;br /&gt;
== Add a search area ==&lt;br /&gt;
&lt;br /&gt;
Note that I will be writing &#039;&#039;&#039;plugintype&#039;&#039;&#039; and &#039;&#039;&#039;pluginname&#039;&#039;&#039; to make it easier to understand for 3rd party developers not working in core, but it is also applicable for core subsystems, using &#039;&#039;&#039;core&#039;&#039;&#039; as plugintype and &#039;&#039;&#039;componentname&#039;&#039;&#039; as pluginname. I will be using &#039;&#039;&#039;areaname&#039;&#039;&#039; as the area you are defining.&lt;br /&gt;
&lt;br /&gt;
=== Easy case: Activity information ===&lt;br /&gt;
&lt;br /&gt;
If you just want to index your activity basic information, like the name and the description, you can skip all the documentation below. You can do it by extending &#039;&#039;&#039;\core_search\base_activity&#039;&#039;&#039; (\core_search\area\base_activity in Moodle 3.1). Copy &amp;amp; paste mod/book/classes/search/activity.php and replace &#039;book&#039; by your &#039;activityname&#039;. If you use different fields than &#039;&#039;&#039;name&#039;&#039;&#039; and &#039;&#039;&#039;intro&#039;&#039;&#039; (the defacto standard) or you want to index extra fields or files look at mod/page/classes/search/activity.php (extra fields) or mod/assign/classes/search/activity.php (files).&lt;br /&gt;
&lt;br /&gt;
To index other information your activity tables contain (e.g. glossary or journal entries, assignment submissions...) you need to extend \core_search\base_mod instead. Continue reading below please.&lt;br /&gt;
&lt;br /&gt;
=== Base class ===&lt;br /&gt;
All the search area stuff in contained in a single &#039;&#039;&#039;\plugintype_pluginname\search\areaname&#039;&#039;&#039; class and it should be extending one of the following classes:&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base (\core_search\area\base in Moodle 3.1)&#039;&#039;&#039;: Generic base class for a search area&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_mod (\core_search\area\base_mod in Moodle 3.1)&#039;&#039;&#039;: Base class for activities search areas ([[Activity_modules]])&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_activity (\core_search\area\base_activity in Moodle 3.1)&#039;&#039;&#039;: Base class also for activities, but more specific than &#039;&#039;&#039;base_mod&#039;&#039;&#039; as it is intended to be used to index Moodle activities basic data like the activity name and description. For other specific activity data to be indexed use &#039;&#039;&#039;base_mod&#039;&#039;&#039;. If you have doubts and you don&#039;t know which one you should use think of forum activities and forum posts, forum activities should use &#039;&#039;&#039;base_activity&#039;&#039;&#039;, but forum posts should use &#039;&#039;&#039;base_mod&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_block (Available in Moodle 3.4+)&#039;&#039;&#039;: Base class for block search areas ([[Blocks]]). Only blocks displayed on some areas of the site are supported for indexing. This is based on the &#039;pagetypepattern&#039; of the block_instance. Supported page types are as follows:&lt;br /&gt;
** Blocks on the site home page (pagetypepattern = &#039;site-index&#039;)&lt;br /&gt;
** Blocks on a course home page (pagetypepattern = &#039;course-view-*&#039;)&lt;br /&gt;
** Blocks on any course page (pagetypepattern = &#039;course-*&#039;)&lt;br /&gt;
** Blocks on any page (pagetypepattern = &#039;*&#039;)&lt;br /&gt;
: Note: Indexing is not supported for blocks configured for course-module-level display.&lt;br /&gt;
&lt;br /&gt;
: &#039;&#039;&#039;\core_search\base_block&#039;&#039;&#039; includes a default implementation for &#039;&#039;&#039;get_document_recordset&#039;&#039;&#039; which is suitable for blocks storing their data in the &#039;&#039;&#039;block_instances&#039;&#039;&#039; table. Blocks of this nature only need implement the &#039;&#039;&#039;get_document&#039;&#039;&#039; function to return the document. Blocks storing their data in other tables will need to override the &#039;&#039;&#039;get_document_recordset&#039;&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Name ===&lt;br /&gt;
You will need to set a visible name for the search area, for plugins this should be defined in the plugin&#039;s language strings file and for core subsystems in &#039;&#039;lang/en/search.php&#039;&#039;. The format to set this search area name that will be visible for Moodle users (on search page filters section or on admin search pages) is &#039;&#039;&#039;search:AREANAME&#039;&#039;&#039;, so for example for forum posts we have &#039;&#039;&#039;$string[&#039;search:posts&#039;] = &#039;Forum - posts&#039;&#039;&#039;&#039; in &#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Index data ===&lt;br /&gt;
&lt;br /&gt;
This function should return a recordset with all the stuff in your component that has been modified since &#039;&#039;&#039;$modifiedfrom&#039;&#039;&#039; timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_recordset_by_timestamp($modifiedfrom = 0) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    // The idea is to include here most (if not all) of the data you will need to index (see get_document below)&lt;br /&gt;
    $sql = &amp;quot;SELECT x.* FROM {xxxxx} WHERE x.timemodified &amp;gt;= ? ORDER BY x.timemodified ASC&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // Note that this is an example, you might have more params.&lt;br /&gt;
    return $DB-&amp;gt;get_recordset_sql($sql, array($modifiedfrom);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function receives one of the previous query results ($record) and an array of options. It should return a &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; object with all the data to be indexed.&lt;br /&gt;
&lt;br /&gt;
The options this function can receive are:&lt;br /&gt;
* &#039;&#039;&#039;indexfiles&#039;&#039;&#039;: Whether to index files or not. File indexing support also depends on the backend search engine, not all of them support file indexing, no need to set if the document is new if there is no filesindexing support&lt;br /&gt;
* &#039;&#039;&#039;lastindexedtime&#039;&#039;&#039;: Also related with files, see example below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_document($record, $options = array) {&lt;br /&gt;
&lt;br /&gt;
    // All wrapped in a try &amp;amp; catch as we should not stop the indexing process because of a legacy corrupted database.&lt;br /&gt;
    try {&lt;br /&gt;
        $context = \context_course::instance($record-&amp;gt;contextid);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document, not all required data is available: &#039; .&lt;br /&gt;
            $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document: &#039; . $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Prepare associative array with data from DB.&lt;br /&gt;
    $doc = \core_search\document_factory::instance($record-&amp;gt;id, $this-&amp;gt;componentname, $this-&amp;gt;areaname);&lt;br /&gt;
&lt;br /&gt;
    // Any content should be converted to plain text.&lt;br /&gt;
&lt;br /&gt;
    // If you just have a text string you need to call content_to_text() with the $contentformat param set to false.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;title&#039;, content_to_text($record-&amp;gt;something-that-describes-the-result));&lt;br /&gt;
&lt;br /&gt;
    // For a property named &#039;content&#039; where another property &#039;contentformat&#039; is also present the text should be&lt;br /&gt;
    // passed through to content_to_text() when declaring the document. This will ensure that HTML, Markdown, ...&lt;br /&gt;
    // formats are converted to plain text. Similar to what we do with format_text.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;content&#039;, content_to_text($record-&amp;gt;content, $record-&amp;gt;contentformat));&lt;br /&gt;
&lt;br /&gt;
    $doc-&amp;gt;set(&#039;contextid&#039;, $context-&amp;gt;id);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;type&#039;, \core_search\manager::TYPE_TEXT);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;courseid&#039;, $record-&amp;gt;courseid);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;modified&#039;, $record-&amp;gt;timemodified);&lt;br /&gt;
&lt;br /&gt;
    // Optional fields.&lt;br /&gt;
&lt;br /&gt;
    // The user that created the record. It is optional. In some cases like forum posts makes sense to have it available, but in some other cases like activities it does not help much.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;userid&#039;, $record-&amp;gt;userid);&lt;br /&gt;
&lt;br /&gt;
    // In case the indexed document should only be accessed by the user that created it replace NO_OWNER_ID constant by the owner user userid.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;owneruserid&#039;, \core_search\manager::NO_OWNER_ID);&lt;br /&gt;
&lt;br /&gt;
    // Extra contents associated to the document.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description1&#039;, content_to_text($record-&amp;gt;extracontent1, $record-&amp;gt;extracontent1format));&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description2&#039;, content_to_text($record-&amp;gt;extracontent2, $record-&amp;gt;extracontent2format));&lt;br /&gt;
&lt;br /&gt;
    // Not compulsory, but speeds up things when the search area includes files (see [[#Indexing files]])&lt;br /&gt;
    if (isset($options[&#039;lastindexedtime&#039;]) &amp;amp;&amp;amp; ($options[&#039;lastindexedtime&#039;] &amp;lt; $record-&amp;gt;created)) {&lt;br /&gt;
        // If the document was created after the last index time, it must be new.&lt;br /&gt;
        $doc-&amp;gt;set_is_new(true);&lt;br /&gt;
    }&lt;br /&gt;
    return $doc;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing files ====&lt;br /&gt;
&lt;br /&gt;
First declare that the search area is interested in indexing the files attached to its documents.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function uses_file_indexing() {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Define attach_files function, which receives a \core_search\document object and fills it with stored_file objects.&lt;br /&gt;
&lt;br /&gt;
This is a simplified example of one of the cases we can find in core.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function attach_files($document) {&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
    $context = \context_module::instance($document-&amp;gt;get(&#039;contextid&#039;));&lt;br /&gt;
&lt;br /&gt;
    $files = $fs-&amp;gt;get_area_files($context-&amp;gt;id, &#039;COMPONENTNAME&#039;, &#039;FILEAREA&#039;, $document-&amp;gt;itemid);&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $document-&amp;gt;add_stored_file($file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing performance ====&lt;br /&gt;
&lt;br /&gt;
The indexing process runs by cron incrementally, keeping track of the last indexing time... In big Moodle sites this process can be very heavy as millions of records can be indexed in the same PHP process, so performance is quite important. Think that any database query that you add in your &#039;&#039;&#039;get_document&#039;&#039;&#039; function to retrieve data that is part of your document will run for every document. If you search area can potentially contain a big number of records, you might be interested in adding static caches.&lt;br /&gt;
&lt;br /&gt;
=== Access control ===&lt;br /&gt;
&lt;br /&gt;
Moodle&#039;s global search limits the access to the indexed data in two different ways.&lt;br /&gt;
&lt;br /&gt;
==== Automatic context-based filtering ====&lt;br /&gt;
&lt;br /&gt;
This is done automatically by Moodle, the user performing a query will only have access to contents in contexts where the user have access. If your search area indexes contents that belong to a course the user will only see results that belong to the courses where they have access, if your search area belongs to a course module, only to visible activities in courses where you have access and there are no completion rules preventing them to be accessible. Similarly, if your search area belong to a user, only the current user and site admins will have access to your search area contents. &#039;&#039;Note that this last option is closely related to doc&#039;s &#039;owneruserid&#039; field, the main difference is that setting &#039;owneruserid&#039; to the user id will make the search area documents unavailable to admin users.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to specify to what context (or contexts) your search area contents belong to. Overwrite &#039;&#039;&#039;$levels&#039;&#039;&#039; static attribute in your class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected static $levels = [CONTEXT_COURSE]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Final access checking ====&lt;br /&gt;
&lt;br /&gt;
Your search area is the responsible to filter out the results that &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_access($id) {&lt;br /&gt;
    try {&lt;br /&gt;
        $myobject = $this-&amp;gt;get_xxxxx($id);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        // If the record does not exist anymore in Moodle we should return \core_search\manager::ACCESS_DELETED.&lt;br /&gt;
        return \core_search\manager::ACCESS_DELETED;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        // Skip results if there is any unexpected error.&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($myobject-&amp;gt;visible === false) {&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return \core_search\manager::ACCESS_GRANTED;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The automatic context-based filtering should get rid of most of the results where a user do not have access. If &#039;&#039;&#039;check_access&#039;&#039;&#039;&#039;s ratio of visible results vs non-visible results is high you might need to rethink about the context where your search area belongs or we might need to expand the APIs.&lt;br /&gt;
&lt;br /&gt;
=== Other required methods ===&lt;br /&gt;
&lt;br /&gt;
The link to the result. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_doc_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The link to the result context, in some cases it might be the same than the doc url. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_context_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component/context.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53242</id>
		<title>Search API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53242"/>
		<updated>2017-11-14T06:52:33Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Base class */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The search API allows you to index contents in a search engine and query the search engine for results. Any Moodle component (all plugin types and all core subsystems) can define search areas for their contents.&lt;br /&gt;
&lt;br /&gt;
This is different from the internal search feature some components have.&lt;br /&gt;
&lt;br /&gt;
== Add a search area ==&lt;br /&gt;
&lt;br /&gt;
Note that I will be writing &#039;&#039;&#039;plugintype&#039;&#039;&#039; and &#039;&#039;&#039;pluginname&#039;&#039;&#039; to make it easier to understand for 3rd party developers not working in core, but it is also applicable for core subsystems, using &#039;&#039;&#039;core&#039;&#039;&#039; as plugintype and &#039;&#039;&#039;componentname&#039;&#039;&#039; as pluginname. I will be using &#039;&#039;&#039;areaname&#039;&#039;&#039; as the area you are defining.&lt;br /&gt;
&lt;br /&gt;
=== Easy case: Activity information ===&lt;br /&gt;
&lt;br /&gt;
If you just want to index your activity basic information, like the name and the description, you can skip all the documentation below. You can do it by extending &#039;&#039;&#039;\core_search\base_activity&#039;&#039;&#039; (\core_search\area\base_activity in Moodle 3.1). Copy &amp;amp; paste mod/book/classes/search/activity.php and replace &#039;book&#039; by your &#039;activityname&#039;. If you use different fields than &#039;&#039;&#039;name&#039;&#039;&#039; and &#039;&#039;&#039;intro&#039;&#039;&#039; (the defacto standard) or you want to index extra fields or files look at mod/page/classes/search/activity.php (extra fields) or mod/assign/classes/search/activity.php (files).&lt;br /&gt;
&lt;br /&gt;
To index other information your activity tables contain (e.g. glossary or journal entries, assignment submissions...) you need to extend \core_search\base_mod instead. Continue reading below please.&lt;br /&gt;
&lt;br /&gt;
=== Base class ===&lt;br /&gt;
All the search area stuff in contained in a single &#039;&#039;&#039;\plugintype_pluginname\search\areaname&#039;&#039;&#039; class and it should be extending one of the following classes:&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base (\core_search\area\base in Moodle 3.1)&#039;&#039;&#039;: Generic base class for a search area&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_mod (\core_search\area\base_mod in Moodle 3.1)&#039;&#039;&#039;: Base class for activities search areas ([[Activity_modules]])&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_activity (\core_search\area\base_activity in Moodle 3.1)&#039;&#039;&#039;: Base class also for activities, but more specific than &#039;&#039;&#039;base_mod&#039;&#039;&#039; as it is intended to be used to index Moodle activities basic data like the activity name and description. For other specific activity data to be indexed use &#039;&#039;&#039;base_mod&#039;&#039;&#039;. If you have doubts and you don&#039;t know which one you should use think of forum activities and forum posts, forum activities should use &#039;&#039;&#039;base_activity&#039;&#039;&#039;, but forum posts should use &#039;&#039;&#039;base_mod&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_block (Available in Moodle 3.4+)&#039;&#039;&#039;: Base class for block search areas ([[Blocks]]). Only blocks displayed on some areas of the site are supported for indexing. This is based on the &#039;pagetypepattern&#039; of the block_instance. Supported page types are as follows:&lt;br /&gt;
** Blocks on the site home page (pagetypepattern = &#039;site-index&#039;)&lt;br /&gt;
** Blocks on a course home page (pagetypepattern = &#039;course-view-*&#039;)&lt;br /&gt;
** Blocks on any course page (pagetypepattern = &#039;course-*&#039;)&lt;br /&gt;
** Blocks on any page (pagetypepattern = &#039;*&#039;)&lt;br /&gt;
: Note: Indexing is not supported for blocks configured for course-module-level display.&lt;br /&gt;
&lt;br /&gt;
: \core_search\base_block includes a default implementation for get_document_recordset which is suitable for blocks storing their data in the block_instances table. Blocks of this nature only need implement the get_document function to return the document. Blocks storing their data in other tables will need to override the get_document_recordset function.&lt;br /&gt;
&lt;br /&gt;
=== Name ===&lt;br /&gt;
You will need to set a visible name for the search area, for plugins this should be defined in the plugin&#039;s language strings file and for core subsystems in &#039;&#039;lang/en/search.php&#039;&#039;. The format to set this search area name that will be visible for Moodle users (on search page filters section or on admin search pages) is &#039;&#039;&#039;search:AREANAME&#039;&#039;&#039;, so for example for forum posts we have &#039;&#039;&#039;$string[&#039;search:posts&#039;] = &#039;Forum - posts&#039;&#039;&#039;&#039; in &#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Index data ===&lt;br /&gt;
&lt;br /&gt;
This function should return a recordset with all the stuff in your component that has been modified since &#039;&#039;&#039;$modifiedfrom&#039;&#039;&#039; timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_recordset_by_timestamp($modifiedfrom = 0) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    // The idea is to include here most (if not all) of the data you will need to index (see get_document below)&lt;br /&gt;
    $sql = &amp;quot;SELECT x.* FROM {xxxxx} WHERE x.timemodified &amp;gt;= ? ORDER BY x.timemodified ASC&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // Note that this is an example, you might have more params.&lt;br /&gt;
    return $DB-&amp;gt;get_recordset_sql($sql, array($modifiedfrom);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function receives one of the previous query results ($record) and an array of options. It should return a &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; object with all the data to be indexed.&lt;br /&gt;
&lt;br /&gt;
The options this function can receive are:&lt;br /&gt;
* &#039;&#039;&#039;indexfiles&#039;&#039;&#039;: Whether to index files or not. File indexing support also depends on the backend search engine, not all of them support file indexing, no need to set if the document is new if there is no filesindexing support&lt;br /&gt;
* &#039;&#039;&#039;lastindexedtime&#039;&#039;&#039;: Also related with files, see example below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_document($record, $options = array) {&lt;br /&gt;
&lt;br /&gt;
    // All wrapped in a try &amp;amp; catch as we should not stop the indexing process because of a legacy corrupted database.&lt;br /&gt;
    try {&lt;br /&gt;
        $context = \context_course::instance($record-&amp;gt;contextid);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document, not all required data is available: &#039; .&lt;br /&gt;
            $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document: &#039; . $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Prepare associative array with data from DB.&lt;br /&gt;
    $doc = \core_search\document_factory::instance($record-&amp;gt;id, $this-&amp;gt;componentname, $this-&amp;gt;areaname);&lt;br /&gt;
&lt;br /&gt;
    // Any content should be converted to plain text.&lt;br /&gt;
&lt;br /&gt;
    // If you just have a text string you need to call content_to_text() with the $contentformat param set to false.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;title&#039;, content_to_text($record-&amp;gt;something-that-describes-the-result));&lt;br /&gt;
&lt;br /&gt;
    // For a property named &#039;content&#039; where another property &#039;contentformat&#039; is also present the text should be&lt;br /&gt;
    // passed through to content_to_text() when declaring the document. This will ensure that HTML, Markdown, ...&lt;br /&gt;
    // formats are converted to plain text. Similar to what we do with format_text.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;content&#039;, content_to_text($record-&amp;gt;content, $record-&amp;gt;contentformat));&lt;br /&gt;
&lt;br /&gt;
    $doc-&amp;gt;set(&#039;contextid&#039;, $context-&amp;gt;id);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;type&#039;, \core_search\manager::TYPE_TEXT);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;courseid&#039;, $record-&amp;gt;courseid);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;modified&#039;, $record-&amp;gt;timemodified);&lt;br /&gt;
&lt;br /&gt;
    // Optional fields.&lt;br /&gt;
&lt;br /&gt;
    // The user that created the record. It is optional. In some cases like forum posts makes sense to have it available, but in some other cases like activities it does not help much.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;userid&#039;, $record-&amp;gt;userid);&lt;br /&gt;
&lt;br /&gt;
    // In case the indexed document should only be accessed by the user that created it replace NO_OWNER_ID constant by the owner user userid.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;owneruserid&#039;, \core_search\manager::NO_OWNER_ID);&lt;br /&gt;
&lt;br /&gt;
    // Extra contents associated to the document.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description1&#039;, content_to_text($record-&amp;gt;extracontent1, $record-&amp;gt;extracontent1format));&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description2&#039;, content_to_text($record-&amp;gt;extracontent2, $record-&amp;gt;extracontent2format));&lt;br /&gt;
&lt;br /&gt;
    // Not compulsory, but speeds up things when the search area includes files (see [[#Indexing files]])&lt;br /&gt;
    if (isset($options[&#039;lastindexedtime&#039;]) &amp;amp;&amp;amp; ($options[&#039;lastindexedtime&#039;] &amp;lt; $record-&amp;gt;created)) {&lt;br /&gt;
        // If the document was created after the last index time, it must be new.&lt;br /&gt;
        $doc-&amp;gt;set_is_new(true);&lt;br /&gt;
    }&lt;br /&gt;
    return $doc;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing files ====&lt;br /&gt;
&lt;br /&gt;
First declare that the search area is interested in indexing the files attached to its documents.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function uses_file_indexing() {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Define attach_files function, which receives a \core_search\document object and fills it with stored_file objects.&lt;br /&gt;
&lt;br /&gt;
This is a simplified example of one of the cases we can find in core.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function attach_files($document) {&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
    $context = \context_module::instance($document-&amp;gt;get(&#039;contextid&#039;));&lt;br /&gt;
&lt;br /&gt;
    $files = $fs-&amp;gt;get_area_files($context-&amp;gt;id, &#039;COMPONENTNAME&#039;, &#039;FILEAREA&#039;, $document-&amp;gt;itemid);&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $document-&amp;gt;add_stored_file($file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing performance ====&lt;br /&gt;
&lt;br /&gt;
The indexing process runs by cron incrementally, keeping track of the last indexing time... In big Moodle sites this process can be very heavy as millions of records can be indexed in the same PHP process, so performance is quite important. Think that any database query that you add in your &#039;&#039;&#039;get_document&#039;&#039;&#039; function to retrieve data that is part of your document will run for every document. If you search area can potentially contain a big number of records, you might be interested in adding static caches.&lt;br /&gt;
&lt;br /&gt;
=== Access control ===&lt;br /&gt;
&lt;br /&gt;
Moodle&#039;s global search limits the access to the indexed data in two different ways.&lt;br /&gt;
&lt;br /&gt;
==== Automatic context-based filtering ====&lt;br /&gt;
&lt;br /&gt;
This is done automatically by Moodle, the user performing a query will only have access to contents in contexts where the user have access. If your search area indexes contents that belong to a course the user will only see results that belong to the courses where they have access, if your search area belongs to a course module, only to visible activities in courses where you have access and there are no completion rules preventing them to be accessible. Similarly, if your search area belong to a user, only the current user and site admins will have access to your search area contents. &#039;&#039;Note that this last option is closely related to doc&#039;s &#039;owneruserid&#039; field, the main difference is that setting &#039;owneruserid&#039; to the user id will make the search area documents unavailable to admin users.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to specify to what context (or contexts) your search area contents belong to. Overwrite &#039;&#039;&#039;$levels&#039;&#039;&#039; static attribute in your class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected static $levels = [CONTEXT_COURSE]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Final access checking ====&lt;br /&gt;
&lt;br /&gt;
Your search area is the responsible to filter out the results that &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_access($id) {&lt;br /&gt;
    try {&lt;br /&gt;
        $myobject = $this-&amp;gt;get_xxxxx($id);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        // If the record does not exist anymore in Moodle we should return \core_search\manager::ACCESS_DELETED.&lt;br /&gt;
        return \core_search\manager::ACCESS_DELETED;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        // Skip results if there is any unexpected error.&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($myobject-&amp;gt;visible === false) {&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return \core_search\manager::ACCESS_GRANTED;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The automatic context-based filtering should get rid of most of the results where a user do not have access. If &#039;&#039;&#039;check_access&#039;&#039;&#039;&#039;s ratio of visible results vs non-visible results is high you might need to rethink about the context where your search area belongs or we might need to expand the APIs.&lt;br /&gt;
&lt;br /&gt;
=== Other required methods ===&lt;br /&gt;
&lt;br /&gt;
The link to the result. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_doc_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The link to the result context, in some cases it might be the same than the doc url. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_context_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component/context.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53241</id>
		<title>Search API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53241"/>
		<updated>2017-11-14T06:50:38Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Base class */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The search API allows you to index contents in a search engine and query the search engine for results. Any Moodle component (all plugin types and all core subsystems) can define search areas for their contents.&lt;br /&gt;
&lt;br /&gt;
This is different from the internal search feature some components have.&lt;br /&gt;
&lt;br /&gt;
== Add a search area ==&lt;br /&gt;
&lt;br /&gt;
Note that I will be writing &#039;&#039;&#039;plugintype&#039;&#039;&#039; and &#039;&#039;&#039;pluginname&#039;&#039;&#039; to make it easier to understand for 3rd party developers not working in core, but it is also applicable for core subsystems, using &#039;&#039;&#039;core&#039;&#039;&#039; as plugintype and &#039;&#039;&#039;componentname&#039;&#039;&#039; as pluginname. I will be using &#039;&#039;&#039;areaname&#039;&#039;&#039; as the area you are defining.&lt;br /&gt;
&lt;br /&gt;
=== Easy case: Activity information ===&lt;br /&gt;
&lt;br /&gt;
If you just want to index your activity basic information, like the name and the description, you can skip all the documentation below. You can do it by extending &#039;&#039;&#039;\core_search\base_activity&#039;&#039;&#039; (\core_search\area\base_activity in Moodle 3.1). Copy &amp;amp; paste mod/book/classes/search/activity.php and replace &#039;book&#039; by your &#039;activityname&#039;. If you use different fields than &#039;&#039;&#039;name&#039;&#039;&#039; and &#039;&#039;&#039;intro&#039;&#039;&#039; (the defacto standard) or you want to index extra fields or files look at mod/page/classes/search/activity.php (extra fields) or mod/assign/classes/search/activity.php (files).&lt;br /&gt;
&lt;br /&gt;
To index other information your activity tables contain (e.g. glossary or journal entries, assignment submissions...) you need to extend \core_search\base_mod instead. Continue reading below please.&lt;br /&gt;
&lt;br /&gt;
=== Base class ===&lt;br /&gt;
All the search area stuff in contained in a single &#039;&#039;&#039;\plugintype_pluginname\search\areaname&#039;&#039;&#039; class and it should be extending one of the following classes:&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base (\core_search\area\base in Moodle 3.1)&#039;&#039;&#039;: Generic base class for a search area&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_mod (\core_search\area\base_mod in Moodle 3.1)&#039;&#039;&#039;: Base class for activities search areas ([[Activity_modules]])&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_activity (\core_search\area\base_activity in Moodle 3.1)&#039;&#039;&#039;: Base class also for activities, but more specific than &#039;&#039;&#039;base_mod&#039;&#039;&#039; as it is intended to be used to index Moodle activities basic data like the activity name and description. For other specific activity data to be indexed use &#039;&#039;&#039;base_mod&#039;&#039;&#039;. If you have doubts and you don&#039;t know which one you should use think of forum activities and forum posts, forum activities should use &#039;&#039;&#039;base_activity&#039;&#039;&#039;, but forum posts should use &#039;&#039;&#039;base_mod&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_block (Available in Moodle 3.4+)&#039;&#039;&#039;: Base class for block search areas ([[Blocks]]). Only blocks displayed on some areas of the site are supported for indexing. This is based on the &#039;pagetypepattern&#039; of the block_instance. Supported page types are as follows:&lt;br /&gt;
** Blocks on the site home page (pagetypepattern = &#039;site-index&#039;)&lt;br /&gt;
** Blocks on a course home page (pagetypepattern = &#039;course-view-*&#039;)&lt;br /&gt;
** Blocks on any course page (pagetypepattern = &#039;course-*&#039;)&lt;br /&gt;
** Blocks on any page (pagetypepattern = &#039;*&#039;)&lt;br /&gt;
*: Note: Indexing is not supported for blocks configured for course-module-level display.&lt;br /&gt;
*:&lt;br /&gt;
*: \core_search\base_block includes a default implementation for get_document_recordset which is suitable for blocks storing their data in the block_instances table. Blocks of this nature only need implement the get_document function to return the document. Blocks storing their data in other tables will need to override the get_document_recordset function.&lt;br /&gt;
&lt;br /&gt;
=== Name ===&lt;br /&gt;
You will need to set a visible name for the search area, for plugins this should be defined in the plugin&#039;s language strings file and for core subsystems in &#039;&#039;lang/en/search.php&#039;&#039;. The format to set this search area name that will be visible for Moodle users (on search page filters section or on admin search pages) is &#039;&#039;&#039;search:AREANAME&#039;&#039;&#039;, so for example for forum posts we have &#039;&#039;&#039;$string[&#039;search:posts&#039;] = &#039;Forum - posts&#039;&#039;&#039;&#039; in &#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Index data ===&lt;br /&gt;
&lt;br /&gt;
This function should return a recordset with all the stuff in your component that has been modified since &#039;&#039;&#039;$modifiedfrom&#039;&#039;&#039; timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_recordset_by_timestamp($modifiedfrom = 0) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    // The idea is to include here most (if not all) of the data you will need to index (see get_document below)&lt;br /&gt;
    $sql = &amp;quot;SELECT x.* FROM {xxxxx} WHERE x.timemodified &amp;gt;= ? ORDER BY x.timemodified ASC&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // Note that this is an example, you might have more params.&lt;br /&gt;
    return $DB-&amp;gt;get_recordset_sql($sql, array($modifiedfrom);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function receives one of the previous query results ($record) and an array of options. It should return a &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; object with all the data to be indexed.&lt;br /&gt;
&lt;br /&gt;
The options this function can receive are:&lt;br /&gt;
* &#039;&#039;&#039;indexfiles&#039;&#039;&#039;: Whether to index files or not. File indexing support also depends on the backend search engine, not all of them support file indexing, no need to set if the document is new if there is no filesindexing support&lt;br /&gt;
* &#039;&#039;&#039;lastindexedtime&#039;&#039;&#039;: Also related with files, see example below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_document($record, $options = array) {&lt;br /&gt;
&lt;br /&gt;
    // All wrapped in a try &amp;amp; catch as we should not stop the indexing process because of a legacy corrupted database.&lt;br /&gt;
    try {&lt;br /&gt;
        $context = \context_course::instance($record-&amp;gt;contextid);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document, not all required data is available: &#039; .&lt;br /&gt;
            $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document: &#039; . $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Prepare associative array with data from DB.&lt;br /&gt;
    $doc = \core_search\document_factory::instance($record-&amp;gt;id, $this-&amp;gt;componentname, $this-&amp;gt;areaname);&lt;br /&gt;
&lt;br /&gt;
    // Any content should be converted to plain text.&lt;br /&gt;
&lt;br /&gt;
    // If you just have a text string you need to call content_to_text() with the $contentformat param set to false.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;title&#039;, content_to_text($record-&amp;gt;something-that-describes-the-result));&lt;br /&gt;
&lt;br /&gt;
    // For a property named &#039;content&#039; where another property &#039;contentformat&#039; is also present the text should be&lt;br /&gt;
    // passed through to content_to_text() when declaring the document. This will ensure that HTML, Markdown, ...&lt;br /&gt;
    // formats are converted to plain text. Similar to what we do with format_text.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;content&#039;, content_to_text($record-&amp;gt;content, $record-&amp;gt;contentformat));&lt;br /&gt;
&lt;br /&gt;
    $doc-&amp;gt;set(&#039;contextid&#039;, $context-&amp;gt;id);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;type&#039;, \core_search\manager::TYPE_TEXT);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;courseid&#039;, $record-&amp;gt;courseid);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;modified&#039;, $record-&amp;gt;timemodified);&lt;br /&gt;
&lt;br /&gt;
    // Optional fields.&lt;br /&gt;
&lt;br /&gt;
    // The user that created the record. It is optional. In some cases like forum posts makes sense to have it available, but in some other cases like activities it does not help much.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;userid&#039;, $record-&amp;gt;userid);&lt;br /&gt;
&lt;br /&gt;
    // In case the indexed document should only be accessed by the user that created it replace NO_OWNER_ID constant by the owner user userid.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;owneruserid&#039;, \core_search\manager::NO_OWNER_ID);&lt;br /&gt;
&lt;br /&gt;
    // Extra contents associated to the document.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description1&#039;, content_to_text($record-&amp;gt;extracontent1, $record-&amp;gt;extracontent1format));&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description2&#039;, content_to_text($record-&amp;gt;extracontent2, $record-&amp;gt;extracontent2format));&lt;br /&gt;
&lt;br /&gt;
    // Not compulsory, but speeds up things when the search area includes files (see [[#Indexing files]])&lt;br /&gt;
    if (isset($options[&#039;lastindexedtime&#039;]) &amp;amp;&amp;amp; ($options[&#039;lastindexedtime&#039;] &amp;lt; $record-&amp;gt;created)) {&lt;br /&gt;
        // If the document was created after the last index time, it must be new.&lt;br /&gt;
        $doc-&amp;gt;set_is_new(true);&lt;br /&gt;
    }&lt;br /&gt;
    return $doc;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing files ====&lt;br /&gt;
&lt;br /&gt;
First declare that the search area is interested in indexing the files attached to its documents.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function uses_file_indexing() {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Define attach_files function, which receives a \core_search\document object and fills it with stored_file objects.&lt;br /&gt;
&lt;br /&gt;
This is a simplified example of one of the cases we can find in core.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function attach_files($document) {&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
    $context = \context_module::instance($document-&amp;gt;get(&#039;contextid&#039;));&lt;br /&gt;
&lt;br /&gt;
    $files = $fs-&amp;gt;get_area_files($context-&amp;gt;id, &#039;COMPONENTNAME&#039;, &#039;FILEAREA&#039;, $document-&amp;gt;itemid);&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $document-&amp;gt;add_stored_file($file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing performance ====&lt;br /&gt;
&lt;br /&gt;
The indexing process runs by cron incrementally, keeping track of the last indexing time... In big Moodle sites this process can be very heavy as millions of records can be indexed in the same PHP process, so performance is quite important. Think that any database query that you add in your &#039;&#039;&#039;get_document&#039;&#039;&#039; function to retrieve data that is part of your document will run for every document. If you search area can potentially contain a big number of records, you might be interested in adding static caches.&lt;br /&gt;
&lt;br /&gt;
=== Access control ===&lt;br /&gt;
&lt;br /&gt;
Moodle&#039;s global search limits the access to the indexed data in two different ways.&lt;br /&gt;
&lt;br /&gt;
==== Automatic context-based filtering ====&lt;br /&gt;
&lt;br /&gt;
This is done automatically by Moodle, the user performing a query will only have access to contents in contexts where the user have access. If your search area indexes contents that belong to a course the user will only see results that belong to the courses where they have access, if your search area belongs to a course module, only to visible activities in courses where you have access and there are no completion rules preventing them to be accessible. Similarly, if your search area belong to a user, only the current user and site admins will have access to your search area contents. &#039;&#039;Note that this last option is closely related to doc&#039;s &#039;owneruserid&#039; field, the main difference is that setting &#039;owneruserid&#039; to the user id will make the search area documents unavailable to admin users.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to specify to what context (or contexts) your search area contents belong to. Overwrite &#039;&#039;&#039;$levels&#039;&#039;&#039; static attribute in your class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected static $levels = [CONTEXT_COURSE]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Final access checking ====&lt;br /&gt;
&lt;br /&gt;
Your search area is the responsible to filter out the results that &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_access($id) {&lt;br /&gt;
    try {&lt;br /&gt;
        $myobject = $this-&amp;gt;get_xxxxx($id);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        // If the record does not exist anymore in Moodle we should return \core_search\manager::ACCESS_DELETED.&lt;br /&gt;
        return \core_search\manager::ACCESS_DELETED;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        // Skip results if there is any unexpected error.&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($myobject-&amp;gt;visible === false) {&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return \core_search\manager::ACCESS_GRANTED;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The automatic context-based filtering should get rid of most of the results where a user do not have access. If &#039;&#039;&#039;check_access&#039;&#039;&#039;&#039;s ratio of visible results vs non-visible results is high you might need to rethink about the context where your search area belongs or we might need to expand the APIs.&lt;br /&gt;
&lt;br /&gt;
=== Other required methods ===&lt;br /&gt;
&lt;br /&gt;
The link to the result. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_doc_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The link to the result context, in some cases it might be the same than the doc url. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_context_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component/context.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53240</id>
		<title>Search API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53240"/>
		<updated>2017-11-14T06:49:02Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Base class */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The search API allows you to index contents in a search engine and query the search engine for results. Any Moodle component (all plugin types and all core subsystems) can define search areas for their contents.&lt;br /&gt;
&lt;br /&gt;
This is different from the internal search feature some components have.&lt;br /&gt;
&lt;br /&gt;
== Add a search area ==&lt;br /&gt;
&lt;br /&gt;
Note that I will be writing &#039;&#039;&#039;plugintype&#039;&#039;&#039; and &#039;&#039;&#039;pluginname&#039;&#039;&#039; to make it easier to understand for 3rd party developers not working in core, but it is also applicable for core subsystems, using &#039;&#039;&#039;core&#039;&#039;&#039; as plugintype and &#039;&#039;&#039;componentname&#039;&#039;&#039; as pluginname. I will be using &#039;&#039;&#039;areaname&#039;&#039;&#039; as the area you are defining.&lt;br /&gt;
&lt;br /&gt;
=== Easy case: Activity information ===&lt;br /&gt;
&lt;br /&gt;
If you just want to index your activity basic information, like the name and the description, you can skip all the documentation below. You can do it by extending &#039;&#039;&#039;\core_search\base_activity&#039;&#039;&#039; (\core_search\area\base_activity in Moodle 3.1). Copy &amp;amp; paste mod/book/classes/search/activity.php and replace &#039;book&#039; by your &#039;activityname&#039;. If you use different fields than &#039;&#039;&#039;name&#039;&#039;&#039; and &#039;&#039;&#039;intro&#039;&#039;&#039; (the defacto standard) or you want to index extra fields or files look at mod/page/classes/search/activity.php (extra fields) or mod/assign/classes/search/activity.php (files).&lt;br /&gt;
&lt;br /&gt;
To index other information your activity tables contain (e.g. glossary or journal entries, assignment submissions...) you need to extend \core_search\base_mod instead. Continue reading below please.&lt;br /&gt;
&lt;br /&gt;
=== Base class ===&lt;br /&gt;
All the search area stuff in contained in a single &#039;&#039;&#039;\plugintype_pluginname\search\areaname&#039;&#039;&#039; class and it should be extending one of the following classes:&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base (\core_search\area\base in Moodle 3.1)&#039;&#039;&#039;: Generic base class for a search area&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_mod (\core_search\area\base_mod in Moodle 3.1)&#039;&#039;&#039;: Base class for activities search areas ([[Activity_modules]])&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_activity (\core_search\area\base_activity in Moodle 3.1)&#039;&#039;&#039;: Base class also for activities, but more specific than &#039;&#039;&#039;base_mod&#039;&#039;&#039; as it is intended to be used to index Moodle activities basic data like the activity name and description. For other specific activity data to be indexed use &#039;&#039;&#039;base_mod&#039;&#039;&#039;. If you have doubts and you don&#039;t know which one you should use think of forum activities and forum posts, forum activities should use &#039;&#039;&#039;base_activity&#039;&#039;&#039;, but forum posts should use &#039;&#039;&#039;base_mod&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_block (Available in Moodle 3.4+)&#039;&#039;&#039;: Base class for block search areas ([[Blocks]]). Only blocks displayed on some areas of the site are supported for indexing. This is based on the &#039;pagetypepattern&#039; of the block_instance. Supported page types are as follows:&lt;br /&gt;
** Blocks on the site home page (pagetypepattern = &#039;site-index&#039;)&lt;br /&gt;
** Blocks on a course home page (pagetypepattern = &#039;course-view-*&#039;)&lt;br /&gt;
** Blocks on any course page (pagetypepattern = &#039;course-*&#039;)&lt;br /&gt;
** Blocks on any page (pagetypepattern = &#039;*&#039;)&lt;br /&gt;
*: Indexing is not supported for blocks configured for course-module-level display.&lt;br /&gt;
*:&lt;br /&gt;
*:\core_search\base_block includes a default implementation for get_document_recordset which is suitable for blocks storing their data in the block_instances table. Blocks of this nature only need implement the get_document function to return the document. Blocks storing their data in other tables will need to override the get_document_recordset function.&lt;br /&gt;
&lt;br /&gt;
=== Name ===&lt;br /&gt;
You will need to set a visible name for the search area, for plugins this should be defined in the plugin&#039;s language strings file and for core subsystems in &#039;&#039;lang/en/search.php&#039;&#039;. The format to set this search area name that will be visible for Moodle users (on search page filters section or on admin search pages) is &#039;&#039;&#039;search:AREANAME&#039;&#039;&#039;, so for example for forum posts we have &#039;&#039;&#039;$string[&#039;search:posts&#039;] = &#039;Forum - posts&#039;&#039;&#039;&#039; in &#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Index data ===&lt;br /&gt;
&lt;br /&gt;
This function should return a recordset with all the stuff in your component that has been modified since &#039;&#039;&#039;$modifiedfrom&#039;&#039;&#039; timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_recordset_by_timestamp($modifiedfrom = 0) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    // The idea is to include here most (if not all) of the data you will need to index (see get_document below)&lt;br /&gt;
    $sql = &amp;quot;SELECT x.* FROM {xxxxx} WHERE x.timemodified &amp;gt;= ? ORDER BY x.timemodified ASC&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // Note that this is an example, you might have more params.&lt;br /&gt;
    return $DB-&amp;gt;get_recordset_sql($sql, array($modifiedfrom);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function receives one of the previous query results ($record) and an array of options. It should return a &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; object with all the data to be indexed.&lt;br /&gt;
&lt;br /&gt;
The options this function can receive are:&lt;br /&gt;
* &#039;&#039;&#039;indexfiles&#039;&#039;&#039;: Whether to index files or not. File indexing support also depends on the backend search engine, not all of them support file indexing, no need to set if the document is new if there is no filesindexing support&lt;br /&gt;
* &#039;&#039;&#039;lastindexedtime&#039;&#039;&#039;: Also related with files, see example below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_document($record, $options = array) {&lt;br /&gt;
&lt;br /&gt;
    // All wrapped in a try &amp;amp; catch as we should not stop the indexing process because of a legacy corrupted database.&lt;br /&gt;
    try {&lt;br /&gt;
        $context = \context_course::instance($record-&amp;gt;contextid);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document, not all required data is available: &#039; .&lt;br /&gt;
            $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document: &#039; . $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Prepare associative array with data from DB.&lt;br /&gt;
    $doc = \core_search\document_factory::instance($record-&amp;gt;id, $this-&amp;gt;componentname, $this-&amp;gt;areaname);&lt;br /&gt;
&lt;br /&gt;
    // Any content should be converted to plain text.&lt;br /&gt;
&lt;br /&gt;
    // If you just have a text string you need to call content_to_text() with the $contentformat param set to false.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;title&#039;, content_to_text($record-&amp;gt;something-that-describes-the-result));&lt;br /&gt;
&lt;br /&gt;
    // For a property named &#039;content&#039; where another property &#039;contentformat&#039; is also present the text should be&lt;br /&gt;
    // passed through to content_to_text() when declaring the document. This will ensure that HTML, Markdown, ...&lt;br /&gt;
    // formats are converted to plain text. Similar to what we do with format_text.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;content&#039;, content_to_text($record-&amp;gt;content, $record-&amp;gt;contentformat));&lt;br /&gt;
&lt;br /&gt;
    $doc-&amp;gt;set(&#039;contextid&#039;, $context-&amp;gt;id);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;type&#039;, \core_search\manager::TYPE_TEXT);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;courseid&#039;, $record-&amp;gt;courseid);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;modified&#039;, $record-&amp;gt;timemodified);&lt;br /&gt;
&lt;br /&gt;
    // Optional fields.&lt;br /&gt;
&lt;br /&gt;
    // The user that created the record. It is optional. In some cases like forum posts makes sense to have it available, but in some other cases like activities it does not help much.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;userid&#039;, $record-&amp;gt;userid);&lt;br /&gt;
&lt;br /&gt;
    // In case the indexed document should only be accessed by the user that created it replace NO_OWNER_ID constant by the owner user userid.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;owneruserid&#039;, \core_search\manager::NO_OWNER_ID);&lt;br /&gt;
&lt;br /&gt;
    // Extra contents associated to the document.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description1&#039;, content_to_text($record-&amp;gt;extracontent1, $record-&amp;gt;extracontent1format));&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description2&#039;, content_to_text($record-&amp;gt;extracontent2, $record-&amp;gt;extracontent2format));&lt;br /&gt;
&lt;br /&gt;
    // Not compulsory, but speeds up things when the search area includes files (see [[#Indexing files]])&lt;br /&gt;
    if (isset($options[&#039;lastindexedtime&#039;]) &amp;amp;&amp;amp; ($options[&#039;lastindexedtime&#039;] &amp;lt; $record-&amp;gt;created)) {&lt;br /&gt;
        // If the document was created after the last index time, it must be new.&lt;br /&gt;
        $doc-&amp;gt;set_is_new(true);&lt;br /&gt;
    }&lt;br /&gt;
    return $doc;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing files ====&lt;br /&gt;
&lt;br /&gt;
First declare that the search area is interested in indexing the files attached to its documents.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function uses_file_indexing() {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Define attach_files function, which receives a \core_search\document object and fills it with stored_file objects.&lt;br /&gt;
&lt;br /&gt;
This is a simplified example of one of the cases we can find in core.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function attach_files($document) {&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
    $context = \context_module::instance($document-&amp;gt;get(&#039;contextid&#039;));&lt;br /&gt;
&lt;br /&gt;
    $files = $fs-&amp;gt;get_area_files($context-&amp;gt;id, &#039;COMPONENTNAME&#039;, &#039;FILEAREA&#039;, $document-&amp;gt;itemid);&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $document-&amp;gt;add_stored_file($file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing performance ====&lt;br /&gt;
&lt;br /&gt;
The indexing process runs by cron incrementally, keeping track of the last indexing time... In big Moodle sites this process can be very heavy as millions of records can be indexed in the same PHP process, so performance is quite important. Think that any database query that you add in your &#039;&#039;&#039;get_document&#039;&#039;&#039; function to retrieve data that is part of your document will run for every document. If you search area can potentially contain a big number of records, you might be interested in adding static caches.&lt;br /&gt;
&lt;br /&gt;
=== Access control ===&lt;br /&gt;
&lt;br /&gt;
Moodle&#039;s global search limits the access to the indexed data in two different ways.&lt;br /&gt;
&lt;br /&gt;
==== Automatic context-based filtering ====&lt;br /&gt;
&lt;br /&gt;
This is done automatically by Moodle, the user performing a query will only have access to contents in contexts where the user have access. If your search area indexes contents that belong to a course the user will only see results that belong to the courses where they have access, if your search area belongs to a course module, only to visible activities in courses where you have access and there are no completion rules preventing them to be accessible. Similarly, if your search area belong to a user, only the current user and site admins will have access to your search area contents. &#039;&#039;Note that this last option is closely related to doc&#039;s &#039;owneruserid&#039; field, the main difference is that setting &#039;owneruserid&#039; to the user id will make the search area documents unavailable to admin users.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to specify to what context (or contexts) your search area contents belong to. Overwrite &#039;&#039;&#039;$levels&#039;&#039;&#039; static attribute in your class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected static $levels = [CONTEXT_COURSE]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Final access checking ====&lt;br /&gt;
&lt;br /&gt;
Your search area is the responsible to filter out the results that &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_access($id) {&lt;br /&gt;
    try {&lt;br /&gt;
        $myobject = $this-&amp;gt;get_xxxxx($id);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        // If the record does not exist anymore in Moodle we should return \core_search\manager::ACCESS_DELETED.&lt;br /&gt;
        return \core_search\manager::ACCESS_DELETED;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        // Skip results if there is any unexpected error.&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($myobject-&amp;gt;visible === false) {&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return \core_search\manager::ACCESS_GRANTED;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The automatic context-based filtering should get rid of most of the results where a user do not have access. If &#039;&#039;&#039;check_access&#039;&#039;&#039;&#039;s ratio of visible results vs non-visible results is high you might need to rethink about the context where your search area belongs or we might need to expand the APIs.&lt;br /&gt;
&lt;br /&gt;
=== Other required methods ===&lt;br /&gt;
&lt;br /&gt;
The link to the result. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_doc_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The link to the result context, in some cases it might be the same than the doc url. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_context_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component/context.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53239</id>
		<title>Search API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Search_API&amp;diff=53239"/>
		<updated>2017-11-14T06:39:46Z</updated>

		<summary type="html">&lt;p&gt;Jaked: /* Base class */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The search API allows you to index contents in a search engine and query the search engine for results. Any Moodle component (all plugin types and all core subsystems) can define search areas for their contents.&lt;br /&gt;
&lt;br /&gt;
This is different from the internal search feature some components have.&lt;br /&gt;
&lt;br /&gt;
== Add a search area ==&lt;br /&gt;
&lt;br /&gt;
Note that I will be writing &#039;&#039;&#039;plugintype&#039;&#039;&#039; and &#039;&#039;&#039;pluginname&#039;&#039;&#039; to make it easier to understand for 3rd party developers not working in core, but it is also applicable for core subsystems, using &#039;&#039;&#039;core&#039;&#039;&#039; as plugintype and &#039;&#039;&#039;componentname&#039;&#039;&#039; as pluginname. I will be using &#039;&#039;&#039;areaname&#039;&#039;&#039; as the area you are defining.&lt;br /&gt;
&lt;br /&gt;
=== Easy case: Activity information ===&lt;br /&gt;
&lt;br /&gt;
If you just want to index your activity basic information, like the name and the description, you can skip all the documentation below. You can do it by extending &#039;&#039;&#039;\core_search\base_activity&#039;&#039;&#039; (\core_search\area\base_activity in Moodle 3.1). Copy &amp;amp; paste mod/book/classes/search/activity.php and replace &#039;book&#039; by your &#039;activityname&#039;. If you use different fields than &#039;&#039;&#039;name&#039;&#039;&#039; and &#039;&#039;&#039;intro&#039;&#039;&#039; (the defacto standard) or you want to index extra fields or files look at mod/page/classes/search/activity.php (extra fields) or mod/assign/classes/search/activity.php (files).&lt;br /&gt;
&lt;br /&gt;
To index other information your activity tables contain (e.g. glossary or journal entries, assignment submissions...) you need to extend \core_search\base_mod instead. Continue reading below please.&lt;br /&gt;
&lt;br /&gt;
=== Base class ===&lt;br /&gt;
All the search area stuff in contained in a single &#039;&#039;&#039;\plugintype_pluginname\search\areaname&#039;&#039;&#039; class and it should be extending one of the following classes:&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base (\core_search\area\base in Moodle 3.1)&#039;&#039;&#039;: Generic base class for a search area&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_mod (\core_search\area\base_mod in Moodle 3.1)&#039;&#039;&#039;: Base class for activities search areas ([[Activity_modules]])&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_activity (\core_search\area\base_activity in Moodle 3.1)&#039;&#039;&#039;: Base class also for activities, but more specific than &#039;&#039;&#039;base_mod&#039;&#039;&#039; as it is intended to be used to index Moodle activities basic data like the activity name and description. For other specific activity data to be indexed use &#039;&#039;&#039;base_mod&#039;&#039;&#039;. If you have doubts and you don&#039;t know which one you should use think of forum activities and forum posts, forum activities should use &#039;&#039;&#039;base_activity&#039;&#039;&#039;, but forum posts should use &#039;&#039;&#039;base_mod&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;\core_search\base_block (Available in Moodle 3.4+)&#039;&#039;&#039;: Base class for block search areas ([[Blocks]]). Only blocks displayed on some areas of the site are supported for indexing. This is based on the &#039;pagetypepattern&#039; of the block_instance. Supported page types are as follows:&lt;br /&gt;
** Blocks on the site home page (pagetypepattern = &#039;site-index&#039;)&lt;br /&gt;
** Blocks on a course home page (pagetypepattern = &#039;course-view-*&#039;)&lt;br /&gt;
** Blocks on any course page (pagetypepattern = &#039;course-*&#039;)&lt;br /&gt;
** Blocks on any page (pagetypepattern = &#039;*&#039;)&lt;br /&gt;
*: Indexing is not supported for blocks configured for course-module-level display.&lt;br /&gt;
&lt;br /&gt;
=== Name ===&lt;br /&gt;
You will need to set a visible name for the search area, for plugins this should be defined in the plugin&#039;s language strings file and for core subsystems in &#039;&#039;lang/en/search.php&#039;&#039;. The format to set this search area name that will be visible for Moodle users (on search page filters section or on admin search pages) is &#039;&#039;&#039;search:AREANAME&#039;&#039;&#039;, so for example for forum posts we have &#039;&#039;&#039;$string[&#039;search:posts&#039;] = &#039;Forum - posts&#039;&#039;&#039;&#039; in &#039;&#039;mod/forum/lang/en/forum.php&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Index data ===&lt;br /&gt;
&lt;br /&gt;
This function should return a recordset with all the stuff in your component that has been modified since &#039;&#039;&#039;$modifiedfrom&#039;&#039;&#039; timestamp (integer).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_recordset_by_timestamp($modifiedfrom = 0) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    // The idea is to include here most (if not all) of the data you will need to index (see get_document below)&lt;br /&gt;
    $sql = &amp;quot;SELECT x.* FROM {xxxxx} WHERE x.timemodified &amp;gt;= ? ORDER BY x.timemodified ASC&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // Note that this is an example, you might have more params.&lt;br /&gt;
    return $DB-&amp;gt;get_recordset_sql($sql, array($modifiedfrom);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function receives one of the previous query results ($record) and an array of options. It should return a &#039;&#039;&#039;\core_search\document&#039;&#039;&#039; object with all the data to be indexed.&lt;br /&gt;
&lt;br /&gt;
The options this function can receive are:&lt;br /&gt;
* &#039;&#039;&#039;indexfiles&#039;&#039;&#039;: Whether to index files or not. File indexing support also depends on the backend search engine, not all of them support file indexing, no need to set if the document is new if there is no filesindexing support&lt;br /&gt;
* &#039;&#039;&#039;lastindexedtime&#039;&#039;&#039;: Also related with files, see example below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_document($record, $options = array) {&lt;br /&gt;
&lt;br /&gt;
    // All wrapped in a try &amp;amp; catch as we should not stop the indexing process because of a legacy corrupted database.&lt;br /&gt;
    try {&lt;br /&gt;
        $context = \context_course::instance($record-&amp;gt;contextid);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document, not all required data is available: &#039; .&lt;br /&gt;
            $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        debugging(&#039;Error retrieving &#039; . $this-&amp;gt;areaid . &#039; &#039; . $record-&amp;gt;id . &#039; document: &#039; . $ex-&amp;gt;getMessage(), DEBUG_DEVELOPER);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Prepare associative array with data from DB.&lt;br /&gt;
    $doc = \core_search\document_factory::instance($record-&amp;gt;id, $this-&amp;gt;componentname, $this-&amp;gt;areaname);&lt;br /&gt;
&lt;br /&gt;
    // Any content should be converted to plain text.&lt;br /&gt;
&lt;br /&gt;
    // If you just have a text string you need to call content_to_text() with the $contentformat param set to false.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;title&#039;, content_to_text($record-&amp;gt;something-that-describes-the-result));&lt;br /&gt;
&lt;br /&gt;
    // For a property named &#039;content&#039; where another property &#039;contentformat&#039; is also present the text should be&lt;br /&gt;
    // passed through to content_to_text() when declaring the document. This will ensure that HTML, Markdown, ...&lt;br /&gt;
    // formats are converted to plain text. Similar to what we do with format_text.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;content&#039;, content_to_text($record-&amp;gt;content, $record-&amp;gt;contentformat));&lt;br /&gt;
&lt;br /&gt;
    $doc-&amp;gt;set(&#039;contextid&#039;, $context-&amp;gt;id);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;type&#039;, \core_search\manager::TYPE_TEXT);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;courseid&#039;, $record-&amp;gt;courseid);&lt;br /&gt;
    $doc-&amp;gt;set(&#039;modified&#039;, $record-&amp;gt;timemodified);&lt;br /&gt;
&lt;br /&gt;
    // Optional fields.&lt;br /&gt;
&lt;br /&gt;
    // The user that created the record. It is optional. In some cases like forum posts makes sense to have it available, but in some other cases like activities it does not help much.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;userid&#039;, $record-&amp;gt;userid);&lt;br /&gt;
&lt;br /&gt;
    // In case the indexed document should only be accessed by the user that created it replace NO_OWNER_ID constant by the owner user userid.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;owneruserid&#039;, \core_search\manager::NO_OWNER_ID);&lt;br /&gt;
&lt;br /&gt;
    // Extra contents associated to the document.&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description1&#039;, content_to_text($record-&amp;gt;extracontent1, $record-&amp;gt;extracontent1format));&lt;br /&gt;
    $doc-&amp;gt;set(&#039;description2&#039;, content_to_text($record-&amp;gt;extracontent2, $record-&amp;gt;extracontent2format));&lt;br /&gt;
&lt;br /&gt;
    // Not compulsory, but speeds up things when the search area includes files (see [[#Indexing files]])&lt;br /&gt;
    if (isset($options[&#039;lastindexedtime&#039;]) &amp;amp;&amp;amp; ($options[&#039;lastindexedtime&#039;] &amp;lt; $record-&amp;gt;created)) {&lt;br /&gt;
        // If the document was created after the last index time, it must be new.&lt;br /&gt;
        $doc-&amp;gt;set_is_new(true);&lt;br /&gt;
    }&lt;br /&gt;
    return $doc;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing files ====&lt;br /&gt;
&lt;br /&gt;
First declare that the search area is interested in indexing the files attached to its documents.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function uses_file_indexing() {&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Define attach_files function, which receives a \core_search\document object and fills it with stored_file objects.&lt;br /&gt;
&lt;br /&gt;
This is a simplified example of one of the cases we can find in core.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function attach_files($document) {&lt;br /&gt;
    $fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
    $context = \context_module::instance($document-&amp;gt;get(&#039;contextid&#039;));&lt;br /&gt;
&lt;br /&gt;
    $files = $fs-&amp;gt;get_area_files($context-&amp;gt;id, &#039;COMPONENTNAME&#039;, &#039;FILEAREA&#039;, $document-&amp;gt;itemid);&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $document-&amp;gt;add_stored_file($file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Indexing performance ====&lt;br /&gt;
&lt;br /&gt;
The indexing process runs by cron incrementally, keeping track of the last indexing time... In big Moodle sites this process can be very heavy as millions of records can be indexed in the same PHP process, so performance is quite important. Think that any database query that you add in your &#039;&#039;&#039;get_document&#039;&#039;&#039; function to retrieve data that is part of your document will run for every document. If you search area can potentially contain a big number of records, you might be interested in adding static caches.&lt;br /&gt;
&lt;br /&gt;
=== Access control ===&lt;br /&gt;
&lt;br /&gt;
Moodle&#039;s global search limits the access to the indexed data in two different ways.&lt;br /&gt;
&lt;br /&gt;
==== Automatic context-based filtering ====&lt;br /&gt;
&lt;br /&gt;
This is done automatically by Moodle, the user performing a query will only have access to contents in contexts where the user have access. If your search area indexes contents that belong to a course the user will only see results that belong to the courses where they have access, if your search area belongs to a course module, only to visible activities in courses where you have access and there are no completion rules preventing them to be accessible. Similarly, if your search area belong to a user, only the current user and site admins will have access to your search area contents. &#039;&#039;Note that this last option is closely related to doc&#039;s &#039;owneruserid&#039; field, the main difference is that setting &#039;owneruserid&#039; to the user id will make the search area documents unavailable to admin users.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to specify to what context (or contexts) your search area contents belong to. Overwrite &#039;&#039;&#039;$levels&#039;&#039;&#039; static attribute in your class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected static $levels = [CONTEXT_COURSE]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Final access checking ====&lt;br /&gt;
&lt;br /&gt;
Your search area is the responsible to filter out the results that &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function check_access($id) {&lt;br /&gt;
    try {&lt;br /&gt;
        $myobject = $this-&amp;gt;get_xxxxx($id);&lt;br /&gt;
    } catch (\dml_missing_record_exception $ex) {&lt;br /&gt;
        // If the record does not exist anymore in Moodle we should return \core_search\manager::ACCESS_DELETED.&lt;br /&gt;
        return \core_search\manager::ACCESS_DELETED;&lt;br /&gt;
    } catch (\dml_exception $ex) {&lt;br /&gt;
        // Skip results if there is any unexpected error.&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($myobject-&amp;gt;visible === false) {&lt;br /&gt;
        return \core_search\manager::ACCESS_DENIED;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return \core_search\manager::ACCESS_GRANTED;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The automatic context-based filtering should get rid of most of the results where a user do not have access. If &#039;&#039;&#039;check_access&#039;&#039;&#039;&#039;s ratio of visible results vs non-visible results is high you might need to rethink about the context where your search area belongs or we might need to expand the APIs.&lt;br /&gt;
&lt;br /&gt;
=== Other required methods ===&lt;br /&gt;
&lt;br /&gt;
The link to the result. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_doc_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The link to the result context, in some cases it might be the same than the doc url. Should return a \moodle_url object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function get_context_url(\core_search\document $doc) {&lt;br /&gt;
    // This is just an example, can vary a lot depending on what are you indexing.&lt;br /&gt;
    return new \moodle_url(&#039;link/to/your/component/context.php&#039;, array(&#039;id&#039; =&amp;gt; $doc-&amp;gt;get(&#039;id&#039;)));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Search]]&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_System_API&amp;diff=52955</id>
		<title>File System API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_System_API&amp;diff=52955"/>
		<updated>2017-09-20T06:27:48Z</updated>

		<summary type="html">&lt;p&gt;Jaked: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.3}}&lt;br /&gt;
As standard Moodle uses the locally available file system for all files.&lt;br /&gt;
Since Moodle 3.3 it is possible to extend the file system component of the File Storage API to support alternative File Systems.&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Moodle ships with a file system API which enables the internal Moodle File Storage system to set, and retrieve files, and file content.&lt;br /&gt;
The standard file system implementation uses the Moodle filedir, which is a locally available directory on disk, and which can be shared between clustered servers via network file systems such as NFS.&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.3 it is possible to use alternative file systems, including remote file systems. These are easy to setup and configure, and allow for greater scalability which does not depends so heavily upon traditional network file systems. See config-dist.php for more details. &lt;br /&gt;
&lt;br /&gt;
Please note that the file *must* exist remotely and a local file is *optional*. The remote file is seen as canonical and this system is *not* designed to be used to cater to a split brain solution where some files are present in one place, and some files are present in another.&lt;br /&gt;
&lt;br /&gt;
All files accessed via the standard File Storage API is processed using this API, however the existing tempdir, cachedir, and localcachedir parameters remain separate.&lt;br /&gt;
&lt;br /&gt;
== Defining a new filesystem ==&lt;br /&gt;
&lt;br /&gt;
All file system implementations must extend the &amp;lt;code&amp;gt;file_system&amp;lt;/code&amp;gt; class, and define the required abstract functions.&lt;br /&gt;
&lt;br /&gt;
It is entirely up to the individual implementation how it handles storage, saving, and retrieval of files from it&#039;s file system, however certain key concepts apply.&lt;br /&gt;
&lt;br /&gt;
=== Concepts ===&lt;br /&gt;
&lt;br /&gt;
==== Moodle File API ====&lt;br /&gt;
&lt;br /&gt;
The Moodle File API is broken into different components, each having a related but fundamentally separate purpose.&lt;br /&gt;
&lt;br /&gt;
===== File Storage API =====&lt;br /&gt;
&lt;br /&gt;
The File Storage API is responsible for all interactions with the rest of Moodle.&lt;br /&gt;
&lt;br /&gt;
Files can be accessed using this API.&lt;br /&gt;
&lt;br /&gt;
===== Stored File =====&lt;br /&gt;
&lt;br /&gt;
Any file stored in Moodle&#039;s File Storage can be represented as a &amp;lt;code&amp;gt;stored_file&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;stored_file&amp;lt;/code&amp;gt; class holds various metadata about the files in the repository.&lt;br /&gt;
&lt;br /&gt;
==== Content Hash ====&lt;br /&gt;
&lt;br /&gt;
The Moodle File Storage API performs de-duplication by generating a &#039;&#039;&#039;checksum&#039;&#039;&#039; of the content of the file.&lt;br /&gt;
The checksum can be generated using the &#039;&#039;&#039;file_storage::hash_from_path&#039;&#039;&#039; and &#039;&#039;&#039;file_storage::hash_from_string&#039;&#039;&#039; functions.&lt;br /&gt;
&lt;br /&gt;
This checksum, referred to as a &#039;&#039;&#039;Content Hash&#039;&#039;&#039; (typically &amp;lt;code&amp;gt;$contenthash&amp;lt;/code&amp;gt;) is stored for each file in the Moodle File Storage Database tables.&lt;br /&gt;
&lt;br /&gt;
Files are always referred to using this content hash, and can be both stored and fetched using it.&lt;br /&gt;
&lt;br /&gt;
Helper functions allow conversion of &amp;lt;code&amp;gt;stored_file&amp;lt;/code&amp;gt; objects into a content hash.&lt;br /&gt;
&lt;br /&gt;
==== Distinction between local and remote file paths ====&lt;br /&gt;
&lt;br /&gt;
The file system API makes the distinction between a &#039;&#039;&#039;local&#039;&#039;&#039;, and a &#039;&#039;&#039;remote&#039;&#039;&#039; file path.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Local&#039;&#039; file paths &#039;&#039;&#039;&#039;&#039;must&#039;&#039;&#039;&#039;&#039; be capable of existing on disk.&lt;br /&gt;
&#039;&#039;Remote&#039;&#039; file paths may be either the valid local path, or a [http://au1.php.net/manual/en/wrappers.php:Supported Protocol].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note:&#039;&#039;&#039;&#039;&#039; Several functions offer a &amp;lt;code&amp;gt;$fetchifnotfound&amp;lt;/code&amp;gt;&lt;br /&gt;
option.&lt;br /&gt;
This is particularly useful when dealing with remote file systems which do not keep a local copy of the file on disk.&lt;br /&gt;
These functions should only be called with a truthful value &#039;&#039;&#039;if the file is required locally&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
It is also advisable when dealing with remote file systems to initially store the file on disk in this path as there is a strong possibility that subsequent actions will attempt to read the file further - for example, in order to generate an image thumbnail.&lt;br /&gt;
&lt;br /&gt;
===== Local file paths =====&lt;br /&gt;
&lt;br /&gt;
Local file paths &#039;&#039;&#039;&#039;&#039;must&#039;&#039;&#039;&#039;&#039; be formatted as a standard local file path. They &#039;&#039;&#039;&#039;&#039;must not&#039;&#039;&#039;&#039;&#039; be a streamable URL.&lt;br /&gt;
This is because some PHP functions are unable to work with seekable, or streamable resources and can &#039;&#039;&#039;only&#039;&#039;&#039; work with local files.&lt;br /&gt;
These include, but are not limited to:&lt;br /&gt;
* The &amp;lt;code&amp;gt;ZipArchive&amp;lt;/code&amp;gt; used in the &amp;lt;code&amp;gt;zip_packer&amp;lt;/code&amp;gt;; and&lt;br /&gt;
* &amp;lt;code&amp;gt;curl_file_create&amp;lt;/code&amp;gt;, used to add files to a curl request; and&lt;br /&gt;
* &amp;lt;code&amp;gt;finfo&amp;lt;/code&amp;gt;, used to determine mime information about files.&lt;br /&gt;
&lt;br /&gt;
Additionally there are some cases which may suffer performance issues when dealing with streamable files. This includes:&lt;br /&gt;
* &amp;lt;code&amp;gt;getimagesize()&amp;lt;/code&amp;gt; which must fetch the entire image first in order to determine size.&lt;br /&gt;
===== Remote file paths =====&lt;br /&gt;
&lt;br /&gt;
Remote file paths &#039;&#039;&#039;may&#039;&#039;&#039; be formatted as a standard local file path. They &#039;&#039;&#039;may&#039;&#039;&#039; be a streamable URL.&lt;br /&gt;
&lt;br /&gt;
See the PHP documentation on [http://au1.php.net/manual/en/wrappers.php:Supported Protocols] for more information on the accepted formats.&lt;br /&gt;
&lt;br /&gt;
Remote files may be passed into PHP functions such as:&lt;br /&gt;
* &amp;lt;code&amp;gt;file_get_contents&amp;lt;/code&amp;gt;; and&lt;br /&gt;
* &amp;lt;code&amp;gt;readfile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Explanation of required functions ===&lt;br /&gt;
&lt;br /&gt;
==== setup_instance ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;setup_instance()&amp;lt;/code&amp;gt; function is called during instantiation and allows you to setup any required configuration for your file system implementation.&lt;br /&gt;
&lt;br /&gt;
An example implementation might look like:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
protected function setup_instance() {&lt;br /&gt;
    // Setup the client.&lt;br /&gt;
    self::$client = new Awesome\Remote\File\Storage\System();&lt;br /&gt;
&lt;br /&gt;
    // Create a directory for use during the current request.&lt;br /&gt;
    // This directory will be automatically removed at the end of the request.&lt;br /&gt;
    self::$filedir = make_request_directory();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== get_local_filepath_from_hash ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;get_local_filepath_from_hash&amp;lt;/code&amp;gt; function is responsible for returning the correct path to the file in disk.&lt;br /&gt;
&lt;br /&gt;
This path must be consistent for each contenthash.&lt;br /&gt;
&lt;br /&gt;
The file &#039;&#039;&#039;does not&#039;&#039;&#039; need to exist on disk unless the &amp;lt;code&amp;gt;$fetchifnotfound&amp;lt;/code&amp;gt; parameter is truthy.&lt;br /&gt;
&lt;br /&gt;
An example implementation might look like:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
protected function get_local_path_from_hash($contenthash, $fetchifnotfound = false) {&lt;br /&gt;
    $path = self::$filedir . DIRECTORY_SEPARATOR . $contenthash;&lt;br /&gt;
&lt;br /&gt;
    if ($fetchifnotfound &amp;amp;&amp;amp; !is_readable($path)) {&lt;br /&gt;
        $this-&amp;gt;fetch_local_copy($contenthash, $path);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $path;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== get_remote_filepath_from_hash =====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;get_remote_filepath_from_hash($contenthash)&amp;lt;/code&amp;gt; function is responsible for returning the correct path to the file.&lt;br /&gt;
&lt;br /&gt;
The returned path must be in either:&lt;br /&gt;
* a local file format; or&lt;br /&gt;
* a remote file path as per the [http://au1.php.net/manual/en/wrappers.php:Supported Protocol] documentation.&lt;br /&gt;
&lt;br /&gt;
Remote paths should not be passed outside of the File System implementation.&lt;br /&gt;
&lt;br /&gt;
An example implementation might look like:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
protected function get_remote_path_from_hash($contenthash, $fetchifnotfound = false) {&lt;br /&gt;
    return $this-&amp;gt;get_presigned_url($contenthash, &#039;+6 hours&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note&#039;&#039;: If using a one-time/pre-signed URL, please ensure that the lifetime of the URL is sufficient for larger files.&lt;br /&gt;
&lt;br /&gt;
==== add_file_to_pool ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;add_file_to_pool&amp;lt;/code&amp;gt; function is responsible for storing the provided local file on disk into your file system.&lt;br /&gt;
&lt;br /&gt;
It is your responsibility to:&lt;br /&gt;
* generate the file&#039;s contenthash (if it is not provided);&lt;br /&gt;
* check whether an existing file with the same contenthash exists in the file system;&lt;br /&gt;
* ensure that the contenthash matches if there is a matching file; and&lt;br /&gt;
* copy the file to your file system; and&lt;br /&gt;
* ensure that file permissions are correct.&lt;br /&gt;
&lt;br /&gt;
==== add_string_to_pool ====&lt;br /&gt;
&lt;br /&gt;
Similar to &amp;lt;code&amp;gt;add_file_to_pool&amp;lt;/code&amp;gt;, the &amp;lt;code&amp;gt;add_string_to_pool&amp;lt;/code&amp;gt; function is responsible for storing the provided string content into your file system.&lt;br /&gt;
&lt;br /&gt;
==== copy_content_from_storedfile ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;copy_content_from_storedfile&amp;lt;/code&amp;gt; function is responsible for copying an existing file in the file system to a new local file.&lt;br /&gt;
&lt;br /&gt;
If you are using a local file system, you will likely just copy the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
public function copy_content_from_storedfile(stored_file $file, $target) {&lt;br /&gt;
    return copy($this-&amp;gt;get_local_filepath_from_storedfile($file), $target);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, if you are implementing a remote file system, you can likely make certain performance improvements by downloading the file straight to the intended target:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
public function copy_content_from_storedfile(stored_file $file, $target) {&lt;br /&gt;
    if ($this-&amp;gt;is_readable_locally_from_storedfile($file, false)) {&lt;br /&gt;
        return copy($this-&amp;gt;get_local_filepath_from_storedfile($file), $target);&lt;br /&gt;
    } else {&lt;br /&gt;
        return $this-&amp;gt;fetch_local_copy($file-&amp;gt;get_contenthash(), $target);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== remove_file ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;remove_file&amp;lt;/code&amp;gt; function is responsible for removing a file.&lt;br /&gt;
A file system &#039;&#039;&#039;may&#039;&#039;&#039; choose to implement it&#039;s own trash mechanism, as in the case of &amp;lt;code&amp;gt;filesystem_filedir&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
==== FTP file system ====&lt;br /&gt;
Tying together the information in the previous sections, we&#039;ll now set up an example FTP file system. Please note: Neither the sample &amp;lt;code&amp;gt;core_file_system_ftp&amp;lt;/code&amp;gt; class, nor the use as FTP as a file system is recommended for use in production installs. It simply serves as an example to aid developers in grasping the concept of a remote file system.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The first step is creating the &amp;lt;code&amp;gt;core_file_system_ftp&amp;lt;/code&amp;gt; class and setting it up as the alternative file system. To do this, create a file called &#039;&#039;&#039;file_system_ftp.php&#039;&#039;&#039; in your &#039;&#039;&#039;lib/classes&#039;&#039;&#039; directory and add the following content:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
/**&lt;br /&gt;
 * FTP File System.&lt;br /&gt;
 *&lt;br /&gt;
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * File system class used for low level access to real files via ftp (aieee! don&#039;t do it!).&lt;br /&gt;
 *&lt;br /&gt;
 * @package   core_files&lt;br /&gt;
 * @category  files&lt;br /&gt;
 * @copyright 2017 Damyon Wiese&lt;br /&gt;
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
class core_file_system_ftp extends file_system {&lt;br /&gt;
&lt;br /&gt;
    public function setup_instance() {&lt;br /&gt;
        global $CFG;&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;dirpermissions = $CFG-&amp;gt;directorypermissions;&lt;br /&gt;
        $this-&amp;gt;filepermissions = $CFG-&amp;gt;filepermissions;&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;cachedir = make_request_directory();&lt;br /&gt;
        $this-&amp;gt;check_root_files_exist();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function check_root_files_exist() {&lt;br /&gt;
        // Make sure the file pool directory exists.&lt;br /&gt;
        $filedir = $this-&amp;gt;get_root_path();&lt;br /&gt;
        if (!is_dir($filedir)) {&lt;br /&gt;
            if (!mkdir($filedir, $this-&amp;gt;dirpermissions, true)) {&lt;br /&gt;
                // Permission trouble.&lt;br /&gt;
                throw new file_exception(&#039;storedfilecannotcreatefiledirs&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Place warning file in file pool root.&lt;br /&gt;
            $warningfile = $this-&amp;gt;get_root_path(&#039;warning.txt&#039;);&lt;br /&gt;
            if (!file_exists($warningfile)) {&lt;br /&gt;
                file_put_contents($warningfile,&lt;br /&gt;
                        &#039;This directory contains the content of uploaded files and is controlled by Moodle code. &#039; .&lt;br /&gt;
                        &#039;Do not manually move, change or rename any of the files and subdirectories here.&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_remote_path_from_hash($contenthash) {&lt;br /&gt;
        return $this-&amp;gt;get_root_path($contenthash);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    protected function get_local_path_from_hash($contenthash, $fetchifnotfound = false) {&lt;br /&gt;
        $this-&amp;gt;setup_instance();&lt;br /&gt;
        $localurl = $this-&amp;gt;cachedir . DIRECTORY_SEPARATOR . $contenthash;&lt;br /&gt;
&lt;br /&gt;
        if ($fetchifnotfound &amp;amp;&amp;amp; !is_readable($localurl)) {&lt;br /&gt;
            $remoteurl = $this-&amp;gt;get_remote_path_from_hash($contenthash);&lt;br /&gt;
            copy($remoteurl, $localurl);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $localurl;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_root_url() {&lt;br /&gt;
        global $CFG;&lt;br /&gt;
&lt;br /&gt;
        static $path = null;&lt;br /&gt;
&lt;br /&gt;
        if ($path === null) {&lt;br /&gt;
            $path = &amp;quot;ftp://{$CFG-&amp;gt;file_system_ftp_user}:{$CFG-&amp;gt;file_system_ftp_pass}@&amp;quot; .&lt;br /&gt;
                    &amp;quot;{$CFG-&amp;gt;file_system_ftp_host}/{$CFG-&amp;gt;file_system_ftp_path}&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $path;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    public function get_root_path($file = null) {&lt;br /&gt;
        $path = $this-&amp;gt;get_root_url() . DIRECTORY_SEPARATOR . &#039;filedir&#039;;&lt;br /&gt;
        if ($file) {&lt;br /&gt;
           $path .= DIRECTORY_SEPARATOR . $file;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $path;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Copy content of file to given pathname.&lt;br /&gt;
     *&lt;br /&gt;
     * @param stored_file $file The file to be copied&lt;br /&gt;
     * @param string $target real path to the new file&lt;br /&gt;
     * @return bool success&lt;br /&gt;
     */&lt;br /&gt;
    public function copy_content_from_storedfile(stored_file $file, $target) {&lt;br /&gt;
        return copy($this-&amp;gt;get_remote_path_from_stored_file($file), $target);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Removes the file.&lt;br /&gt;
     *&lt;br /&gt;
     * @param string $contenthash&lt;br /&gt;
     */&lt;br /&gt;
    public function remove_file($contenthash) {&lt;br /&gt;
        if (!self::is_file_removable($contenthash)) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        $contentfile = $this-&amp;gt;get_remote_path_from_hash($contenthash, false);&lt;br /&gt;
&lt;br /&gt;
        unlink($contentfile);&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Add file content to sha1 pool.&lt;br /&gt;
     *&lt;br /&gt;
     * @param string $pathname Path to file currently on disk&lt;br /&gt;
     * @param string $contenthash SHA1 hash of content if known (performance only)&lt;br /&gt;
     * @return array (contenthash, filesize, newfile)&lt;br /&gt;
     */&lt;br /&gt;
    public function add_file_from_path($pathname, $contenthash = null) {&lt;br /&gt;
        if (!is_readable($pathname)) {&lt;br /&gt;
            throw new file_exception(&#039;storedfilecannotread&#039;, &#039;&#039;, $pathname);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $filesize = filesize($pathname);&lt;br /&gt;
        if ($filesize === false) {&lt;br /&gt;
            throw new file_exception(&#039;storedfilecannotread&#039;, &#039;&#039;, $pathname);&lt;br /&gt;
        }&lt;br /&gt;
        if (is_null($contenthash)) {&lt;br /&gt;
            $contenthash = sha1_file($pathname);&lt;br /&gt;
        }&lt;br /&gt;
        if (is_null($contenthash)) {&lt;br /&gt;
            throw new file_exception(&#039;storedfilecannotread&#039;, &#039;&#039;, $pathname);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Copy the file to it&#039;s local position. This helps perf in some&lt;br /&gt;
        // situations.&lt;br /&gt;
        $localpath = $this-&amp;gt;get_local_path_from_hash($contenthash, false);&lt;br /&gt;
        copy($pathname, $localpath);&lt;br /&gt;
&lt;br /&gt;
        $remotepath = $this-&amp;gt;get_remote_path_from_hash($contenthash, false);&lt;br /&gt;
&lt;br /&gt;
        $newfile = false;&lt;br /&gt;
        if (!file_exists($remotepath)) {&lt;br /&gt;
            $newfile = true;&lt;br /&gt;
            $result = copy($localpath, $remotepath);&lt;br /&gt;
&lt;br /&gt;
            if ($result === false) {&lt;br /&gt;
                throw new file_exception(&#039;storedfilecannotcreatefile&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return [$contenthash, $filesize, $newfile];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Add string content to sha1 pool.&lt;br /&gt;
     *&lt;br /&gt;
     * @param string $content file content - binary string&lt;br /&gt;
     * @return array (contenthash, filesize, newfile)&lt;br /&gt;
     */&lt;br /&gt;
    public function add_file_from_string($content) {&lt;br /&gt;
        $contenthash = sha1($content);&lt;br /&gt;
        $filesize = strlen($content);&lt;br /&gt;
&lt;br /&gt;
        if ($content === &#039;&#039;) {&lt;br /&gt;
            return [$contenthash, $filesize, false];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $newfile = false;&lt;br /&gt;
        $remotepath = $this-&amp;gt;get_remote_path_from_hash($contenthash, false);&lt;br /&gt;
        if (!file_exists($remotepath)) {&lt;br /&gt;
            $newfile = true;&lt;br /&gt;
            // Store the file remotely first - it&#039;s likely to be used again.&lt;br /&gt;
            $localpath = $this-&amp;gt;get_local_path_from_hash($contenthash, false);&lt;br /&gt;
            file_put_contents($localpath, $content);&lt;br /&gt;
&lt;br /&gt;
            copy($localpath, $remotepath);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [$contenthash, $filesize, $newfile];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that you can place this file anywhere that supports class autoloading in Moodle, you&#039;ll just need to adjust the $CFG var in the next step appropriately.&lt;br /&gt;
&lt;br /&gt;
Now, in your config.php, add:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;alternative_file_system_class = &#039;\\core_file_system_ftp&#039;;&lt;br /&gt;
$CFG-&amp;gt;file_system_ftp_user = &#039;test&#039;;&lt;br /&gt;
$CFG-&amp;gt;file_system_ftp_pass = &#039;test&#039;;&lt;br /&gt;
$CFG-&amp;gt;file_system_ftp_host = &#039;localhost&#039;;&lt;br /&gt;
$CFG-&amp;gt;file_system_ftp_path = &#039;testing&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, you&#039;ll need to set up an FTP server on localhost, setting up a user with the above credentials and making sure that the directory &#039;&#039;&#039;/testing/filedir/&#039;&#039;&#039; is writable by the &#039;&#039;&#039;test&#039;&#039;&#039; user. There are many virtualisation options allowing you to do this fairly quickly and easily. &lt;br /&gt;
&lt;br /&gt;
Once you&#039;re done, you should be able to upload files to your new remote file system.&lt;/div&gt;</summary>
		<author><name>Jaked</name></author>
	</entry>
</feed>