diff --git a/docs/devupdate.md b/docs/devupdate.md
index 06d7c35daa..a688decc46 100644
--- a/docs/devupdate.md
+++ b/docs/devupdate.md
@@ -1,45 +1,6 @@
---
-title: Moodle 5.1 developer update
+title: Moodle 5.2 developer update
tags:
- Core development
-- Moodle 5.1
+- Moodle 5.2
---
-
-
-
-This page highlights the important changes that are coming in Moodle 5.1 for developers.
-
-## Code Structure
-
-
-
-Most of the Moodle codebase, including all plugins, has been moved into a new `public` directory within the Moodle web root.
-
-Most Moodle tooling has already been updated to support this, but minor web server reconfiguration will be required to support this.
-
-See the [Restructure documentation](./guides/restructure/index.md) for further information on some of the changes required.
-
-## Course: activity chooser footer has been changed
-
-
-The activity chooser UI now features a dedicated footer button for adding the selected activity to the course. The logic for managing the activity chooser footer has moved to `course/amd/src/local/activitychooser/dialogue.js`, which now controls the visibility of the back and add buttons based on the modal's content. This update may impact plugins that implement custom activity chooser footers.
-
-**How to determine if your plugin is affected:**
-
-- Check if your plugin provides a `custom_chooser_footer` implementation. You can do this by searching your plugin's `lib.php` for a function named `PLUGINTYPE_PLUGINNAME_custom_chooser_footer`.
-- If your plugin implements this function, review your footer AMD module to see if it calls `modal.setFooter(...)`. To identify the AMD module, look at the first parameter passed when creating a new `core_course\local\entity\activity_chooser_footer` instance in your `custom_chooser_footer` function—this is the `$footerjspath`.
-
-**What you need to do:**
-
-- In most cases, simply remove the `modal.setFooter(...)` call from your AMD module, as the new activity chooser footer now manages this logic for you.
-- For more advanced customizations, ensure you use the `course/templates/local/activitychooser/footer.mustache` template to render your custom footer content.
-
-## Course format: max sections setting is now deprecated
-
-
-
-The `maxsections` setting in course formats is now deprecated. Previously, this setting was used to limit the number of sections in a course. Starting with Moodle 5.1, courses can have an unlimited number of sections.
-
-Although the `maxsections` setting remains available for now, it is marked as deprecated and will be removed in Moodle 6.0. Also, the `get_max_sections` from `core_courseformat\base` is also deprecated and will be removed in Moodle 6.0.
-
-If your format plugin relies on `maxsections`, you should add a custom setting in your plugin to control section limits. For reference, see the week format plugin, which now uses its own setting for this functionality.
diff --git a/docs/intro.md b/docs/intro.md
index 3096e9f4fd..7408fd3f68 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -1,13 +1,13 @@
---
id: introduction
title: Introduction
-description: Developer documentation for Moodle 5.1.
+description: Developer documentation for Moodle 5.2.
slug: /
tags:
- Getting started
---
-Welcome to the Developer Documentation for **Moodle 5.1**.
+Welcome to the Developer Documentation for **Moodle 5.2**.
This documentation is version-specific and includes a range of useful guides and information.
@@ -17,10 +17,10 @@ This documentation is version-specific and includes a range of useful guides and
- Look through our [guides to Moodle APIs](./apis.md)
- Browse our [Moodle feature](./guides.md) deep dives
- Interested in supporting the Moodle App in your plugins? Read the [Moodle App documentation](/general/app)
-{/*- You may want to read the [Release notes](/general/releases/5.1) for Moodle 5.1 */}
+{/*- You may want to read the [Release notes](/general/releases/5.2) for Moodle 5.2 */}
:::
import ReleaseStateSummary from '@site/src/components/ReleaseStateSummary';
-
+
diff --git a/general/releases/5.1.md b/general/releases/5.1.md
index 6ff047b00a..3740cbf0b2 100644
--- a/general/releases/5.1.md
+++ b/general/releases/5.1.md
@@ -12,7 +12,7 @@ import { ReleaseNoteIntro } from '@site/src/components/ReleaseInformation';
-If you are upgrading from a previous version, please see [Upgrading](https://docs.moodle.org/en/Upgrading) in the user docs.
+If you are upgrading from a previous version, please see [Upgrading](https://docs.moodle.org/501/en/Upgrading) in the user docs.
## Server requirements
diff --git a/nextVersion.js b/nextVersion.js
index 80666f9974..8f7c6814dd 100644
--- a/nextVersion.js
+++ b/nextVersion.js
@@ -15,7 +15,7 @@
* along with Moodle. If not, see .
*/
-const nextVersion = '5.1';
+const nextVersion = '5.2';
const nextLTSVersion = '5.3';
const nextVersionRoot = `/docs/${nextVersion}`;
diff --git a/static/_redirects b/static/_redirects
index 71765f8cc6..1ac519bf4b 100644
--- a/static/_redirects
+++ b/static/_redirects
@@ -77,4 +77,4 @@
# Issue #955
# Redirect /docs/* to /docs/[nextVersion]/:splat
-/docs/* /docs/5.1/:splat
+/docs/* /docs/5.2/:splat
diff --git a/versioned_docs/version-5.1/_devupdate/activity-icons.png b/versioned_docs/version-5.1/_devupdate/activity-icons.png
new file mode 100644
index 0000000000..389dd3b7d5
Binary files /dev/null and b/versioned_docs/version-5.1/_devupdate/activity-icons.png differ
diff --git a/versioned_docs/version-5.1/_utils.tsx b/versioned_docs/version-5.1/_utils.tsx
new file mode 100644
index 0000000000..437aa81219
--- /dev/null
+++ b/versioned_docs/version-5.1/_utils.tsx
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React, { type ReactNode } from 'react';
+import ComponentFileSummaryGeneric, {
+ type ComponentFileSummaryProps,
+} from '@site/src/components/ComponentFileSummary';
+import { MDXProvider } from '@mdx-js/react';
+
+import { getExample } from '@site/src/moodleBridge';
+
+export {
+ getExample,
+ ComponentFileSummaryProps,
+};
+
+/**
+ * Fill the default properties.
+ * @param {Props} props
+ * @return {Props}
+ */
+export const fillDefaultProps = (props: ComponentFileSummaryProps): ComponentFileSummaryProps => ({
+ filetype: 'php',
+ examplePurpose: props.summary,
+ ...props,
+});
+
+const normaliseDescription = (Value: ReactNode | string): null | JSX.Element => {
+ if (typeof Value === 'boolean' || !Value) {
+ return null;
+ }
+
+ if (typeof Value === 'string' || React.isValidElement(Value)) {
+ return (
+
+ {Value}
+
+ );
+ }
+
+ return (
+
+
+
+ );
+};
+
+/**
+ * Get the preferred description given a standard properties value which contains an optional description,
+ * and/or extraDescription, and a DefaultDescription object.
+ *
+ * @param {Props} props
+ * @param {DefaultDescription} DefaultDescription The default description to use if the `description` property is empty
+ * @returns {MDXLayout}
+ */
+export const getDescription = ({
+ description = null,
+ extraDescription = null,
+ children = null,
+}: ComponentFileSummaryProps, defaultDescription?: ReactNode | string): null | ReactNode | JSX.Element => {
+ if (children) {
+ const Description = normaliseDescription(children);
+ return (
+
+ {Description}
+
+ );
+ }
+
+ if (description) {
+ const Description = normaliseDescription(description);
+ return (
+
+ {Description}
+
+ );
+ }
+
+ const Description = normaliseDescription(defaultDescription);
+ const ExtraDescription = normaliseDescription(extraDescription);
+
+ if (Description) {
+ return (
+
+ {Description}
+ {ExtraDescription}
+
+ );
+ }
+
+ return null;
+};
+
+export const ComponentFileSummary = (initialProps: ComponentFileSummaryProps): JSX.Element => {
+ const props = fillDefaultProps({
+ examplePurpose: initialProps?.summary ?? null,
+ ...initialProps,
+ });
+
+ props.description = getDescription(props, props?.defaultDescription ?? null);
+
+ if (props?.example || props?.defaultExample) {
+ props.example = getExample(props, props?.defaultExample ?? null);
+ }
+
+ return ComponentFileSummaryGeneric(props);
+};
diff --git a/versioned_docs/version-5.1/apis.md b/versioned_docs/version-5.1/apis.md
new file mode 100644
index 0000000000..fdf7d23b79
--- /dev/null
+++ b/versioned_docs/version-5.1/apis.md
@@ -0,0 +1,264 @@
+---
+title: API Guides
+---
+
+Moodle has a number of core APIs that provide tools for Moodle scripts.
+
+They are essential when writing [Moodle plugins](https://docs.moodle.org/dev/Plugins).
+
+## Most-used General API
+
+These APIs are critical and will be used by nearly every Moodle plugin.
+
+### Access API (access)
+
+The [Access API](./apis/subsystems/access.md) 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.
+
+### Data manipulation API (dml)
+
+The [Data manipulation API](./apis/core/dml/index.md) allows you to read/write to databases in a consistent and safe way.
+
+### File API (files)
+
+The [File API](./apis/subsystems/files/index.md) controls the storage of files in connection to various plugins.
+
+### Form API (form)
+
+The [Form API](./apis/subsystems/form/index.md) defines and handles user data via web forms.
+
+### Logging API (log)
+
+The [Events API](https://docs.moodle.org/dev/Events_API) allows you to log events in Moodle, while [Logging 2](https://docs.moodle.org/dev/Logging_2) describes how logs are stored and retrieved.
+
+### Navigation API (navigation)
+
+The [Navigation API](./apis/core/navigation/index.md) allows you to manipulate the navigation tree to add and remove items as you wish.
+
+### Page API (page)
+
+The [Page API](https://docs.moodle.org/dev/Page_API) is used to set up the current page, add JavaScript, and configure how things will be displayed to the user.
+
+### Output API (output)
+
+The [Output API](./apis/subsystems/output/index.md) is used to render the HTML for all parts of the page.
+
+### String API (string)
+
+The [String API](https://docs.moodle.org/dev/String_API) is how you get language text strings to use in the user interface. It handles any language translations that might be available.
+
+### Upgrade API (upgrade)
+
+The [Upgrade API](./guides/upgrade/index.md) is how your module installs and upgrades itself, by keeping track of its own version.
+
+### Moodlelib API (core)
+
+The [Moodlelib API](https://docs.moodle.org/dev/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.
+
+## Other General API
+
+### Admin settings API (admin)
+
+The [Admin settings](./apis/subsystems/admin/index.md) API deals with providing configuration options for each plugin and Moodle core.
+
+### Admin presets API (adminpresets)
+
+The [Admin presets API](https://docs.moodle.org/dev/AdminPresetsAPI) allows plugins to make some decisions/implementations related to the Site admin presets.
+
+### Analytics API (analytics)
+
+The [Analytics API](./apis/subsystems/analytics/index.md) allow you to create prediction models and generate insights.
+
+### Availability API (availability)
+
+The [Availability API](./apis/subsystems/availability/index.md) controls access to activities and sections.
+
+### Backup API (backup)
+
+The [Backup API](./apis/subsystems/backup/index.md) defines exactly how to convert course data into XML for backup purposes, and the [Restore API](./apis/subsystems/backup/restore.md) describes how to convert it back the other way.
+
+### Cache API (cache)
+
+The [The Moodle Universal Cache (MUC)](https://docs.moodle.org/dev/The_Moodle_Universal_Cache_(MUC)) is the structure for storing cache data within Moodle. [Cache API](./apis/subsystems/muc/index.md) explains some of what is needed to use a cache in your code.
+
+### Calendar API (calendar)
+
+The [Calendar API](./apis/core/calendar/index.md) allows you to add and modify events in the calendar for user, groups, courses, or the whole site.
+
+### Check API (check)
+
+The [Check API](./apis/subsystems/check/index.md) allows you to add security, performance or health checks to your site.
+
+### Comment API (comment)
+
+The [Comment API](https://docs.moodle.org/dev/Comment_API) allows you to save and retrieve user comments, so that you can easily add commenting to any of your code.
+
+### Communication API (communication)
+
+The [Communication API](./apis/subsystems/communication/index.md) provides access to the messaging system and other communication providers (such as Matrix).
+
+### Competency API (competency)
+
+The [Competency API](https://docs.moodle.org/dev/Competency_API) allows you to list and add evidence of competencies to learning plans, learning plan templates, frameworks, courses and activities.
+
+### Data definition API (ddl)
+
+The [Data definition API](./apis/core/dml/ddl.md) is what you use to create, change and delete tables and fields in the database during upgrades.
+
+### Editor API
+
+The [Editor API](./apis/subsystems/editor/index.md) is used to control HTML text editors.
+
+### Enrolment API (enrol)
+
+The [Enrolment API](./apis/subsystems/enrol.md) deals with course participants.
+
+### Events API (event)
+
+The [Events API](https://docs.moodle.org/dev/Events_API) allows to define "events" 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.
+
+### Hooks API
+
+The [Hooks API](./apis/core/hooks/index.md) allows core and plugins to communicate indirectly with other plugins.
+
+### Experience API (xAPI)
+
+The Experience API (xAPI) is an e-learning standard that allows learning content and learning systems to speak to each other. The [Experience API (xAPI)](https://docs.moodle.org/dev/Experience_API_(xAPI))
+allows any plugin to generate and handle xAPI standard statements.
+
+### External functions API (external)
+
+The [External functions API](./apis/subsystems/external/functions.md) allows you to create fully parametrised methods that can be accessed by external programs (such as [Web services](./apis/subsystems/external/index.md)).
+
+### Favourites API
+
+The [Favourites API](./apis/subsystems/favourites/index.md) allows you to mark items as favourites for a user and manage these favourites. This is often referred to as 'Starred'.
+
+### H5P API (h5p)
+
+The [H5P API](https://docs.moodle.org/dev/H5P_API) allows plugins to make some decisions/implementations related to the [H5P integration](https://docs.moodle.org/dev/H5P).
+
+### Lock API (lock)
+
+The [Lock API](./apis/core/lock/index.md) lets you synchronise processing between multiple requests, even for separate nodes in a cluster.
+
+### Message API (message)
+
+The [Message API](./apis/core/message/index.md) lets you post messages to users. They decide how they want to receive them.
+
+### Media API (media)
+
+The [Media](https://docs.moodle.org/dev/Media_players#Using_media_players) API can be used to embed media items such as audio, video, and Flash.
+
+### My profile API
+
+The [My profile API](https://docs.moodle.org/dev/My_profile_API) is used to add things to the profile page.
+
+### OAuth 2 API (oauth2)
+
+The [OAuth 2 API](https://docs.moodle.org/dev/OAuth_2_API) is used to provide a common place to configure and manage external systems using OAuth 2.
+
+### Payment API (payment)
+
+The [Payment API](https://docs.moodle.org/dev/Payment_API) deals with payments.
+
+### Preference API (preference)
+
+The [Preference API](./apis/core/preference/index.md) is a simple way to store and retrieve preferences for individual users.
+
+### Portfolio API (portfolio)
+
+The [Portfolio API](https://docs.moodle.org/dev/Portfolio_API) allows you to add portfolio interfaces on your pages and allows users to package up data to send to their portfolios.
+
+### Privacy API (privacy)
+
+The [Privacy API](./apis/subsystems/privacy/index.md) 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.
+This allows compliance with regulation such as the General Data Protection Regulation (GDPR) in Europe.
+
+### Rating API (rating)
+
+The [Rating API](https://docs.moodle.org/dev/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.
+
+
+### Report builder API (reportbuilder)
+
+The [Report builder API](./apis/core/reportbuilder/index.md) allows you to create reports in your plugin, as well as providing custom reporting data which users can use to build their own reports.
+
+### RSS API (rss)
+
+The [RSS API](https://docs.moodle.org/dev/RSS_API) allows you to create secure RSS feeds of data in your module.
+
+### Search API (search)
+
+The [Search API](https://docs.moodle.org/dev/Search_API) allows you to index contents in a search engine and query the search engine for results.
+
+### Tag API (tag)
+
+The [Tag API](./apis/subsystems/tag/index.md) allows you to store tags (and a tag cloud) to items in your module.
+
+### Task API (task)
+
+The [Task API](./apis/subsystems/task/index.md) lets you run jobs in the background. Either once off, or on a regular schedule.
+
+### Time API (time)
+
+The [Time API](./apis/subsystems/time/index.md) takes care of translating and displaying times between users in the site.
+
+### Testing API (test)
+
+The testing API contains the Unit test API ([PHPUnit](/general/development/tools/phpunit)) and Acceptance test API ([Acceptance testing](/general/development/tools/behat)). Ideally all new code should have unit tests written FIRST.
+
+### User-related APIs (user)
+
+This is a rather informal grouping of miscellaneous [User-related APIs](./apis/core/user/index.md) relating to sorting and searching lists of users.
+
+### Web services API (webservice)
+
+The [Web services API](./apis/subsystems/external/writing-a-service.md) allows you to expose particular functions (usually external functions) as web services.
+
+### Badges API (badges)
+
+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).
+
+### Custom fields API (customfield)
+
+The [Custom fields API](./apis/core/customfields/index.md) allows you to configure and add custom fields for different entities
+
+## Activity module APIs
+
+Activity modules are the most important plugin in Moodle. There are several core APIs that service only Activity modules.
+
+### Activity completion API (completion)
+
+The [Activity completion API](./apis/core/activitycompletion/index.md) is to indicate to the system how activities are completed.
+
+### Advanced grading API (grading)
+
+The [Advanced grading API](./apis/core/grading/index.md) allows you to add more advanced grading interfaces (such as rubrics) that can produce simple grades for the gradebook.
+
+### Conditional activities API (condition) - deprecated in 2.7
+
+The deprecated [Conditional activities API](./apis/core/conditionalactivities/index.md) used to provide conditional access to modules and sections in Moodle 2.6 and below. It has been replaced by the [Availability API](./apis/subsystems/availability/index.md).
+
+### Groups API (group)
+
+The [Groups API](./apis/subsystems/group/index.md) allows you to check the current activity group mode and set the current group.
+
+### Gradebook API (grade)
+
+The [Gradebook API](https://docs.moodle.org/dev/Gradebook_API) allows you to read and write from the gradebook. It also allows you to provide an interface for detailed grading information.
+
+### Plagiarism API (plagiarism)
+
+The [Plagiarism API](./apis/subsystems/plagiarism.md) allows your activity module to send files and data to external services to have them checked for plagiarism.
+
+### Question API (question)
+
+The [Question API](https://docs.moodle.org/dev/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.
+
+## See also
+
+
+- [Plugins](https://docs.moodle.org/dev/Plugins) - plugin types also have their own APIs
+- [Callbacks](https://docs.moodle.org/dev/Callbacks) - list of all callbacks in Moodle
+- [Coding style](/general/development/policies/codingstyle) - general information about writing PHP code for Moodle
+- [Session locks](https://docs.moodle.org/dev/Session_locks)
diff --git a/versioned_docs/version-5.1/apis/_files/amd-dir.mdx b/versioned_docs/version-5.1/apis/_files/amd-dir.mdx
new file mode 100644
index 0000000000..b22f14b4cd
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/amd-dir.mdx
@@ -0,0 +1,10 @@
+
+JavaScript in Moodle is written in the ESM format, and transpiled into AMD modules for deployment.
+
+The [Moodle JavaScript Guide](../guides/javascript) has detailed information and examples on writing JavaScript in Moodle. Further information is also available in the [JavaScript Modules](../../guides/javascript/modules.md) documentation.
+
+:::caution
+
+Although the AMD module format is supported, all new JavaScript is written in the EcmaScript Module (ESM) format.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/amd-dir.tsx b/versioned_docs/version-5.1/apis/_files/amd-dir.tsx
new file mode 100644
index 0000000000..3587716005
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/amd-dir.tsx
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './amd-dir.mdx';
+
+const defaultExample = `
+import {fetchThings} from './repository';
+
+export const updateThings = (thingData) => {
+ return fetchThings(thingData);
+};
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/backup-dir.mdx b/versioned_docs/version-5.1/apis/_files/backup-dir.mdx
new file mode 100644
index 0000000000..7b77168eb2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/backup-dir.mdx
@@ -0,0 +1,7 @@
+
+If your plugin stores data then you may need to implement the Backup feature which allows the activity to backed up, restored, and duplicated.
+
+For more information on Backup and restore, see the following:
+
+- [Backup 2.0 for developers](https://docs.moodle.org/dev/Backup_2.0_for_developers)
+- [Restore 2.0 for developers](https://docs.moodle.org/dev/Restore_2.0_for_developers)
diff --git a/versioned_docs/version-5.1/apis/_files/backup-dir.tsx b/versioned_docs/version-5.1/apis/_files/backup-dir.tsx
new file mode 100644
index 0000000000..00e6225317
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/backup-dir.tsx
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './backup-dir.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/changes.mdx b/versioned_docs/version-5.1/apis/_files/changes.mdx
new file mode 100644
index 0000000000..afcb40332e
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/changes.mdx
@@ -0,0 +1,7 @@
+
+If your plugin includes a changelog in its root directory, this will be used to automatically pre-fill the release notes field when uploading new versions of your plugin to the [Plugins directory](/general/community/plugincontribution/pluginsdirectory). This file can be in any of the following locations:
+
+- `CHANGES.md`: as a markdown file; or
+- `CHANGES.txt`: as a text file; or
+- `CHANGES.html`: as an HTML file; or
+- `CHANGES`: as a text file.
diff --git a/versioned_docs/version-5.1/apis/_files/changes.tsx b/versioned_docs/version-5.1/apis/_files/changes.tsx
new file mode 100644
index 0000000000..7ba853911c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/changes.tsx
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './changes.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/classes-dir.mdx b/versioned_docs/version-5.1/apis/_files/classes-dir.mdx
new file mode 100644
index 0000000000..d5a522cef6
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/classes-dir.mdx
@@ -0,0 +1,9 @@
+
+Moodle supports, and recommends, the use of autoloaded PHP classes.
+
+By placing files within the `classes` directory or appropriate sub-directories, and with the correct PHP Namespace, and class name, Moodle is able to autoload classes without the need to manually require, or include them.
+
+Details on these rules and conventions are available in the following documentation:
+
+- [Coding style - namespace conventions](/general/development/policies/codingstyle#namespaces)
+- [Automatic class loading](https://docs.moodle.org/dev/Automatic_class_loading)
diff --git a/versioned_docs/version-5.1/apis/_files/classes-dir.tsx b/versioned_docs/version-5.1/apis/_files/classes-dir.tsx
new file mode 100644
index 0000000000..ef801dddbd
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/classes-dir.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './classes-dir.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/cli-dir.mdx b/versioned_docs/version-5.1/apis/_files/cli-dir.mdx
new file mode 100644
index 0000000000..8757cadb97
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/cli-dir.mdx
@@ -0,0 +1,8 @@
+
+For plugins which make use of [CLI scripts](https://docs.moodle.org/dev/CLI_scripts), the convention is that these are placed into the `cli` folder to make their purpose clear, and easy to find.
+
+:::caution
+
+All CLI scripts **must** declare themselves as being a CLI script by defining the `CLI_SCRIPT` constant to true before including `config.php`.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/cli-dir.tsx b/versioned_docs/version-5.1/apis/_files/cli-dir.tsx
new file mode 100644
index 0000000000..94f11fcb00
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/cli-dir.tsx
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './cli-dir.mdx';
+
+const defaultExample = `define('CLI_SCRIPT', true);
+
+require_once(__DIR__ . '/../../config.php');
+require_once("{$CFG->libdir}/clilib.php");
+
+// Your CLI features go here.
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-access-php.mdx b/versioned_docs/version-5.1/apis/_files/db-access-php.mdx
new file mode 100644
index 0000000000..f4bd89b3a4
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-access-php.mdx
@@ -0,0 +1,13 @@
+
+
+The `db/access.php` file contains the __initial__ configuration for a plugin's access control rules.
+
+Access control is handled in Moodle by the use of Roles, and Capabilities. You can read more about these in the [Access API](../subsystems/access.md) documentation.
+
+:::caution Changing initial configuration
+
+If you make changes to the initial configuration of _existing_ access control rules, these will only take effect for _new installations of your plugin_. Any existing installation **will not** be updated with the latest configuration.
+
+Updating existing capability configuration for an installed site is not recommended as it may have already been modified by an administrator.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/db-access-php.tsx b/versioned_docs/version-5.1/apis/_files/db-access-php.tsx
new file mode 100644
index 0000000000..877cd86aae
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-access-php.tsx
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-access-php.mdx';
+
+const defaultExample = `$capabilities = [
+ // Ability to use the plugin.
+ 'plugintype/pluginname:useplugininstance' => [
+ 'riskbitmask' => RISK_XSS,
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+];
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-events-php.mdx b/versioned_docs/version-5.1/apis/_files/db-events-php.mdx
new file mode 100644
index 0000000000..11667d3c5e
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-events-php.mdx
@@ -0,0 +1,14 @@
+
+Moodle supports a feature known as _ [Event observers](https://docs.moodle.org/dev/Events_API#Event_observers) _ to allow components to make changes when certain events take place.
+
+The `db/events.php` file allows you define any event subscriptions that your plugin needs to listen for.
+
+Event subscriptions are a convenient way to observe events generated elsewhere in Moodle.
+
+:::caution Communication between components
+
+You _should not_ use event subscriptions to subscribe to events belonging to other plugins, without defining a dependency upon that plugin.
+
+See the [Component communication principles](/general/development/policies/component-communication#event-observers) documentation for a description of some of the risks of doing so.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/db-events-php.tsx b/versioned_docs/version-5.1/apis/_files/db-events-php.tsx
new file mode 100644
index 0000000000..71e928813f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-events-php.tsx
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-events-php.mdx';
+
+const defaultExample = `$observers = [
+ [
+ 'eventname' => '\\core\\event\\course_module_created',
+ 'callback' => '\\plugintype_pluginname\\event\\observer\\course_module_created::store',
+ 'priority' => 1000,
+ ],
+];
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-install-php.mdx b/versioned_docs/version-5.1/apis/_files/db-install-php.mdx
new file mode 100644
index 0000000000..e91709f741
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-install-php.mdx
@@ -0,0 +1,10 @@
+
+The `db/install.php` file allows you define a post-installation hook, which is called immediately after the initial creation of your database schema.
+
+:::caution
+
+This file is not used at all after the _initial_ installation of your plugin.
+
+It is _not called_ during any upgrade.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/db-install-php.tsx b/versioned_docs/version-5.1/apis/_files/db-install-php.tsx
new file mode 100644
index 0000000000..9a18223bf7
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-install-php.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-install-php.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-install-xml.mdx b/versioned_docs/version-5.1/apis/_files/db-install-xml.mdx
new file mode 100644
index 0000000000..4af56d4989
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-install-xml.mdx
@@ -0,0 +1,8 @@
+
+The `install.xml` file is used to define any database tables, fields, indexes, and keys, which should be created for a plugin during its initial installation.
+
+:::caution
+
+When creating or updating the `install.xml` you **must** use the built-in [XMLDB editor](https://docs.moodle.org/dev/XMLDB_Documentation) within Moodle.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/db-legacyclasses-php.mdx b/versioned_docs/version-5.1/apis/_files/db-legacyclasses-php.mdx
new file mode 100644
index 0000000000..12eb9a5d34
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-legacyclasses-php.mdx
@@ -0,0 +1,8 @@
+
+Details of legacy classes that have been moved to the classes directory to support autoloading but are not yet named properly.
+
+:::note
+
+Adding classes to `db/legacyclasses.php` is only necessary when the class is part of a _public_ API, or the class name cannot be changed.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/db-legacyclasses-php.tsx b/versioned_docs/version-5.1/apis/_files/db-legacyclasses-php.tsx
new file mode 100644
index 0000000000..e83e62e417
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-legacyclasses-php.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-legacyclasses-php.mdx';
+
+const defaultExample = `
+defined('MOODLE_INTERNAL') || die;
+
+$legacyclasses = [
+ 'old_class_name' => 'path/within/classes/directory.php',
+
+ // Examples:
+ \\coding_exception::class => 'exception/coding_exception.php',
+ \\moodle_exception::class => 'exception/moodle_exception.php',
+
+ // Example loading a class from a different subsystem.
+ // This should typically only be used in core.
+ \\cache::class => [
+ 'core_cache', // The name of the subsystem to load from.
+ 'cache.php', // The file name within that filesystem's classes directory.
+ ],
+];
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-messages-php.mdx b/versioned_docs/version-5.1/apis/_files/db-messages-php.mdx
new file mode 100644
index 0000000000..2b52b7cf0a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-messages-php.mdx
@@ -0,0 +1,4 @@
+
+The `db/messages.php` file allows you to declare the messages that your plugin sends.
+
+See the [Message API](../core/message/index.md) documentation for further information.
diff --git a/versioned_docs/version-5.1/apis/_files/db-messages-php.tsx b/versioned_docs/version-5.1/apis/_files/db-messages-php.tsx
new file mode 100644
index 0000000000..f8c576ec84
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-messages-php.tsx
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-messages-php.mdx';
+
+const defaultExample = `
+$messageproviders = [
+ 'things' => [
+ 'defaults' => [
+ 'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED,
+ ],
+ ],
+];
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-mobile-php.mdx b/versioned_docs/version-5.1/apis/_files/db-mobile-php.mdx
new file mode 100644
index 0000000000..b16a65f3c1
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-mobile-php.mdx
@@ -0,0 +1,6 @@
+
+The Moodle Mobile remote add-on is the mobile app version of the plugin that will be loaded when a user accesses the plugin on the app.
+
+A plugin can include several Mobile add-ons. Each add-on must indicate a unique name.
+
+See the [Moodle App Plugins development guide](/general/app/development/plugins-development-guide) for more information on configuring your plugin for the Moodle App.
diff --git a/versioned_docs/version-5.1/apis/_files/db-mobile-php.tsx b/versioned_docs/version-5.1/apis/_files/db-mobile-php.tsx
new file mode 100644
index 0000000000..322b29f8f9
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-mobile-php.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-mobile-php.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-renamedclasses-php.mdx b/versioned_docs/version-5.1/apis/_files/db-renamedclasses-php.mdx
new file mode 100644
index 0000000000..91e69e0053
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-renamedclasses-php.mdx
@@ -0,0 +1,8 @@
+
+Details of classes that have been renamed to fit in with autoloading. See [forum discussion](https://moodle.org/mod/forum/discuss.php?d=262403) for details.
+
+:::note
+Adding renamed or moved classes to `renamedclasses.php` is only necessary when the class is part of the component's API where it can be reused by other components, especially by third-party plugins. This is to maintain backwards-compatibility in addition to autoloading purposes.
+
+If the renamed or moved class is private/internal to the component and is not subject for external use, there is no need to add it to `renamedclasses.php`.
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/db-renamedclasses-php.tsx b/versioned_docs/version-5.1/apis/_files/db-renamedclasses-php.tsx
new file mode 100644
index 0000000000..ef6d5b81d6
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-renamedclasses-php.tsx
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-renamedclasses-php.mdx';
+
+const defaultExample = `
+defined('MOODLE_INTERNAL') || die;
+
+$renamedclasses = [
+ 'old_class_name' => 'fully_qualified\\\\new\\\\name',
+
+ // Examples:
+ 'assign_header' => 'mod_assign\\\\output\\\\header',
+ '\\assign_header' => 'mod_assign\\\\output\\\\header',
+ '\\assign' => 'mod_assign\\\\assignment',
+
+ // Incorrect:
+ // The new class name should _not_ have a leading \\.
+ 'assign_header' => '\\\\mod_assign\\\\output\\\\header',
+];
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-services-php.mdx b/versioned_docs/version-5.1/apis/_files/db-services-php.mdx
new file mode 100644
index 0000000000..8e34f888db
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-services-php.mdx
@@ -0,0 +1,16 @@
+
+The `db/services.php` file is used to describe the external functions available for use in web services. This includes
+
+web service functions defined for JavaScript, and for the [Moodle Mobile App](/general/app).
+
+:::note
+
+Web services should be named following the [naming convention for web services](https://docs.moodle.org/dev/Web_service_API_functions#Naming_convention).
+
+:::
+
+For further information on external functions and web services, see:
+
+- [Adding a web service to a plugin](../subsystems/external/writing-a-service.md)
+- [Web services API](../subsystems/external/writing-a-service.md)
+- [External functions API](../subsystems/external/functions.md)
diff --git a/versioned_docs/version-5.1/apis/_files/db-services-php.tsx b/versioned_docs/version-5.1/apis/_files/db-services-php.tsx
new file mode 100644
index 0000000000..04a8536db3
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-services-php.tsx
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-services-php.mdx';
+
+const defaultExample = `
+$functions = [
+ 'plugintype_pluginname_create_things' => [
+ 'classname' => 'plugintype_pluginname\\external\\create_things',
+ 'description' => 'Create a new thing',
+ 'type' => 'write',
+ 'ajax' => true,
+ 'services' => [
+ MOODLE_OFFICIAL_MOBILE_SERVICE,
+ ],
+ ],
+];
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-tasks-example.php b/versioned_docs/version-5.1/apis/_files/db-tasks-example.php
new file mode 100644
index 0000000000..1c8ac31514
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-tasks-example.php
@@ -0,0 +1,11 @@
+$tasks = [
+ [
+ 'classname' => 'mod_example\task\do_something',
+ 'blocking' => 0,
+ 'minute' => '30',
+ 'hour' => '17',
+ 'day' => '*',
+ 'month' => '1,7',
+ 'dayofweek' => '0',
+ ],
+];
diff --git a/versioned_docs/version-5.1/apis/_files/db-tasks-php.mdx b/versioned_docs/version-5.1/apis/_files/db-tasks-php.mdx
new file mode 100644
index 0000000000..ea33364e06
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-tasks-php.mdx
@@ -0,0 +1,18 @@
+
+
+The `db/tasks.php` file contains the initial schedule configuration for each of your plugins _scheduled_ tasks. Adhoc tasks are not run on a regular schedule and therefore are not described in this file.
+
+:::caution Editing the schedule for an existing task
+
+If an existing task is edited, it will only be updated in the database if the administrator has not customised the schedule of that task in any way.
+
+:::
+
+The following fields also accept a value of `R`, which indicates that Moodle should choose a random value for that field:
+
+- minute
+- hour
+- dayofweek
+- day
+
+See [db/tasks.php](../commonfiles/db-tasks.php/index.md) for full details of the file format.
diff --git a/versioned_docs/version-5.1/apis/_files/db-tasks-php.tsx b/versioned_docs/version-5.1/apis/_files/db-tasks-php.tsx
new file mode 100644
index 0000000000..68c9ac809b
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-tasks-php.tsx
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary, type ComponentFileSummaryProps } from '../../_utils';
+import DefaultDescription from './db-tasks-php.mdx';
+// eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved
+import DefaultExample from '!!raw-loader!./db-tasks-example.php';
+
+export default (initialProps: ComponentFileSummaryProps): JSX.Element => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-uninstall-php.mdx b/versioned_docs/version-5.1/apis/_files/db-uninstall-php.mdx
new file mode 100644
index 0000000000..5ed3089e4b
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-uninstall-php.mdx
@@ -0,0 +1,2 @@
+
+The `db/uninstall.php` file allows you define a pre-uninstallation hook, which is called immediately before all table and data from your plugin are removed.
diff --git a/versioned_docs/version-5.1/apis/_files/db-uninstall-php.tsx b/versioned_docs/version-5.1/apis/_files/db-uninstall-php.tsx
new file mode 100644
index 0000000000..8d4f8c2081
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-uninstall-php.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-uninstall-php.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/db-upgrade-php.mdx b/versioned_docs/version-5.1/apis/_files/db-upgrade-php.mdx
new file mode 100644
index 0000000000..3dde622bc7
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-upgrade-php.mdx
@@ -0,0 +1,30 @@
+
+The `db/upgrade.php` file contains upgrade steps, including database schema changes, changes to settings, and other steps which must be performed during upgrade.
+
+See the [Upgrade API](../../guides/upgrade/index.md) documentation for further information.
+
+:::danger Generating Database Schema changes
+
+When making changes to the database schema you **must** use the build-in [XMLDB editor](https://docs.moodle.org/dev/XMLDB_Documentation) within
+Moodle. This can be used to generate php upgrade steps.
+
+The [install.xml](../commonfiles/index.mdx#dbinstallxml) schema must match the schema generated by the upgrade at all times.
+
+:::
+
+To create an upgrade step you must:
+
+1. Use the [XMLDB editor](/general/development/tools/xmldb) to create the definition of the new fields
+1. Update the `install.xml` from the XMLDB editor
+1. Generate the PHP upgrade steps from within the XMLDB Editor
+1. Update the version number in your `version.php`
+
+:::tip
+
+In many cases you will be able to combine multiple upgrade steps into a single version change.
+
+:::
+
+When a version number increment is detected during an upgrade, the `xmldb_[pluginname]_upgrade` function is called with the old version number as the first argument.
+
+See the [Upgrade API](../../guides/upgrade/index.md) documentation for more information on the upgrade process.
diff --git a/versioned_docs/version-5.1/apis/_files/db-upgrade-php.tsx b/versioned_docs/version-5.1/apis/_files/db-upgrade-php.tsx
new file mode 100644
index 0000000000..8e96d96b65
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/db-upgrade-php.tsx
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-upgrade-php.mdx';
+
+const defaultExample = `
+function xmldb_certificate_upgrade($oldversion = 0) {
+ if ($oldversion < 2012091800) {
+ // Add new fields to certificate table.
+ $table = new xmldb_table('certificate');
+ $field = new xmldb_field('showcode');
+ $field->set_attributes(XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'savecert');
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+ // Add new fields to certificate_issues table.
+ $table = new xmldb_table('certificate_issues');
+ $field = new xmldb_field('code');
+ $field->set_attributes(XMLDB_TYPE_CHAR, '50', null, null, null, null, 'certificateid');
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Certificate savepoint reached.
+ upgrade_mod_savepoint(true, 2012091800, 'certificate');
+ }
+
+ // Everything has succeeded to here. Return true.
+ return true;
+}`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/environment-xml.mdx b/versioned_docs/version-5.1/apis/_files/environment-xml.mdx
new file mode 100644
index 0000000000..62af6b1660
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/environment-xml.mdx
@@ -0,0 +1,4 @@
+
+A plugin can declare its own environment requirements, in addition to those declared by Moodle core. These may includes features such as PHP extension requirements, version requirements, and similar items.
+
+Further information on this file and its format can be found in the [Environment checking](https://docs.moodle.org/dev/Environment_checking) documentation.
diff --git a/versioned_docs/version-5.1/apis/_files/environment-xml.tsx b/versioned_docs/version-5.1/apis/_files/environment-xml.tsx
new file mode 100644
index 0000000000..ce2afb8324
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/environment-xml.tsx
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './environment-xml.mdx';
+
+const defaultExample = `
+
+
+
+
+
+
+
+
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/index.tsx b/versioned_docs/version-5.1/apis/_files/index.tsx
new file mode 100644
index 0000000000..76ac8c209f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/index.tsx
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+
+import AmdDir from './amd-dir';
+import BackupDir from './backup-dir';
+import CLIDir from './cli-dir';
+import Changes from './changes';
+import ClassesDir from './classes-dir';
+import DbAccessPHP from './db-access-php';
+import DbEventsPHP from './db-events-php';
+import DbInstallPHP from './db-install-php';
+import DbInstallXML from './install-xml';
+import DbMessagesPHP from './db-messages-php';
+import DbMobilePHP from './db-mobile-php';
+import DbLegacyclassesPHP from './db-legacyclasses-php';
+import DbRenamedclassesPHP from './db-renamedclasses-php';
+import DbServicesPHP from './db-services-php';
+import DbTasksPHP from './db-tasks-php';
+import DbUninstallPHP from './db-uninstall-php';
+import DbUpgradePHP from './db-upgrade-php';
+import EnvironmentXML from './environment-xml';
+import Lang from './lang';
+import Lib from './lib';
+import LocalLib from './locallib';
+import PixDir from './pix-dir';
+import Readme from './readme';
+import ReadmeMoodleTXT from './readme_moodle-txt';
+import SettingsPHP from './settings-php';
+import StylesCSS from './styles-css';
+import ThirdpartylibsXML from './thirdpartylibs-xml';
+import UpgradeTXT from './upgrade-txt';
+import VersionPHP from './version-php';
+import YUIDir from './yui-dir';
+
+export {
+ AmdDir,
+ BackupDir,
+ CLIDir,
+ Changes,
+ ClassesDir,
+ DbAccessPHP,
+ DbEventsPHP,
+ DbInstallPHP,
+ DbInstallXML,
+ DbMessagesPHP,
+ DbMobilePHP,
+ DbLegacyclassesPHP,
+ DbRenamedclassesPHP,
+ DbServicesPHP,
+ DbTasksPHP,
+ DbUninstallPHP,
+ EnvironmentXML,
+ Lang,
+ Lib,
+ LocalLib,
+ PixDir,
+ Readme,
+ ReadmeMoodleTXT,
+ SettingsPHP,
+ StylesCSS,
+ ThirdpartylibsXML,
+ DbUpgradePHP,
+ UpgradeTXT,
+ VersionPHP,
+ YUIDir,
+};
diff --git a/versioned_docs/version-5.1/apis/_files/install-xml.tsx b/versioned_docs/version-5.1/apis/_files/install-xml.tsx
new file mode 100644
index 0000000000..98b03c8821
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/install-xml.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './db-install-xml.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/lang-extra.md b/versioned_docs/version-5.1/apis/_files/lang-extra.md
new file mode 100644
index 0000000000..f6f10fb772
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/lang-extra.md
@@ -0,0 +1,15 @@
+
+
+:::caution Activity modules are different
+
+Activity modules do not use the __frankenstyle__ name as a filename, they use the plugin name. For example the forum activity plugin:
+
+```php
+// Plugin type: `mod`
+// Plugin name: `forum`
+// Frankenstyle plugin name: `mod_forum`
+// Plugin location: `mod/forum`
+// Language string location: `mod/forum/lang/en/forum.php`
+```
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/lang.md b/versioned_docs/version-5.1/apis/_files/lang.md
new file mode 100644
index 0000000000..c73920bb90
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/lang.md
@@ -0,0 +1,25 @@
+
+
+Each plugin must define a set of language strings with, at a minimum, an English translation. These are specified in the plugin's `lang/en` directory in a file named after the plugin. For example the LDAP authentication plugin:
+
+```php
+// Plugin type: `auth`
+// Plugin name: `ldap`
+// Frankenstyle plugin name: `auth_ldap`
+// Plugin location: `auth/ldap`
+// Language string location: `auth/ldap/lang/en/auth_ldap.php`
+```
+
+:::warning
+
+Every plugin _must_ define the name of the plugin, or its `pluginname`.
+
+:::
+
+The `get_string` API can be used to translate a string identifier back into a translated string.
+
+```
+get_string('pluginname', '[plugintype]_[pluginname]');
+```
+
+- See the [String API](https://docs.moodle.org/dev/String_API#Adding_language_file_to_plugin) documentation for more information on language files.
diff --git a/versioned_docs/version-5.1/apis/_files/lang.tsx b/versioned_docs/version-5.1/apis/_files/lang.tsx
new file mode 100644
index 0000000000..8356a3d5af
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/lang.tsx
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { ComponentFileSummaryProps } from '../../_utils';
+import DefaultDescription from './lang.md';
+
+const defaultExample = "$string['pluginname'] = 'The name of my plugin will go here';";
+
+export default (initialProps: ComponentFileSummaryProps): JSX.Element => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/lib.mdx b/versioned_docs/version-5.1/apis/_files/lib.mdx
new file mode 100644
index 0000000000..e9f02313d0
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/lib.mdx
@@ -0,0 +1,11 @@
+
+
+The `lib.php` file is a legacy file which acts as a bridge between Moodle core, and the plugin. In recent plugins it is should only used to define callbacks and related functionality which currently is not supported as an auto-loadable class.
+
+All functions defined in this file **must** meet the requirements set out in the relevant section of the [Coding style](/general/development/policies/codingstyle#functions-and-methods).
+
+:::note Performance impact
+
+Moodle core often loads all the lib.php files of a given plugin types. For performance reasons, it is strongly recommended to keep this file as small as possible and have just required code implemented in it. All the plugin's internal logic should be implemented in the auto-loaded classes.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/lib.tsx b/versioned_docs/version-5.1/apis/_files/lib.tsx
new file mode 100644
index 0000000000..bc3ddec6b9
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/lib.tsx
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './lib.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/locallib.mdx b/versioned_docs/version-5.1/apis/_files/locallib.mdx
new file mode 100644
index 0000000000..fb3cab2811
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/locallib.mdx
@@ -0,0 +1,14 @@
+
+
+
+:::caution Legacy feature
+
+The use of this file is no longer recommended, and new uses of it will not be permitted in core code.
+
+:::
+
+Rather than creating global functions in a global namespace in a locallib.php file, you should use autoloaded classes which are located in the classes/ directory.
+
+Where this file is in use, all functions **must** meet the requirements set out in the relevant section of the [Coding style](/general/development/policies/codingstyle#functions-and-methods)
+
+Existing functions which have been incorrectly named **will not** be accepted as an example of an existing convention. Existing functions which are incorrectly named **should** be converted to use a namespaced class.
diff --git a/versioned_docs/version-5.1/apis/_files/locallib.tsx b/versioned_docs/version-5.1/apis/_files/locallib.tsx
new file mode 100644
index 0000000000..0093700154
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/locallib.tsx
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './locallib.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/pix-dir.mdx b/versioned_docs/version-5.1/apis/_files/pix-dir.mdx
new file mode 100644
index 0000000000..f58f9c6fee
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/pix-dir.mdx
@@ -0,0 +1,6 @@
+
+Plugins can provide icons in several formats, and most plugin types require that a default icon be provided.
+
+Where a browser supports it, the `svg` format is used, falling back to `png` formats when an SVG is unavailable.
+
+Full details of the correct naming, sizing, and design guidelines for icons in Moodle can be found in the [Moodle icons](https://docs.moodle.org/dev/Moodle_icons) documentation.
diff --git a/versioned_docs/version-5.1/apis/_files/pix-dir.tsx b/versioned_docs/version-5.1/apis/_files/pix-dir.tsx
new file mode 100644
index 0000000000..cdb803f34a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/pix-dir.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './pix-dir.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/readme.mdx b/versioned_docs/version-5.1/apis/_files/readme.mdx
new file mode 100644
index 0000000000..42bb31fa21
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/readme.mdx
@@ -0,0 +1,4 @@
+
+We recommend that you include any additional information for your plugin in a project readme file. Ideally this should act as an offline version of all information in your plugin's page in the [Plugins directory](/general/community/plugincontribution/pluginsdirectory).
+
+We recommend creating your readme file in either a `README.md`, or `README.txt` format.
diff --git a/versioned_docs/version-5.1/apis/_files/readme.tsx b/versioned_docs/version-5.1/apis/_files/readme.tsx
new file mode 100644
index 0000000000..8999ddaccc
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/readme.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './readme.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/readme_moodle-txt.mdx b/versioned_docs/version-5.1/apis/_files/readme_moodle-txt.mdx
new file mode 100644
index 0000000000..ed589cbfd4
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/readme_moodle-txt.mdx
@@ -0,0 +1,5 @@
+
+When importing a third-party library into your plugin, it is advisable to create a `readme_moodle.txt` file detailing relevant information, including:
+
+- Download URLs
+- Build instructions
diff --git a/versioned_docs/version-5.1/apis/_files/readme_moodle-txt.tsx b/versioned_docs/version-5.1/apis/_files/readme_moodle-txt.tsx
new file mode 100644
index 0000000000..55827c08aa
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/readme_moodle-txt.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './readme_moodle-txt.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/settings-php.mdx b/versioned_docs/version-5.1/apis/_files/settings-php.mdx
new file mode 100644
index 0000000000..04b942c4c9
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/settings-php.mdx
@@ -0,0 +1,16 @@
+
+You can define settings for your plugin that the administrator can configure by creating a `settings.php` file in the root of your plugins' directory.
+
+:::caution
+
+Settings must named in the following format:
+
+```
+plugintype_pluginname/settingname
+```
+
+By following the correct naming, all settings will automatically be stored in the `config_plugins` database table.
+
+:::
+
+Full details on how to create settings are available in the [Admin settings](../subsystems/admin/index.md) documentation.
diff --git a/versioned_docs/version-5.1/apis/_files/settings-php.tsx b/versioned_docs/version-5.1/apis/_files/settings-php.tsx
new file mode 100644
index 0000000000..9dd13af88e
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/settings-php.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './settings-php.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/styles-css.mdx b/versioned_docs/version-5.1/apis/_files/styles-css.mdx
new file mode 100644
index 0000000000..5b5495f69b
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/styles-css.mdx
@@ -0,0 +1,11 @@
+
+Plugins may define a '/styles.css' to provide plugin-specific styling. See the following for further documentation:
+
+- [Plugin contribution checklist#CSS styles](/general/community/plugincontribution/checklist#css-styles)
+- [CSS Coding Style](https://docs.moodle.org/dev/CSS_Coding_Style)
+
+:::tip Avoid custom styles where possible
+
+Rather than writing custom CSS for your plugin, where possible apply Bootstrap classes to the DOM elements in your output. These will be easier to maintain and will adopt most colour, branding, and other customisations applied to a theme.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/styles-css.tsx b/versioned_docs/version-5.1/apis/_files/styles-css.tsx
new file mode 100644
index 0000000000..21ea5adcc5
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/styles-css.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './styles-css.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/thirdpartylibs-xml.mdx b/versioned_docs/version-5.1/apis/_files/thirdpartylibs-xml.mdx
new file mode 100644
index 0000000000..301dde8126
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/thirdpartylibs-xml.mdx
@@ -0,0 +1,14 @@
+
+Details of all third-party libraries should be declared in the `thirdpartylibs.xml` file.
+
+This information is used to generate ignore file configuration for linting tools. For Moodle core it is also used to generate library information as part of release notes and credits.
+
+Within the XML the `location` is a file, or directory, relative to your plugin's root.
+
+:::caution Licensing
+
+The license of any third-party code included in your plugin, and within the `thirdpartylibs.xml` file **must** be [compatible with the GNU GPLv3](http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses).
+
+:::
+
+See the [Third Party Libraries](https://docs.moodle.org/dev/Third_Party_Libraries) documentation for further information.
diff --git a/versioned_docs/version-5.1/apis/_files/thirdpartylibs-xml.tsx b/versioned_docs/version-5.1/apis/_files/thirdpartylibs-xml.tsx
new file mode 100644
index 0000000000..052e1a2ed0
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/thirdpartylibs-xml.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './thirdpartylibs-xml.mdx';
+
+const defaultExample = `
+
+
+ javascript/html5shiv.js
+ Html5Shiv
+ 3.6.2
+ Apache
+ 2.0
+
+
+ vendor/guzzle/guzzle/
+ guzzle
+ v3.9.3
+ MIT
+
+
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/upgrade-php.mdx b/versioned_docs/version-5.1/apis/_files/upgrade-php.mdx
new file mode 100644
index 0000000000..41f6748ed8
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/upgrade-php.mdx
@@ -0,0 +1,13 @@
+
+The `db/upgrade.php` file contains upgrade steps, including database schema changes, changes to settings, and other steps which must be performed during upgrade.
+
+See the [Upgrade API](../../guides/upgrade/index.md) documentation for further information.
+
+:::danger Generating Database Schema changes
+
+When making changes to the database schema you **must** use the build-in [XMLDB editor](https://docs.moodle.org/dev/XMLDB_Documentation) within
+Moodle. This can be used to generate php upgrade steps.
+
+The [install.xml](../commonfiles/index.mdx#dbinstallxml) schema must match the schema generated by the upgrade at all times.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/upgrade-txt.mdx b/versioned_docs/version-5.1/apis/_files/upgrade-txt.mdx
new file mode 100644
index 0000000000..a29d8cd37f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/upgrade-txt.mdx
@@ -0,0 +1,56 @@
+
+Each component and subsystem may make use of an `upgrade.txt` file in the top level folder. A section title is used to identify the Moodle version where the change was introduced, and significant changes for that version relating to that component or subsystem are noted.
+
+For example, given an API change is applied for the upcoming Moodle version 4.1 which is still in the **main** branch (4.1dev), the version number on the `upgrade.txt`'s section title will be set to **4.1**.
+
+```txt title="Example 1: Change applied to the main branch"
+== 4.1 ==
+An API change to empower educators!
+```
+
+#### Changes applied to multiple branches
+
+When changes are integrated to multiple branches, for example a stable version and the main branch, then the relevant versions used to describe the change in the `upgrade.txt` file should be the next version to be released _for each branch_. The **main** branch should always use the next major version.
+
+For example, if a change is applied to the **MOODLE_400_STABLE** during the development of Moodle 4.0.2, and the **main** branch during the development of Moodle 4.1, then the relevant versions will be **4.0.2** and **4.1**, respectively. The section title for the **main** branch will be the same as the one in Example 1. The section title for the **MOODLE_400_STABLE** branch will indicate the next upcoming minor version (4.0.2 in this case):
+
+```txt title="Example 2: Patch applied to main and MOODLE_400_STABLE"
+== 4.0.2 ==
+An API change to empower educators!
+```
+
+#### Mentioning other Moodle versions the change applies to
+
+Multiple versions within the section title are **not** allowed. However, developers may note the Moodle versions that the change applies to within the upgrade note text itself.
+
+```txt title="Example 3a: main (4.1dev)"
+== 4.1 ==
+An API change to empower educators! (This was fixed in 4.1 and 4.0.2)
+```
+
+```txt title="Example 3b: MOODLE_400_STABLE"
+== 4.0.2 ==
+An API change to empower educators! (This was fixed in 4.1 and 4.0.2)
+```
+
+```txt title="Example 3c: (INCORRECT) Multiple versions on the section title"
+== 4.1, 4.0.2 ==
+An API change to empower educators!
+```
+
+#### Exception during parallel development
+
+When Moodle is developing two major versions in parallel, for example Moodle 3.11.0, and Moodle 4.0.0, then the
+version in the earliest of the major version development branches will be used for both branches.
+
+For example, given we are in a parallel development situation with **MOODLE_311_STABLE** (3.11dev) and **main** (4.0dev), with Moodle 3.11 as the next upcoming major Moodle version. If an API change is applied to **MOODLE_311_STABLE**, the version number on the section title will be **3.11** for both **main** and **MOODLE_400_STABLE** branches.
+
+```txt title="Example 4a: main (4.0dev)"
+== 3.11 ==
+An API change to empower educators!
+```
+
+```txt title="Example 4b: MOODLE_311_STABLE (3.11dev)"
+== 3.11 ==
+An API change to empower educators!
+```
diff --git a/versioned_docs/version-5.1/apis/_files/upgrade-txt.tsx b/versioned_docs/version-5.1/apis/_files/upgrade-txt.tsx
new file mode 100644
index 0000000000..db928bfec3
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/upgrade-txt.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './upgrade-txt.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/_files/version-php.mdx b/versioned_docs/version-5.1/apis/_files/version-php.mdx
new file mode 100644
index 0000000000..23c9c7847a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/version-php.mdx
@@ -0,0 +1,11 @@
+
+The version.php contains metadata about the plugin.
+
+It is used during the installation and upgrade of the plugin.
+
+This file contains metadata used to describe the plugin, and includes information such as:
+
+- the version number
+- a list of dependencies
+- the minimum Moodle version required
+- maturity of the plugin
diff --git a/versioned_docs/version-5.1/apis/_files/version-php.tsx b/versioned_docs/version-5.1/apis/_files/version-php.tsx
new file mode 100644
index 0000000000..f855221dcb
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/version-php.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import { type ComponentFileSummaryProps } from '@site/src/components/ComponentFileSummary';
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import DefaultDescription from './version-php.mdx';
+
+const defaultExample = `defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = TODO;
+$plugin->requires = TODO;
+$plugin->supported = TODO; // Available as of Moodle 3.9.0 or later.
+$plugin->incompatible = TODO; // Available as of Moodle 3.9.0 or later.
+$plugin->component = 'TODO_FRANKENSTYLE';
+$plugin->maturity = MATURITY_STABLE;
+$plugin->release = 'TODO';
+
+$plugin->dependencies = [
+ 'mod_forum' => 2022042100,
+ 'mod_data' => 2022042100
+];
+`;
+
+export default function VersionPHP(props: ComponentFileSummaryProps): JSX.Element {
+ return (
+
+ );
+}
diff --git a/versioned_docs/version-5.1/apis/_files/yui-dir.mdx b/versioned_docs/version-5.1/apis/_files/yui-dir.mdx
new file mode 100644
index 0000000000..4ec0275759
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/yui-dir.mdx
@@ -0,0 +1,11 @@
+
+In older versions of Moodle, JavaScript was written in the YUI format. This is being phased out in favour of [JavaScript Modules](../../guides/javascript/modules.md), although some older uses still remain in Moodle core.
+
+- [YUI/Modules](../../guides/javascript/yui/modules.md)
+- [YUI](../../guides/javascript/yui/index.md)
+
+:::caution
+
+New YUI code will not be accepted into Moodle core, except for new plugins for the [Atto editor](../plugintypes/atto/index.md).
+
+:::
diff --git a/versioned_docs/version-5.1/apis/_files/yui-dir.tsx b/versioned_docs/version-5.1/apis/_files/yui-dir.tsx
new file mode 100644
index 0000000000..fec0d588eb
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/_files/yui-dir.tsx
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../_utils';
+import type { Props } from '../../_utils';
+import DefaultDescription from './yui-dir.mdx';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/commonfiles/db-tasks.php/index.md b/versioned_docs/version-5.1/apis/commonfiles/db-tasks.php/index.md
new file mode 100644
index 0000000000..024c64999f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/commonfiles/db-tasks.php/index.md
@@ -0,0 +1,154 @@
+---
+title: db/tasks.php
+tags:
+ - Plugins
+ - Common files
+ - Scheduled tasks
+description: A description of the plugin scheduled task configuration file
+---
+
+import { LanguageProperty } from '@site/src/components';
+
+If a plugin wants to configure scheduled task, two items are required:
+
+- a class extending the `\core\task\scheduled_task` class; and
+- the `db/tasks.php` file containing its initial configuration.
+
+The general format of the file is as follows:
+
+```php
+$tasks = [
+ // First task configuration.
+ [ ... ],
+
+ // Second task configuration.
+ [ ... ],
+];
+```
+
+Each task configuration entry has a number of possible properties, described below.
+
+## Task configuration entries
+
+### Classname
+
+
+
+The `classname` contains the fully-qualified class name where the scheduled task is located.
+
+```php
+$tasks = [
+ [
+ 'classname' => 'mod_example\task\do_something',
+ // ...
+ ]
+]
+```
+
+### Blocking
+
+
+
+Tasks can be configured to block the execution of all other tasks by setting the `blocking` property to a truthy value.
+
+:::caution
+
+Whilst this feature is available its use is _strongly_ discouraged and *will not* be accepted in Moodle core.
+
+:::
+
+```php
+$tasks = [
+ [
+ 'classname' => 'mod_example\task\do_something',
+ 'blocking' => 1,
+ // ...
+ ],
+];
+```
+
+### Date and time fields
+
+
+
+The following date and time fields are available:
+
+- month
+- day
+- dayofweek
+- hour
+- month
+
+Each of these fields accepts one, or more values, and the format for each field is described as:
+
+```
+ := (/)(,)
+ := int
+ := |||
+ := *
+ := int-int
+ := R
+```
+
+:::info Random values
+
+A fixed random value can be selected by using a value of `R`. By specifying this option, a random day or time is chosen when the task is installed or updated. The same value will be used each time the task is scheduled.
+
+:::
+
+If no value is specified then the following defaults are used:
+
+- Month: `*` (Every month)
+- Day: `*` (Every day)
+- Day of the week: `*` (Every day of the week)
+- Hour: `*` (Every hour)
+- Minute: `*` (Every minute)
+
+:::info Day and Day of the week
+
+If either field is set to `*` then use the other field, otherwise the soonest value is used.
+
+:::
+
+#### Examples
+
+```php title="Run at a fixed time each day, randomised during installation of the task"
+$tasks = [
+ [
+ 'classname' => 'mod_example\task\do_something',
+
+ // Every month.
+ 'month' => '*',
+ // Every day.
+ 'day' => '*',
+
+ // A fixed random hour and minute.
+ 'hour' => 'R',
+ 'month' => 'R',
+ ],
+];
+```
+
+```php title="Specifying multiple times in an hour"
+$tasks = [
+ [
+ 'classname' => 'mod_example\task\do_something',
+
+ // At two intervals in the hour.
+ 'minute' => '5, 35',
+ ],
+];
+```
+
+### Disabled tasks
+
+You can create a task that defaults to disabled by setting the field **disabled** to 1. Unless the administrator manually enables your task, it will not run.
+
+This is useful if a task is only required in certain situations and shouldn't run on every server that has your plugin installed.
diff --git a/versioned_docs/version-5.1/apis/commonfiles/index.mdx b/versioned_docs/version-5.1/apis/commonfiles/index.mdx
new file mode 100644
index 0000000000..f5749e95c9
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/commonfiles/index.mdx
@@ -0,0 +1,169 @@
+---
+title: Common files
+tags:
+ - Plugins
+ - API
+ - Subsystem
+---
+
+import {
+ AmdDir,
+ BackupDir,
+ CLIDir,
+ Changes,
+ ClassesDir,
+ DbAccessPHP,
+ DbEventsPHP,
+ DbInstallPHP,
+ DbInstallXML,
+ DbMessagesPHP,
+ DbLegacyclassesPHP,
+ DbRenamedclassesPHP,
+ DbServicesPHP,
+ DbTasksPHP,
+ DbUninstallPHP,
+ DbUpgradePHP,
+ EnvironmentXML,
+ Lang,
+ Lib,
+ LocalLib,
+ PixDir,
+ Readme,
+ ReadmeMoodleTXT,
+ SettingsPHP,
+ StylesCSS,
+ ThirdpartylibsXML,
+ UpgradeTXT,
+ VersionPHP,
+ YUIDir,
+} from '../_files';
+
+
+
+This page describes the common files which may be present in any Moodle subsystem or [plugin type](../plugintypes/index.md). Some of these files are mandatory and __must__ exist within a component, whilst others are optional.
+
+### version.php
+
+
+
+### lang/en/plugintype_pluginname.php
+
+import extraLangDescription from '../_files/lang-extra.md';
+
+
+
+### lib.php
+
+
+
+### locallib.php
+
+
+
+### db/install.xml
+
+
+
+### db/upgrade.php
+
+
+
+### db/access.php
+
+
+
+### db/install.php
+
+
+
+### db/uninstall.php
+
+
+
+### db/events.php
+
+
+
+### db/messages.php
+
+
+
+### db/services.php
+
+
+
+### db/tasks.php
+
+
+
+### db/renamedclasses.php
+
+
+
+### db/legacyclasses.php
+
+
+
+### classes/
+
+
+
+### cli/
+
+
+
+### settings.php
+
+
+
+### amd/
+
+
+
+### yui/
+
+
+
+### backup/
+
+
+
+### styles.css
+
+
+
+### pix/icon.svg
+
+
+
+### thirdpartylibs.xml
+
+
+
+### readme_moodle.txt
+
+
+
+### upgrade.txt
+
+
+
+### environment.xml
+
+
+
+### README
+
+
+
+### CHANGES
+
+
+
+## See also
+
+- [Moodle architecture](https://docs.moodle.org/dev/Moodle_architecture) - general overview of Moodle code architecture
+- [Plugin types](../plugintypes/index.md) - list of all supported plugin types
+- [Moodle plugins directory](https://moodle.org/plugins/) - repository of contributed plugins for Moodle
+- [Moodle plugin skeleton generator](https://moodle.org/plugins/tool_pluginskel) - allows to quickly generate code skeleton for a new plugin
+- [Checklist for plugin contributors](/general/community/plugincontribution/checklist) - read before submitting a plugin
diff --git a/versioned_docs/version-5.1/apis/commonfiles/tag.php/index.md b/versioned_docs/version-5.1/apis/commonfiles/tag.php/index.md
new file mode 100644
index 0000000000..fcfd89e679
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/commonfiles/tag.php/index.md
@@ -0,0 +1,112 @@
+---
+title: tag.php
+tags:
+ - Plugins
+ - Tags
+ - API
+description: A description of the library tag.php file, describing what plugins have tags where their callbacks are located.
+---
+
+import { LanguageProperty } from '@site/src/components';
+
+:::note
+There are more options such as specifying the default value for "Standard tags", having a fixed collection or excluding from search.
+:::
+
+## Example file
+
+Here is the core libraries tag.php file for reference:
+
+```php
+.
+
+/**
+ * Tag area definitions
+ *
+ * File db/tag.php lists all available tag areas in core or a plugin.
+ *
+ * Each tag area may have the following attributes:
+ * - itemtype (required) - what is tagged. Must be name of the existing DB table
+ * - component - component responsible for tagging, if the tag area is inside a
+ * plugin the component must be the full frankenstyle name of the plugin
+ * - collection - name of the custom tag collection that will be used to store
+ * tags in this area. If specified aministrator will be able to neither add
+ * any other tag areas to this collection nor move this tag area elsewhere
+ * - searchable (only if collection is specified) - wether the tag collection
+ * should be searchable on /tag/search.php
+ * - showstandard - default value for the "Standard tags" attribute of the area,
+ * this is only respected when new tag area is added and ignored during upgrade
+ * - customurl (only if collection is specified) - custom url to use instead of
+ * /tag/search.php to display information about one tag
+ * - callback - name of the function that returns items tagged with this tag,
+ * see core_tag_tag::get_tag_index() and existing callbacks for more details,
+ * callback should return instance of core_tag\output\tagindex
+ * - callbackfile - file where callback is located (if not an autoloaded location)
+ *
+ * Language file must contain the human-readable names of the tag areas and
+ * collections (either in plugin language file or in component language file or
+ * lang/en/tag.php in case of core):
+ * - for item type "user":
+ * $string['tagarea_user'] = 'Users';
+ * - for tag collection "mycollection":
+ * $string['tagcollection_mycollection'] = 'My tag collection';
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tagareas = [
+ [
+ 'itemtype' => 'user', // Users.
+ 'component' => 'core',
+ 'callback' => 'user_get_tagged_users',
+ 'callbackfile' => '/user/lib.php',
+ 'showstandard' => core_tag_tag::HIDE_STANDARD,
+ ],
+ [
+ 'itemtype' => 'course', // Courses.
+ 'component' => 'core',
+ 'callback' => 'course_get_tagged_courses',
+ 'callbackfile' => '/course/lib.php',
+ ],
+ [
+ 'itemtype' => 'question', // Questions.
+ 'component' => 'core_question',
+ 'multiplecontexts' => true,
+ ],
+ [
+ 'itemtype' => 'post', // Blog posts.
+ 'component' => 'core',
+ 'callback' => 'blog_get_tagged_posts',
+ 'callbackfile' => '/blog/lib.php',
+ ],
+ [
+ 'itemtype' => 'blog_external', // External blogs.
+ 'component' => 'core',
+ ],
+ [
+ 'itemtype' => 'course_modules', // Course modules.
+ 'component' => 'core',
+ 'callback' => 'course_get_tagged_course_modules',
+ 'callbackfile' => '/course/lib.php',
+ ],
+];
+
+```
diff --git a/versioned_docs/version-5.1/apis/commonfiles/version.php/index.md b/versioned_docs/version-5.1/apis/commonfiles/version.php/index.md
new file mode 100644
index 0000000000..f38a378b49
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/commonfiles/version.php/index.md
@@ -0,0 +1,210 @@
+---
+title: version.php
+tags:
+ - Plugins
+ - Common files
+ - Plugin types
+description: A description of the plugin version.php file, describing the various features
+---
+
+import { LanguageProperty } from '@site/src/components';
+
+Every plugin must have a `version.php` file located in the root directory of that plugin.
+
+It contains a number of properties, which are used during the plugin installation and upgrade process. It allows to make sure the plugin is compatible with the given Moodle site, as well as spotting whether an upgrade is needed.
+
+## Plugin version properties
+
+### Component
+
+
+
+The component value contains the name of the plugin in its full [frankenstyle](/general/development/policies/codingstyle/frankenstyle) format.
+
+```php
+$plugin->component = 'plugintype_pluginname';
+```
+
+This value is used during the installation and upgrade process for diagnostics and validation purposes to make sure the plugin code has been deployed to the correct location within the Moodle code tree.
+
+### Version
+
+
+
+The version number of the plugin. The format is partially date based with the form YYYYMMDDXX where XX is an incremental counter for the given year (YYYY), month (MM) and date (DD) of the plugin version's release. Every new plugin version must have this number increased in this file, which is detected by Moodle core and the upgrade process is triggered.
+
+If multiple stable branches of the plugin are maintained, the date part YYYYMMDD should be frozen at the branch forking date and the XX is used for incrementing the value on the given stable branch (allowing up to 100 updates on the given stable branch). The key point is that the version number is always increased both horizontally (within the same stable branch, more recent updates have higher XX than any previous one) and vertically (between any two stable branches, the more recent branch has YYYYMMDD00 higher than the older stable branch). Pay attention to this. It's easy to mess up and hard to fix.
+
+```php
+$plugin->version = 2022061700;
+ // YYYY
+ // MM
+ // DD
+ // XX
+```
+
+### Requirements
+
+
+
+The requires key specifies the minimum version of Moodle core requires for this plugin to function. It is _not_ possible to install the plugin on an earlier version of Moodle.
+
+Moodle core's version number is defined in the `version.php` file located in the Moodle root directory.
+
+```php
+// Require Moodle 4.0.0.
+$plugin->requires = 2022041900.00;
+```
+
+### Supported versions
+
+
+
+
+A set of branch numbers to specify the lowest and highest branches of Moodle that the plugin supports. These value are inclusive.
+
+```php title="Support all versions of Moodle 3.11, and 4.0"
+$plugin->supported = [
+
+ // Support from the Moodle 3.11 series.
+ 311,
+
+ // To the Moodle 4.0 series.
+ 400,
+];
+```
+
+### Incompatible versions
+
+
+
+
+The _earliest_ **incompatible** version of Moodle that the plugin cannot support the specified branch of Moodle.
+
+The plugin will not be installable on any versions of Moodle from this point on.
+
+```php title="Specify that this version of the plugin does not support Moodle 3.11 and subsequent releases"
+$plugin->incompatible = 311;
+```
+
+### Maturity
+
+
+
+The maturity of the plugin, otherwise known as its stability. This value affects the [available update notifications](https://docs.moodle.org/en/Available_update_notifications) feature in Moodle.
+
+Administrators can configure their site so that they are not notified about an available update unless it has certain maturity level declared.
+
+```php
+// The plugin is a pre-release version.
+$plugin->maturity = MATURITY_ALPHA;
+
+// The plugin is a beta version.
+$plugin->maturity = MATURITY_BETA;
+
+// The plugin is a release candidate version.
+$plugin->maturity = MATURITY_RC;
+
+// The plugin is a stable version.
+$plugin->maturity = MATURITY_STABLE;
+```
+
+### Release name
+
+
+
+A human-readable version name that should help to identify each release of the plugin.
+
+This can be any value you like, although it is recommended that you choose a pattern and stick with it. Usually this is a simple version like 2.1 but some plugin authors use more sophisticated schemes or follow the upstream release name if the plugin represents a wrapper for another program.
+
+```php
+// This plugin release is version 1.0 for the 52.0-flamethrower upstream dependency.
+$plugin->release = '52.0-flamethrower-1.0';
+```
+
+### Peer dependenices
+
+
+
+An optional list of related plugins that this plugin depends upon to work.
+
+Moodle core checks that these declared dependencies are met, and will prevent installation and upgrade of this plugin if any of the dependencies are not met.
+
+```php
+$plugin->dependencies = [
+ // Depend upon version 2022041900 of mod_forum.
+ 'mod_forum' => 2022041900,
+
+ // Depend upon version 2022041900 of block_foo.
+ 'block_foo' => 2022041900,
+]
+```
+
+## File template
+
+Here is a template for the plugin's version.php file to copy and paste:
+
+```php
+.
+
+/**
+ * @package plugintype_pluginname
+ * @copyright 2020, You Name
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2022060100;
+$plugin->requires = 2022041900.00; // Moodle 4.0.
+$plugin->supported = [400, 400];
+$plugin->incompatible = 401;
+$plugin->component = 'tool_example';
+$plugin->maturity = MATURITY_STABLE;
+$plugin->release = '41.3-lemmings-1.0';
+
+$plugin->dependencies = [
+ 'mod_forum' => 2022041900,
+ 'mod_data' => 2022041900,
+];
+```
+
+## See also
+
+- [Moodle versions](https://docs.moodle.org/dev/Moodle_versions)
diff --git a/versioned_docs/version-5.1/apis/core/activitycompletion/index.md b/versioned_docs/version-5.1/apis/core/activitycompletion/index.md
new file mode 100644
index 0000000000..e959da9505
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/activitycompletion/index.md
@@ -0,0 +1,481 @@
+---
+title: Activity completion API
+tags:
+ - Conditional activities
+ - API
+---
+
+:::note
+
+There are changes to the completion API introduced in **Moodle 3.11** to be incorporated to this page. Please refer to [Student activity completion](https://docs.moodle.org/dev/Student_activity_completion) for details.
+
+:::
+
+Activities do not need to be changed to support conditional availability, but they do need changing to support the completion system.
+
+If you make no changes to an activity whatsoever, it can only support 'manual' completion (where the user ticks a box).
+
+## Feature support
+
+To support the completion system, your activity must include a `[activityname]_supports` function in its `lib.php`. Here is an example:
+
+```php
+ /**
+ * Indicates API features that the forum supports.
+ *
+ * @param string $feature
+ * @return null|bool
+ */
+function forum_supports(string $feature): bool {
+ switch($feature) {
+ case FEATURE_COMPLETION_TRACKS_VIEWS:
+ return true;
+ case FEATURE_COMPLETION_HAS_RULES:
+ return true;
+ default:
+ return null;
+ }
+}
+```
+
+The relevant features for completion are:
+
+- **`FEATURE_COMPLETION_TRACKS_VIEWS`** - the activity can support completion 'on view', meaning that an activity becomes marked complete as soon as a user clicks on it.
+- **`FEATURE_GRADE_HAS_GRADE`** - the activity provides (or may provide, depending on settings) a grade for students. When an activity supports grades, it can support completion 'on grade', meaning that an activity becomes marked complete as soon as a user is assigned a grade.
+- **`FEATURE_COMPLETION_HAS_RULES`** - the activity has custom completion rules.
+
+## Completion on view
+
+Completion on view means that, if selected, an activity is marked as complete as soon as the user views it.
+
+'View' is usually defined as seeing the activity's main page; if you click on the activity, and there isn't an error, you have probably viewed it. However it is up to each activity precisely how they define 'view'.
+
+### How to implement
+
+In your activity's `[activityname]_supports` function, return true for `FEATURE_COMPLETION_TRACKS_VIEWS`.
+
+Then add this code to run whenever a user successfully views the activity. In order for navigation to work as expected (that is so that the navigation block on the activity's page takes account that you have viewed this activity, if there is another activity that depends on it) you should put this code before printing the page header.
+
+```php
+ $completion = new completion_info($course);
+ $completion->set_module_viewed($cm);
+```
+
+### Performance issues
+
+Calling this method has no significant performance cost if 'on view' completion is not enabled for the activity. If it is enabled, then the performance cost is kept low because the 'viewed' state is cached; it doesn't add a database query to every request.
+
+## Completion on grade
+
+Completion on grade means that, if selected, an activity is marked as complete as soon as the user receives a grade from that activity.
+
+### How to implement
+
+In your `[activityname]_supports` function, return true for `FEATURE_GRADE_HAS_GRADE`. No other action is necessary.
+
+### Performance issues
+
+When 'on grade' completion is enabled, there will be some additional database queries after a grade is assigned or changed. Unless your activity changes grades very frequently, this is unlikely to be an issue.
+
+## Custom completion rules
+
+Custom completion rules allow for activity-specific conditions. For example, the forum has custom rules so that a teacher can configure it to mark a user as having completed the activity when that user makes a certain number of posts to the forum.
+
+Implementing custom completion rules is more complex than using the system-provided 'view' or 'grade' conditions, but the instructions below should help make it clear.
+
+### Implementation overview
+
+To implement custom completion rules, you need to:
+
+1. Return true for `FEATURE_COMPLETION_HAS_RULES` in your activity's `_supports` function.
+1. Add database fields to your activity's main table to store the custom completion settings.
+1. Add backup and restore code to back up these fields.
+1. Add information about the completion settings to the activities cm_info object.
+1. Add controls to your activity's settings form so that users can select the custom rules, altering these database settings.
+1. Add a function that checks the value of these rules (if set).
+1. Add function returns descriptions for the completion states.
+1. Add code so that whenever the value affecting a rule might change, you inform the completion system.
+
+### Database fields for completion settings
+
+When you provide a custom completion rule for a activity, that rule requires data to be stored with each activity instance: whether the rule is enabled for that instance, and any options that apply to the rule.
+
+Usually the best place to store this information is your activity's main table because:
+
+- The information in the relevant row of this table is likely to be available in most parts of your code, so code changes are minimised.
+- You already read this row with most requests, so there is no need for additional database queries which would reduce performance.
+- The main table is used for most other activity options so it is a logical place for this information.
+If you are adding a basic completion condition you probably only need to add one field. To add a field to an existing activity, you need to change the db/install.xml and the db/upgrade.php in the same way as adding any other field.
+
+#### Example
+
+Throughout this section I am using the forum as an example. The forum provides three completion options but because they all behave the same way, I am only showing one of them.
+
+The forum adds this field to store a completion option:
+
+- **`completionposts`** - this may be 0 or an integer. If it's an integer, say 3, then the user needs to add 3 forum posts (either new discussions or replies) in order for the forum to count as 'completed'.
+
+### Backup and restore for completion fields
+
+Activities do not need to back up the generic completion options, which are handled by the system, but they do need to back up any custom options. You should add backup and restore logic for the fields mentioned above.
+
+Remember that your restore code should handle the case when these fields are not present, setting the fields to a suitable default value.
+
+#### Example
+
+The following code in `backup_forum_stepslib.php` lists the fields to back up:
+
+```php
+$forum = new backup_nested_element('forum', ['id'], [
+ 'type', 'name', 'intro', 'introformat',
+ 'assessed', 'assesstimestart', 'assesstimefinish', 'scale',
+ 'maxbytes', 'maxattachments', 'forcesubscribe', 'trackingtype',
+ 'rsstype', 'rssarticles', 'timemodified', 'warnafter',
+ 'blockafter', 'blockperiod', 'completiondiscussions', 'completionreplies',
+ 'completionposts',
+]);
+```
+
+As you can see, I added the **`completionposts`** field (and the others that aren't covered in this example) to the list of fields.
+
+### Add information about the completion settings to the activities cm_info object
+
+You will need to add information about the custom rules into the activities `cm_info` object by either adding, or modifying the `module_get_coursemodule_info` callback
+
+```php
+/**
+ * Add a get_coursemodule_info function in case any forum type wants to add 'extra' information
+ * for the course (see resource).
+ *
+ * Given a course_module object, this function returns any "extra" information that may be needed
+ * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
+ *
+ * @param stdClass $coursemodule The coursemodule object (record).
+ * @return cached_cm_info An object on information that the courses
+ * will know about (most noticeably, an icon).
+ */
+function forum_get_coursemodule_info($coursemodule) {
+ global $DB;
+
+ $dbparams = ['id' => $coursemodule->instance];
+ $fields = 'id, name, intro, introformat, completionposts, completiondiscussions, completionreplies, duedate, cutoffdate';
+ if (!$forum = $DB->get_record('forum', $dbparams, $fields)) {
+ return false;
+ }
+
+ $result = new cached_cm_info();
+ $result->name = $forum->name;
+
+ if ($coursemodule->showdescription) {
+ // Convert intro to html. Do not filter cached version, filters run at display time.
+ $result->content = format_module_intro('forum', $forum, $coursemodule->id, false);
+ }
+
+ // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
+ if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
+ $result->customdata['customcompletionrules']['completiondiscussions'] = $forum->completiondiscussions;
+ $result->customdata['customcompletionrules']['completionreplies'] = $forum->completionreplies;
+ $result->customdata['customcompletionrules']['completionposts'] = $forum->completionposts;
+ }
+
+ // Populate some other values that can be used in calendar or on dashboard.
+ if ($forum->duedate) {
+ $result->customdata['duedate'] = $forum->duedate;
+ }
+ if ($forum->cutoffdate) {
+ $result->customdata['cutoffdate'] = $forum->cutoffdate;
+ }
+
+ return $result;
+}
+```
+
+### Form changes for completion settings
+
+When you have custom completion conditions, you need to add controls to your module's settings form `mod_form.php` so that users can select these conditions. You can add any necessary controls.
+
+- Implement the `add_completion_rules` function which adds the form controls for your new rules.
+- Implement the `completion_rule_enabled` function which is called during form validation to check whether one of your activity's completion rules has been selected.
+- Implement other form changes if necessary to set up the form with your data. If your data is in the form of simple text boxes or dropdowns then this is not necessary, but you might want to have a checkbox that enables the rule with a separate control to set its value. This needs form tweaks.
+
+
+
+The default completion form has undergone a significant rebuild to enhance code reusability and maintainability. To prevent duplicate IDs, a suffix has been introduced to the form elements related to completion rules.
+
+:::info From Moodle 4.3 onwards
+
+Any custom completion rules added will need to use `$this->get_suffix()`.
+
+:::
+
+#### Example
+
+The forum offers a checkbox with a text input box beside it. You tick the checkbox to enable the rule, then type in the desired number of posts.
+
+First, the function that adds these controls:
+
+```php
+/**
+ * Add elements for setting the custom completion rules.
+ *
+ * @category completion
+ * @return array List of added element names, or names of wrapping group elements.
+ */
+public function add_completion_rules() {
+
+ $mform = $this->_form;
+
+ $group = [
+ $mform->createElement(
+ 'checkbox',
+ $this->get_suffixed_name('completionpostsenabled'),
+ ' ',
+ get_string('completionposts', 'forum')
+ ),
+ $mform->createElement(
+ 'text',
+ $this->get_suffixed_name('completionposts'),
+ ' ',
+ ['size' => 3]
+ ),
+ ];
+ $mform->setType('completionposts', PARAM_INT);
+ $mform->addGroup(
+ $group,
+ $this->get_suffixed_name('completionpostsgroup'),
+ get_string('completionpostsgroup','forum'),
+ [' '],
+ false
+ );
+ $mform->addHelpButton(
+ $this->get_suffixed_name('completionpostsgroup'),
+ 'completionposts',
+ 'forum'
+ );
+ $mform->disabledIf(
+ $this->get_suffixed_name('completionposts'),
+ $this->get_suffixed_name('completionpostsenabled'),
+ 'notchecked'
+ );
+
+ return [$this->get_suffixed_name('completionpostsgroup')];
+}
+
+protected function get_suffixed_name(string $fieldname): string {
+ return $fieldname . $this->get_suffix();
+}
+```
+
+- The function creates a checkbox and a text input field, which is set to accept only numbers.
+- These are grouped together so they appear on the same line, and we add a help button.
+- The text input field is disabled if the checkbox isn't ticked.
+- Note that this function must return the top-level element associated with the completion rule. (If there are multiple elements, you can return more than one.)
+ - This is used so that your controls become disabled if automatic completion is not selected.
+
+Next, a function for checking whether the user selected this option:
+
+```php
+/**
+ * Called during validation to see whether some activity-specific completion rules are selected.
+ *
+ * @param array $data Input data not yet validated.
+ * @return bool True if one or more rules is enabled, false if none are.
+ */
+public function completion_rule_enabled($data) {
+ return (!empty($data[$this->get_suffixed_name('completionpostsenabled')]) &&
+ $data[$this->get_suffixed_name('completionposts')] != 0);
+}
+```
+
+- The custom completion rule is enabled if the 'enabled' checkbox is ticked and the text field value is something other than zero.
+ - This is used to give an error if the user selects automatic completion, but fails to select any conditions.
+That's all the 'required' functions, but we need to add some extra code to support the checkbox behaviour. I overrode `get_data` so that if there is a value in the edit field, but the checkbox is not ticked, the value counts as zero (the rule will not be enabled).
+
+```php
+function get_data() {
+ $data = parent::get_data();
+ if (!$data) {
+ return $data;
+ }
+ if (!empty($data->completionunlocked)) {
+ // Turn off completion settings if the checkboxes aren't ticked.
+ $autocompletion = !empty($data->{$this->get_suffixed_name('completion')}) &&
+ $data->{$this->get_suffixed_name('completion')} == COMPLETION_TRACKING_AUTOMATIC;
+ if (empty($data->{$this->get_suffixed_name('completionpostsenabled')}) || !$autocompletion) {
+ $data->{$this->get_suffixed_name('completionposts')} = 0;
+ }
+ }
+ return $data;
+}
+```
+
+You may have noticed the `completionunlocked` check. When some users have already completed the activity, the completion settings are 'locked'; they are disabled and cannot be edited, so there will be no value set for those fields in the `$data` object. Normally this will automatically work but when dealing with checkboxes you need to include a check for the `completionunlocked` value before doing anything that would cause one of those fields to be changed in the database.
+Finally, forum already had a `data_preprocessing` function but I added code to this to set up the checkboxes when the form is displayed, and to make the default value of the text fields 1 instead of 0:
+
+```php
+function data_preprocessing(&$default_values){
+ // [Existing code, not shown]
+
+ // Set up the completion checkboxes which aren't part of standard data.
+ // We also make the default value (if you turn on the checkbox) for those
+ // numbers to be 1, this will not apply unless checkbox is ticked.
+ $default_values[$this->get_suffixed_name('completionpostsenabled')] =
+ !empty($default_values[$this->get_suffixed_name('completionposts')]) ? 1 : 0;
+ if (empty($default_values[$this->get_suffixed_name('completionposts')])) {
+ $default_values[$this->get_suffixed_name('completionposts')] = 1;
+ }
+}
+```
+
+Phew! That's the form done.
+
+### Completion state function
+
+When you create completion conditions, you need to write a function *module*`_get_completion_state` that checks the value of those conditions for a particular user.
+
+The function receives as parameters `$course`, `$cm`, and `$userid` - all self-explanatory, I hope - and `$type`. This has two values:
+
+- **`COMPLETION_AND`** - if multiple conditions are selected, the user must meet all of them.
+- **`COMPLETION_OR`** (not currently used) - if multiple conditions are selected, any one of them is good enough to complete the activity.
+Your function should return:
+- **`true`** if your custom completion options are enabled and the user meets the conditions.
+- **`false`** if your custom completion options are enabled but the user does not yet meet the conditions.
+- `$type` (not false!) if none of your custom completion options are not enabled.
+
+#### Example
+
+Here's the function for forum (simplified to include only the one completion option):
+
+```php title="mod/forum/lib.php"
+ /**
+ * Obtains the automatic completion state for this forum based on any conditions
+ * in forum settings.
+ *
+ * @param object $course Course
+ * @param object $cm Course-module
+ * @param int $userid User ID
+ * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
+ * @return bool True if completed, false if not, $type if conditions not set.
+ */
+ function forum_get_completion_state($course, $cm, $userid, $type) {
+ global $CFG,$DB;
+
+ // Get forum details
+ $forum = $DB->get_record('forum', ['id' => $cm->instance], '*', MUST_EXIST);
+
+ // If completion option is enabled, evaluate it and return true/false
+ if ($forum->completionposts) {
+ return $forum->completionposts <= $DB->get_field_sql("
+ SELECT
+ COUNT(1)
+ FROM
+ {forum_posts} fp
+ INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
+ WHERE
+ fp.userid = :userid AND fd.forum = :forumid
+ ", [
+ 'userid' => $userid,
+ 'forumid' => $forum->id,
+ ]);
+ } else {
+ // Completion option is not enabled so just return $type
+ return $type;
+ }
+ }
+```
+
+### Add function returns descriptions for the completion states
+
+When you create completion conditions, you need to write a function `[activityname]_get_completion_active_rule_descriptions` that gives a human-readable description of the completion state.
+
+The input for the method is the `cm_info` object for that activity.
+
+You need to return an array of strings for each completion rule that is active.
+
+```php
+/**
+ * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
+ *
+ * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
+ * @return array $descriptions the array of descriptions for the custom rules.
+ */
+function mod_forum_get_completion_active_rule_descriptions($cm) {
+ // Values will be present in cm_info, and we assume these are up to date.
+ if (empty($cm->customdata['customcompletionrules']) || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
+ return [];
+ }
+
+ $descriptions = [];
+ foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
+ switch ($key) {
+ case 'completiondiscussions':
+ if (!empty($val)) {
+ $descriptions[] = get_string('completiondiscussionsdesc', 'forum', $val);
+ }
+ break;
+ case 'completionreplies':
+ if (!empty($val)) {
+ $descriptions[] = get_string('completionrepliesdesc', 'forum', $val);
+ }
+ break;
+ case 'completionposts':
+ if (!empty($val)) {
+ $descriptions[] = get_string('completionpostsdesc', 'forum', $val);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return $descriptions;
+}
+```
+
+### Notifying the completion system
+
+Finally you need to notify the completion system whenever these values might have changed for a user (in the case of the forum example, whenever somebody adds or deletes a post). The completion system will end up calling the function above - but only if it needs to.
+
+- To ensure performance is not compromised, you should notify the system only when the completion state might actually have changed. Don't notify the system unless your custom completion rule is actually enabled.
+- You need to pass in the 'possible result' of the change. This is used to significantly improve performance. There are three values:
+ - **`COMPLETION_COMPLETE`** - this change will either have no effect on the user's completion state, or it will make it complete. The change cannot make a user's state *in*complete if it was complete previously. In the forum example, when you add a post, there is no way this can make the user's state incomplete, so this possible result applies.
+ - **`COMPLETION_INCOMPLETE`** - this change will either have no effect on the user's completion state, or it will make it incomplete. The change cannot make a user's state complete if it was incomplete previously. Deleting a forum post would fall into this category.
+ - **`COMPLETION_UNKNOWN`** - this change might have either effect. Using this option is much slower than the others, so try to avoid using it in anything that might happen frequently.
+- If the user whose completion state would be updated is not the current user, then the optional `$userid` parameter must be included. For example, if a teacher deletes a student's forum post, then it is the student's completion state which may need updating, not the teacher's.
+
+#### Example
+
+Here's the code that runs when somebody makes a new forum post:
+
+```php
+// Update completion state
+$completion = new completion_info($course);
+if ($completion->is_enabled($cm) && $forum->completionposts) {
+ $completion->update_state($cm, COMPLETION_COMPLETE);
+}
+```
+
+### Completion Checks in Cron Tasks
+
+If you need to check completion as part of a cron task or another part of Moodle that does not already include the completion_info class, you will need to include it.
+
+#### Example
+
+```php
+require_once($CFG->dirroot.'/lib/completionlib.php');
+```
+
+## See Also
+
+- [Activity completion and availability](https://docs.moodle.org/dev/Conditional_activities) - Original Specification
+- [Course completion](https://docs.moodle.org/dev/Course_completion) - Original Specification
+- [Policy - Retroactive effects of completion settings](https://docs.moodle.org/dev/Policy_-_Retroactive_effects_of_completion_settings)
+- [Core APIs](../../../apis.md)
+
+### User Docs
+
+- [Completion Docs](https://docs.moodle.org/en/Category:Completion)
+- [Activity Completion](https://docs.moodle.org/en/Activity_completion)
+- [Course Completion](https://docs.moodle.org/en/Course_completion)
diff --git a/versioned_docs/version-5.1/apis/core/calendar/_index/my_overview_sam_student.png b/versioned_docs/version-5.1/apis/core/calendar/_index/my_overview_sam_student.png
new file mode 100644
index 0000000000..4e33b2d305
Binary files /dev/null and b/versioned_docs/version-5.1/apis/core/calendar/_index/my_overview_sam_student.png differ
diff --git a/versioned_docs/version-5.1/apis/core/calendar/index.md b/versioned_docs/version-5.1/apis/core/calendar/index.md
new file mode 100644
index 0000000000..6fbca2ccbd
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/calendar/index.md
@@ -0,0 +1,479 @@
+---
+title: Calendar API
+tags: []
+---
+
+This page documents the Calendar API as it is in Moodle 3.3 and later. For the API in older versions of Moodle, see [Calendar API old](https://docs.moodle.org/dev/Calendar_API_old).
+
+The Calendar API allows you to add, modify and delete events in the calendar for user, groups, courses and the site. As of 3.3 it also allows you to provide actions for these events so that they are then displayed on block_myoverview, which by default is shown on users' dashboard.
+
+## Overview
+
+The Moodle [Calendar](https://docs.moodle.org/en/Calendar) collects and displays calendar events to users. These events are generated by other plugins, like activities, to let the user know of an important date. For example, when an assignment opens for submission.
+
+The block_myoverview plugin displays calendar events that have an action associated with them. For example, an activity may have a due date specified, in which case it will create a calendar action event so that the event will display on the dashboard for the user, as well as the calendar. In order to provide the action associated for this event you have to define a callback in your plugin which is detailed below.
+
+## Creating an event
+
+Creating a new calendar event. The optional parameter `$checkcapability` is used to check user's capability to add events. By default the `$checkcapability` parameter is set to true. You should set it to false if you have already checked that the user has the capabilities required for the event to be created, for example when an activity is creating an event based on a deadline.
+
+```php
+require_once($CFG->dirroot.'/calendar/lib.php');
+
+$event = new stdClass();
+$event->eventtype = SCORM_EVENT_TYPE_OPEN; // Constant defined somewhere in your code - this can be any string value you want. It is a way to identify the event.
+$event->type = CALENDAR_EVENT_TYPE_STANDARD; // This is used for events we only want to display on the calendar, and are not needed on the block_myoverview.
+$event->name = get_string('calendarstart', 'scorm', $scorm->name);
+$event->description = format_module_intro('scorm', $scorm, $cmid, false);
+$event->format = FORMAT_HTML;
+$event->courseid = $scorm->course;
+$event->groupid = 0;
+$event->userid = 0;
+$event->modulename = 'scorm';
+$event->instance = $scorm->id;
+$event->timestart = $scorm->timeopen;
+$event->visible = instance_is_visible('scorm', $scorm);
+$event->timeduration = 0;
+
+calendar_event::create($event);
+```
+
+## Updating an event
+
+You can update an existing event in database by providing at least the event id. If the event is a part of a chain of repeated events, the rest of series event will also be updated (depending on the value of property `repeateditall`). This function could also be used to insert new event to database, if the given event does not exist yet. The optional parameter `$checkcapability` is used to check user's capability to edit/add events. By default the `$checkcapability` parameter is set to true. You should set it to false if you have already checked that the user has the capabilities required for the event to be updated, for example when an activity is updating an event based on a change to it's settings.
+
+```php
+$eventid = required_param('id', PARAM_INT);
+$event = calendar_event::load($eventid);
+
+$data = $mform->get_data();
+$event->update($data);
+```
+
+## Deleting an event
+
+You can delete an existing event from the database. The optional parameter `$deleterepeated` is used as an indicator to remove the rest of repeated events. The default value for `$deleterepeated` is true. Deleting an event will also delete all associated files related to the event's editor context.
+
+```php
+$eventid = required_param('id', PARAM_INT);
+$event = calendar_event::load($eventid);
+$event->delete($repeats);
+```
+
+## Event priority
+
+There might be cases that an activity event will have user and/or group overrides. Therefore we need a way to show only the relevant event on the user's calendar. This is where the 'priority' field comes in.
+
+The event priority is set to the following:
+
+- NULL for non-override events.
+
+```php
+$event->priority = null;
+```
+
+- 0 for user override events.
+
+```php
+$event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
+```
+
+- A positive integer for group events.
+
+For integer and non-null event priorities, the lower the value, the higher the priority is. Meaning, user overrides always have a higher priority than group overrides. Group override priorities are currently being determined in two ways in core activities:
+
+1. In the assignment module, the event priorities for group overrides are being determined from the `sortorder` column in the 'assign_overrides' table.
+1. In the lesson and quiz modules, the event priorities for group overrides are being calculated using the functions `lesson_get_group_override_priorities($lessonid)` and `quiz_get_group_override_priorities($quizid)`.
+
+Should you ever decide to sort out group override priorities by implementing `*_get_group_override_priorities()`, the recommended return structure would be something like
+
+```php
+[
+ 'youreventtype1' => $prioritiesforeventtype1,
+ ...
+]
+```
+
+where '$prioritiesforeventtype1' is an associative array that has the timestamp of the group override event as key and the calculated priority as value. For more details, please see the implementation for the lesson module below:
+
+```php
+/**
+ * Calculates the priorities of timeopen and timeclose values for group overrides for a lesson.
+ *
+ * @param int $lessonid The lesson ID.
+ * @return array|null Array of group override priorities for open and close times. Null if there are no group overrides.
+ */
+function lesson_get_group_override_priorities($lessonid) {
+ global $DB;
+
+ // Fetch group overrides.
+ $where = 'lessonid = :lessonid AND groupid IS NOT NULL';
+ $params = ['lessonid' => $lessonid];
+ $overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline');
+ if (!$overrides) {
+ return null;
+ }
+
+ $grouptimeopen = [];
+ $grouptimeclose = [];
+ foreach ($overrides as $override) {
+ if ($override->available !== null && !in_array($override->available, $grouptimeopen)) {
+ $grouptimeopen[] = $override->available;
+ }
+ if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) {
+ $grouptimeclose[] = $override->deadline;
+ }
+ }
+
+ // Sort open times in ascending manner. The earlier open time gets higher priority.
+ sort($grouptimeopen);
+ // Set priorities.
+ $opengrouppriorities = [];
+ $openpriority = 1;
+ foreach ($grouptimeopen as $timeopen) {
+ $opengrouppriorities[$timeopen] = $openpriority++;
+ }
+
+ // Sort close times in descending manner. The later close time gets higher priority.
+ rsort($grouptimeclose);
+ // Set priorities.
+ $closegrouppriorities = [];
+ $closepriority = 1;
+ foreach ($grouptimeclose as $timeclose) {
+ $closegrouppriorities[$timeclose] = $closepriority++;
+ }
+
+ return [
+ 'open' => $opengrouppriorities,
+ 'close' => $closegrouppriorities
+ ];
+}
+```
+
+## Action events
+
+Action events are calendar events that can be actioned. E.g. A student submitting an assignment by a certain date. These events are displayed on the block_myoverview which by default is on users' dashboard. Creating these is the same as creating a normal calendar event except instead of using CALENDAR_EVENT_TYPE_STANDARD as your calendar event type, you use CALENDAR_EVENT_TYPE_ACTION. The events are also sorted on the dashboard by the value specified in the 'timesort' field (unixtime) for the event.
+
+Example of the changes to the above code would be to change the `type` and to specify the `timesort` value.
+
+```php
+$event->type = CALENDAR_EVENT_TYPE_ACTION;
+$event->timesort = $scorm->timeclose;
+```
+
+
+
+### The callbacks
+
+There are 3 callbacks your module can implement that are used to control when and how your action is shown to the user.
+
+#### mod_xyz_core_calendar_is_event_visible()
+
+This callback determines if an event should be visible throughout the site. For example, the assignment module creates a grading event for teachers. We do not want this event being visible to users who can not perform this action (eg. students), so we return false for those users. If you do not implement this function then the event will always be visible.
+
+```php
+/**
+ * Is the event visible?
+ *
+ * This is used to determine global visibility of an event in all places throughout Moodle. For example,
+ * the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar, and
+ * ASSIGN_EVENT_TYPE_DUE events will not be shown to teachers.
+ *
+ * @param calendar_event $event
+ * @return bool Returns true if the event is visible to the current user, false otherwise.
+ */
+function mod_assign_core_calendar_is_event_visible(calendar_event $event) {
+ global $CFG, $USER;
+
+ require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+ $cm = get_fast_modinfo($event->courseid)->instances['assign'][$event->instance];
+ $context = context_module::instance($cm->id);
+
+ $assign = new assign($context, $cm, null);
+
+ if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
+ return $assign->can_grade();
+ } else {
+ return !$assign->can_grade() && $assign->can_view_submission($USER->id);
+ }
+}
+```
+
+#### mod_xyz_core_calendar_provide_event_action()
+
+This function takes a calendar event and provides the action associated with it, or null if there is none in which case the event will not be shown in block_myoverview (but will still be shown in the calendar block). This is used by the block_myoverview plugin. If you do not implement this function then the events created by your plugin will not be shown on the block.
+
+Eg.
+
+```php
+function mod_scorm_core_calendar_provide_event_action(calendar_event $event,
+ \core_calendar\action_factory $factory) {
+ global $CFG;
+
+ require_once($CFG->dirroot . '/mod/scorm/locallib.php');
+
+ $cm = get_fast_modinfo($event->courseid)->instances['scorm'][$event->instance];
+
+ if (!empty($cm->customdata['timeclose']) && $cm->customdata['timeclose'] < time()) {
+ // The scorm has closed so the user can no longer submit anything.
+ return null;
+ }
+
+ // Restore scorm object from cached values in $cm, we only need id, timeclose and timeopen.
+ $customdata = $cm->customdata ?: [];
+ $customdata['id'] = $cm->instance;
+ $scorm = (object)($customdata + ['timeclose' => 0, 'timeopen' => 0]);
+
+ // Check that the SCORM activity is open.
+ list($actionable, $warnings) = scorm_get_availability_status($scorm);
+
+ return $factory->create_instance(
+ get_string('enter', 'scorm'),
+ new \moodle_url('/mod/scorm/view.php', array('id' => $cm->id)),
+ 1,
+ $actionable
+ );
+}
+```
+
+The variables to pass to `create_instance()` are -
+
+1. `string $name` The name of the event, for example `get_string('dosomething', 'mod_xyz')`.
+1. `\moodle_url $url` The URL the user visits in order to perform this action.
+1. `int $itemcount` This represents the number of items that require action (eg. Need to write 3 forum posts). If this is 0 then the event is not displayed.
+1. `bool $actionable` This determines if the event is currently able to be acted on. Eg. the activity may not currently be open due to date restrictions so the event is shown to the user to let them know that there is an upcoming event but the url will not be active.
+
+#### mod_xyz_core_calendar_event_action_shows_item_count()
+
+This function determines if a given event should display the number of items to action on block_myoverview. For example, if the event type is `ASSIGN_EVENT_TYPE_GRADINGDUE` then we only display the item count if there are one or more assignments to grade. If you do not implement this function then the item count is always hidden. This is usually fine as the majority of events only have an item count of '1' (eg. Submitting an assignment) and there is no need display the item count.
+
+Eg.
+
+```php
+/**
+ * Callback function that determines whether an action event should be showing its item count
+ * based on the event type and the item count.
+ *
+ * @param calendar_event $event The calendar event.
+ * @param int $itemcount The item count associated with the action event.
+ * @return bool
+ */
+function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $event, $itemcount = 0) {
+ // List of event types where the action event's item count should be shown.
+ $eventtypesshowingitemcount = [
+ ASSIGN_EVENT_TYPE_GRADINGDUE
+ ];
+ // For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count.
+ return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0;
+}
+```
+
+## Refreshing calendar events of activity modules
+
+A new ad-hoc task 'refresh_mod_calendar_events_task' has been created. This task basically loops through all of the activity modules that implement the '*_refresh_events()' hook.
+
+Sample usage:
+
+```php
+// Create the instance.
+$refreshtask = new refresh_mod_calendar_events_task();
+
+// Add custom data.
+$customdata = [
+ 'plugins' => ['assign', 'lesson', 'quiz'] // Optional. If not specified, it will refresh the events of all of the activity modules.
+];
+$refreshtask->set_custom_data($customdata);
+
+// Queue it.
+\core\task\manager::queue_adhoc_task($refreshtask);
+```
+
+## calendar_get_legacy_events()
+
+This functions accepts the same inputs as 'calendar_get_events()' but is now utilising the new Moodle Calendar API system. It respects overrides and will also add the action properties, whenever appropriate.
+
+*Note that this function will not work as expected if you pass a list of user ids as the current user session is internally used to determine which events should be visible. More info in https://moodle.atlassian.net/browse/[MDL-60340](https://moodle.atlassian.net/browse/MDL-60340)*
+
+## Changes to Behat
+
+The "And I follow "Course1"" Behat step won't work from the Dashboard anymore and has been replaced with "And I am on "Course 1" course homepage" where 'Course 1' is the fullname of the course.
+
+## Drag & drop
+
+The calendar supports dragging and dropping events within the calendar in order to change the start day for the event. Each type of calendar event can be dragged by a user with sufficient permissions to edit the event.
+
+### Dragging action events
+
+When an action event is dragged the corresponding property will also be updated in the activity instance that generated the event. For example, dragging the assignment due date event will result in the assignment activity's due date to be changed.
+
+In order to drag an action event the logged in user must have the `moodle/course:manageactivities` capability in the activity that generated the event.
+
+For an action event to be draggable the activity that generated it will need to have implemented at least one (but ideally both) callback to handle updating itself after the calendar event is dragged.
+
+#### `core_calendar_event_timestart_updated` (required)
+
+This callback is required to be implemented by any activity that wishes to have it's action events draggable in the calendar.
+
+This callback handles updating the activity instance based on the changed action event. The callback will receive the updated calendar event and the corresponding activity instance.
+
+Example:
+
+```php
+function mod_feedback_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $feedback) {
+ global $CFG, $DB;
+
+ if (empty($event->instance) || $event->modulename != 'feedback') {
+ return;
+ }
+
+ if ($event->instance != $feedback->id) {
+ return;
+ }
+
+ if (!in_array($event->eventtype, [FEEDBACK_EVENT_TYPE_OPEN, FEEDBACK_EVENT_TYPE_CLOSE])) {
+ return;
+ }
+
+ $courseid = $event->courseid;
+ $modulename = $event->modulename;
+ $instanceid = $event->instance;
+ $modified = false;
+
+ $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
+ $context = context_module::instance($coursemodule->id);
+
+ // The user does not have the capability to modify this activity.
+ if (!has_capability('moodle/course:manageactivities', $context)) {
+ return;
+ }
+
+ if ($event->eventtype == FEEDBACK_EVENT_TYPE_OPEN) {
+ // If the event is for the feedback activity opening then we should
+ // set the start time of the feedback activity to be the new start
+ // time of the event.
+ if ($feedback->timeopen != $event->timestart) {
+ $feedback->timeopen = $event->timestart;
+ $feedback->timemodified = time();
+ $modified = true;
+ }
+ } else if ($event->eventtype == FEEDBACK_EVENT_TYPE_CLOSE) {
+ // If the event is for the feedback activity closing then we should
+ // set the end time of the feedback activity to be the new start
+ // time of the event.
+ if ($feedback->timeclose != $event->timestart) {
+ $feedback->timeclose = $event->timestart;
+ $modified = true;
+ }
+ }
+
+ if ($modified) {
+ $feedback->timemodified = time();
+ $DB->update_record('feedback', $feedback);
+ $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
+ $event->trigger();
+ }
+}
+```
+
+#### `core_calendar_get_valid_event_timestart_range`
+
+This callback should calculate the minimum and maximum allowed `timestart` property for the given calendar event. This will typically be based on the properties of the activity instance, for example the `timeopen` and `timeclose` properties of the activity could form the minimum and maximum bounds, respectively.
+
+These values will be used to provide a visual indicator to the user in the calendar UI for which days are valid for the event to be dragged to. It will also be used to validate that the calendar event is being updated to a valid `timestart` value.
+
+The callback should return an array with two values, the first value representing the minimum cutoff and the second the maximum.
+
+The callback can return an array for each of the minimum and maximum cutoffs, if it has them. The array should contain the timestamp of the cutoff and an error message to be displayed to the user if they attempt to drag an event to a day that violates the limit. For example:
+
+```php
+[
+ [1505704373, 'The due date must be after the sbumission start date'], // Minimum cutoff.
+ [1506741172, 'The due date must be before the cutoff date'] // Maximum cutoff.
+]
+```
+
+If the calendar event has no limits then it should return null in for either/both of the min and max cutoff values to indicate that it isn't limited. For example:
+
+```php
+[null, null] // No limits.
+[null, [1510625037, “This is the maximum cutoff”]] // No minimum cutoff.
+[[1510625037, “This is the minimum cutoff”], null] // No maximum cutoff.
+```
+
+If the calendar event has no valid `timestart` values then the callback should return `false`. This is used to prevent the drag-and-drop of override events in activities that support them (that is Assign, Quiz).
+
+Example:
+
+```php
+function mod_feedback_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
+ $mindate = null;
+ $maxdate = null;
+
+ if ($event->eventtype == FEEDBACK_EVENT_TYPE_OPEN) {
+ // The start time of the open event can't be equal to or after the
+ // close time of the choice activity.
+ if (!empty($instance->timeclose)) {
+ $maxdate = [
+ $instance->timeclose,
+ get_string('openafterclose', 'feedback')
+ ];
+ }
+ } else if ($event->eventtype == FEEDBACK_EVENT_TYPE_CLOSE) {
+ // The start time of the close event can't be equal to or earlier than the
+ // open time of the choice activity.
+ if (!empty($instance->timeopen)) {
+ $mindate = [
+ $instance->timeopen,
+ get_string('closebeforeopen', 'feedback')
+ ];
+ }
+ }
+
+ return [$mindate, $maxdate];
+}
+```
+
+## Component events
+
+Starting from Moodle 3.9 plugins other than activity modules can create calendar events, too. These can be site-level, course category-level, course-level or user events. Events can be standard or action events.
+
+Example of creating an event:
+
+```php
+$event->component = 'tool_yourtool';
+$event->modulename = '';
+$event->eventtype = 'myeventtype';
+$event->instance = $instanceid; // Whatever instance you want.
+$event->type = CALENDAR_EVENT_TYPE_STANDARD; // Or: $event->type = CALENDAR_EVENT_TYPE_ACTION;
+// ... Other properties, see section "Create event" above.
+
+// For site events:
+$event->courseid = SITEID;
+$event->categoryid = 0;
+
+// For category events:
+$event->categoryid = $categoryid;
+$event->courseid = 0;
+
+// For course events:
+$event->courseid = $courseid;
+$event->categoryid = 0;
+
+// For user events:
+$event->courseid = 0;
+$event->categoryid = 0;
+$event->userid = $userid;
+```
+
+If this is an action event, see the "Action events" section above for supported callbacks. Note that currently category-level action events are not displayed on the timeline (but they will be displayed in the calendar). Timeline only displays the site-wide events, user events and events in the courses where user is enrolled.
+
+Events created by the plugins can not be edited or deleted by users in the calendar. Drag&drop is currently not supported for component events.
+
+To change the icon of the event, add the file `pix/myeventtype.svg` to your plugin. You can add font-awesome mapping in `_get_fontawesome_icon_map()` callback.
+
+To change the alt text for the icon add to the language file:
+
+```php
+$string['myeventtype'] = 'My event type';
+```
diff --git a/versioned_docs/version-5.1/apis/core/clock/index.md b/versioned_docs/version-5.1/apis/core/clock/index.md
new file mode 100644
index 0000000000..db805fda40
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/clock/index.md
@@ -0,0 +1,215 @@
+---
+title: Clock
+tags:
+ - Time
+ - PSR-20
+ - PSR
+ - Unit testing
+ - Testing
+description: Fetching the current time
+---
+
+
+
+Moodle supports use of a [PSR-20](https://php-fig.org/psr/psr-20/) compatible Clock interface, which should be accessed using Dependency Injection.
+
+This should be used instead of `time()` to fetch the current time. This allows unit tests to mock time and therefore to test a variety of cases such as events happening at the same time, or setting an explicit time.
+
+:::tip Recommended usage
+
+We recommend that the Clock Interface is used consistently in your code instead of using the standard `time()` method.
+
+:::
+
+## Usage
+
+The usage of the Clock extends the PSR-20 Clock Interface and adds a new convenience method, `\core\clock::time(): int`, to simplify replacement of the global `time()` method.
+
+### Usage in standard classes
+
+Where the calling code is not instantiated via Dependency Injection itself, the simplest way to fetch the clock is using `\core\di::get(\core\clock::class)`, for example:
+
+```php title="Usage in legacy code"
+$clock = \core\di::get(\core\clock::class);
+
+// Fetch the current time as a \DateTimeImmutable.
+$clock->now();
+
+// Fetch the current time as a Unix Time Stamp.
+$clock->time();
+```
+
+### Usage via Constructor Injection
+
+The recommended approach is to have the Dependency Injector inject into the constructor of a class.
+
+```php title="Usage in injected classes"
+namespace mod_example;
+
+class post {
+ public function __construct(
+ protected readonly \core\clock $clock,
+ protected readonly \moodle_database $db,
+ )
+
+ public function create_thing(\stdClass $data): \stdClass {
+ $data->timecreated = $this->clock->time();
+
+ $data->id = $this->db->insert_record('example_thing', $data);
+
+ return $data;
+ }
+}
+```
+
+When using DI to fetch the class, the dependencies will automatically added to the constructor arguments:
+
+```php title="Obtaining the injected class"
+$post = \core\di::get(post::class);
+```
+
+## Unit testing
+
+One of the most useful benefits to making consistent use of the Clock interface is to mock data within unit tests.
+
+When testing code which makes use of the Clock interface, you can replace the standard system clock implementation with a testing clock which suits your needs.
+
+:::tip Container Reset
+
+The DI container is automatically reset at the end of every test, which ensures that your clock does not bleed into subsequent tests.
+
+:::
+
+Moodle provides two standard test clocks, but you are welcome to create any other, as long as it implements the `\core\clock` interface.
+
+:::warning
+
+When mocking the clock, you _must_ do so _before_ fetching your service.
+
+Any injected value within your service will persist for the lifetime of that service.
+
+Replacing the clock after fetching your service will have *no* effect.
+
+:::
+
+### Incrementing clock
+
+The incrementing clock increases the time by one second every time it is called. It can also be instantiated with a specific start time if preferred.
+
+A helper method, `mock_clock_with_incrementing(?int $starttime = null): \core\clock`, is provided within the standard testcase:
+
+```php title="Obtaining the incrementing clock"
+class my_test extends \advanced_testcase {
+ public function test_create_thing(): void {
+ // This class inserts data into the database.
+ $this->resetAfterTest(true);
+
+ $clock = $this->mock_clock_with_incrementing();
+
+ $post = \core\di::get(post::class);
+ $posta = $post->create_thing((object) [
+ 'name' => 'a',
+ ]);
+ $postb = $post->create_thing((object) [
+ 'name' => 'a',
+ ]);
+
+ // The incrementing clock automatically advanced by one second each time it is called.
+ $this->assertGreaterThan($postb->timecreated, $posta->timecreated);
+ $this->assertLessThan($clock->time(), $postb->timecreated);
+ }
+}
+```
+
+It is also possible to specify a start time for the clock;
+
+```php title="Setting the start time"
+$clock = $this->mock_clock_with_incrementing(12345678);
+```
+
+### Frozen clock
+
+The frozen clock uses a time which does not change, unless manually set. This can be useful when testing code which must handle time-based resolutions.
+
+A helper method, `mock_clock_with_frozen(?int $time = null): \core\clock`, is provided within the standard testcase:
+
+```php title="Obtaining and using the frozen clock"
+class my_test extends \advanced_testcase {
+ public function test_create_thing(): void {
+ // This class inserts data into the database.
+ $this->resetAfterTest(true);
+
+ $clock = $this->mock_clock_with_frozen();
+
+ $post = \core\di::get(post::class);
+ $posta = $post->create_thing((object) [
+ 'name' => 'a',
+ ]);
+ $postb = $post->create_thing((object) [
+ 'name' => 'a',
+ ]);
+
+ // The frozen clock keeps the same time.
+ $this->assertEquals($postb->timecreated, $posta->timecreated);
+ $this->assertEquals($clock->time(), $postb->timecreated);
+
+ // The time can be manually set.
+ $clock->set_to(12345678);
+ $postc = $post->create_thing((object) [
+ 'name' => 'a',
+ ]);
+
+ // The frozen clock keeps the same time.
+ $this->assertEquals(12345678, $postc->timecreated);
+
+ // And can also be bumped.
+ $clock->set_to(0);
+ $this->assertEquals(0, $clock->time());
+
+ // Bump the current time by 1 second.
+ $clock->bump();
+ $this->assertEquals(1, $clock->time());
+
+ // Bump by 4 seconds.
+ $clock->bump(4);
+ $this->assertEquals(5, $clock->time());
+ }
+}
+```
+
+### Custom clock
+
+If the standard cases are not suitable for you, then you can create a custom clock and inject it into the DI container.
+
+```php title="Creating a custom clock"
+class my_clock implements \core\clock {
+ public int $time;
+
+ public function __construct() {
+ $this->time = time();
+ }
+
+ public function now(): \DateTimeImmutable {
+ $time = new \DateTimeImmutable('@' . $this->time);
+ $this->time = $this->time += 5;
+
+ return $time;
+ }
+
+ public function time(): int {
+ return $this->now()->getTimestamp();
+ }
+}
+
+class my_test extends \advanced_testcase {
+ public function test_my_thing(): void {
+ $clock = new my_clock();
+ \core\di:set(\core\clock::class, $clock);
+
+ $post = \core\di::get(post::class);
+ $posta = $post->create_thing((object) [
+ 'name' => 'a',
+ ]);
+ }
+}
+```
diff --git a/versioned_docs/version-5.1/apis/core/conditionalactivities/index.md b/versioned_docs/version-5.1/apis/core/conditionalactivities/index.md
new file mode 100644
index 0000000000..eac7de5ed4
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/conditionalactivities/index.md
@@ -0,0 +1,220 @@
+---
+title: Conditional activities API
+tags: []
+documentationDraft: true
+---
+
+The Conditional Activities API allowsyou to specify whether to hide, or show, an activity when a series of conditions associated with it are met.
+
+:::note
+
+This should not be confused with the [completion API](../activitycompletion/index.md) which is used to mark if an activity is completed or not. The Conditional Activities API is used to handle the _availability_ of an activity, whilst the Completion API helps to track the _progress_ of student in an activity.
+
+:::
+
+## Files
+
+The main file containing all key functions is located at `lib/conditionlib.php`..
+
+## Functions and Examples
+
+The class `condition_info` defined in `lib/conditionlib.php` is the main conditional API in Moodle. Following are some important methods of the above mentioned class.
+
+```php
+fill_availability_conditions($cm)
+get_full_course_module()
+add_completion_condition($cmid, $requiredcompletion)
+add_grade_condition($gradeitemid, $min, $max, $updateinmemory = false)
+wipe_conditions()
+get_full_information($modinfo = null)
+is_available($information, $grabthelot = false, $userid = 0, $modinfo = null)
+show_availability()
+update_cm_from_form($cm, $fromform, $wipefirst=true)
+```
+
+The basic functionality of these methods can be classified as:-
+
+1. Fetching information related to conditions
+1. Adding/Updating conditional clauses to activities
+1. Deleting conditions attached to activities
+
+### Fetching information related to conditions
+
+The following functions are normally used to fetch information regarding conditions associated with activities:
+
+```php
+get_full_course_module();
+get_full_information($modinfo=null);
+is_available($information, $grabthelot = false, $userid = 0, $modinfo = null);
+show_availability();
+```
+
+#### get_full_course_module()
+
+This method can fetches and returns all necessary information as a course module object which are required to determine the availability conditions.
+
+Example:-
+
+```php
+$cm->id = $id;
+$test = new condition_info($cm, CONDITION_MISSING_EVERYTHING);
+$fullcm = $test->get_full_course_module();
+```
+
+#### get_full_information()
+
+This function returns a string which describes the various conditions in place for the activity in the given context.Some possible outputs can be:-
+
+```php
+ a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact)
+ b) From 14 Oct until 12:11 on 17 Oct (midnight, exact)
+ c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct)
+```
+
+Please refer to the inline documentation in the code for detailed explanation of the logic and all possible cases.
+
+Example:-
+
+```php
+$ci = new condition_info($mod);
+$fullinfo = $ci->get_full_information();
+```
+
+#### is_available()
+
+This function is used to check if a given course module is currently available or not. A thing worth noting is that this doesn't take "visibility settings" and `viewhiddenactivities` capability into account. That is these settings should be properly checked after the result of is_available(), before dumping any data to the user.
+
+Example:-
+
+```php
+$ci = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING);
+$bool = $ci->is_available($text, false, 0);
+```
+
+#### show_availability()
+
+This function is used to check if information regarding availability of the current module should be shown to the user or not.
+
+Example:-
+
+```php
+$ci = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING);
+$bool = $ci->show_availability();
+```
+
+### Adding/Updating conditional clauses to activities
+
+```php
+fill_availability_conditions($cm);
+add_completion_condition($cmid, $requiredcompletion);
+add_grade_condition($gradeitemid, $min, $max, $updateinmemory = false);
+update_cm_from_form($cm, $fromform, $wipefirst = true)
+```
+
+#### fill_availability_conditions()
+
+This function adds any extra availability conditions to given course module object.
+
+```php
+$rawmods = get_course_mods($courseid);
+if (empty($rawmods)) {
+ die;
+}
+if ($sections = $DB->get_records("course_sections", ["course" => $courseid], "section ASC")) {
+ foreach ($sections as $section) {
+ if (!empty($section->sequence)) {
+ $sequence = explode(",", $section->sequence);
+ foreach ($sequence as $seq) {
+ if (empty($rawmods[$seq])) {
+ continue;
+ }
+ if (!empty($CFG->enableavailability)) {
+ condition_info::fill_availability_conditions($rawmods[$seq]);
+ // Do something.
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+#### add_completion_condition()
+
+In Moodle availability condition of a Module or activity can depend on another activity. For example activity B will not be unlocked until activity A is successfully completed. To add such inter-dependent conditions, this function is used.
+
+Example:-
+
+```php
+$test1 = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING);
+$test1->add_completion_condition(13, 3);
+```
+
+#### add_grade_condition()
+
+This function is used to add a grade related restriction to an activity based on the grade secured in another activity. In the following example a minimum grade of 0.4 is required on gradeitem 666 to unlock the current activity in context.
+
+Example:-
+
+```php
+$test1 = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING);
+$test1->add_grade_condition(666, 0.4, null, true);
+```
+
+#### update_cm_from_form()
+
+This function is used to update availability conditions from a user submitted form.
+
+Example:-
+
+```php
+$fromform = $mform->get_data();
+if (!empty($fromform->update)) {
+ if (!empty($course->groupmodeforce) or !isset($fromform->groupmode)) {
+ $fromform->groupmode = $cm->groupmode; // Keep the original.
+ }
+
+ // update course module first
+ $cm->groupmode = $fromform->groupmode;
+ $cm->groupingid = $fromform->groupingid;
+ $cm->groupmembersonly = $fromform->groupmembersonly;
+
+ $completion = new completion_info($course);
+ if ($completion->is_enabled()) {
+ // Update completion settings.
+ $cm->completion = $fromform->completion;
+ $cm->completiongradeitemnumber = $fromform->completiongradeitemnumber;
+ $cm->completionview = $fromform->completionview;
+ $cm->completionexpected = $fromform->completionexpected;
+ }
+ if (!empty($CFG->enableavailability)) {
+ $cm->availablefrom = $fromform->availablefrom;
+ $cm->availableuntil = $fromform->availableuntil;
+ $cm->showavailability = $fromform->showavailability;
+ condition_info::update_cm_from_form($cm,$fromform,true);
+ }
+ // Do something else with the data.
+}
+```
+
+### Deleting conditions attached to activities
+
+we have a simple function wipe_conditions() that can erase all conditions associated with the current activity.
+consider an example:-
+
+```php
+$ci = new condition_info($cm, CONDITION_MISSING_EVERYTHING, false);
+$ci->wipe_conditions();
+```
+
+## See Also
+
+- [Conditional activities Adding module support](https://docs.moodle.org/dev/Conditional_activities_Adding_module_support)
+- [Conditional activities](https://docs.moodle.org/dev/Conditional_activities) - original specification for this feature.
+
+### User documentation
+
+- [How to make a new availability condition plugin](../../plugintypes/availability/index.md).
+- [Conditional Activities](https://docs.moodle.org/en/Conditional_activities)
+- [Conditional Activities Settings](https://docs.moodle.org/en/Conditional_activities_settings)
+- [Using Conditional Activities](https://docs.moodle.org/en/Using_Conditional_activities)
diff --git a/versioned_docs/version-5.1/apis/core/customfields/index.md b/versioned_docs/version-5.1/apis/core/customfields/index.md
new file mode 100644
index 0000000000..6a2e7742c2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/customfields/index.md
@@ -0,0 +1,172 @@
+---
+title: Custom fields API
+tags:
+ - customfield
+ - Custom field
+---
+
+
+
+## Custom fields API overview
+
+Custom fields API allows to configure custom fields that can be added to various contexts. Each **component** (or plugin) that wants to use custom fields can define several **areas**.
+
+:::info Example
+The `core_course` component defines an area `course` that allows to add custom fields to the courses. The same component can define another area `coursecat` that will allow to add custom fields to the course categories.
+:::
+
+Inside each area, the component/plugin can decide whether to use or not to use **itemid**.
+
+:::info Example
+Course custom fields are the same throughout the system and they don't use `itemid` (it is always 0). But there could be an activity module that would want to configure different custom fields for each individual instance of module. Then this module would use the module id as the `itemid`, as in ([example](https://github.com/marinaglancy/moodle-mod_surveybuilder)). This would allow to create modules similar to `mod_data` and `mod_feedback` where each instance has its own set of fields.
+:::
+
+New plugin type `customfield` was also added as part of the Custom fields API. Additional types of custom fields can be installed into `/customfield/field/`.
+
+## How to use custom fields
+
+Component/plugin that uses custom fields must define a **handler class** for each area and a **configuration page**. Handler class must be called `/customfield/_handler` and be placed in autoloaded location `/classes/customfield/_handler.php`. This class must extend **\core_customfield\handler** . Configuration page may be located anywhere. For course custom fields configuration the admin settings page is used [/course/customfield.php](https://github.com/moodle/moodle/blob/main/course/customfield.php). If the area uses `itemid` this page should take `itemid` as a parameter.
+
+Handler has protected constructor, to get a handler call `create()` method. Some areas may choose to return a singleton here:
+
+```php
+$handler = HANDLERCLASS::create($itemid);
+```
+
+Configuration page contents will be:
+
+```php
+$output = $PAGE->get_renderer('core_customfield');
+$outputpage = new \core_customfield\output\management($handler);
+echo $output->render($outputpage);
+```
+
+Handler must implement all abstract methods (calculate configuration or instance context, check permissions to configure, view or edit) and also may choose to overwrite:
+
+```php
+handler::uses_categories()
+handler::generate_category_name()
+handler::config_form_definition() // For example, the course_handler adds "locked" and "visibility" settings that control who can edit or view the particular field.
+handler::setup_edit_page() // Sets page context/url/breadcrumb for the customfield/edit.php page, in some cases it must be overridden.
+```
+
+### Add custom fields to the instance edit form
+
+Custom fields are added to the **instances**. For example, course custom fields are added to the courses, so `courseid` is the `instanceid`. In the example of [`mod_surveybuilder`](https://github.com/marinaglancy/moodle-mod_surveybuilder) we use `$USER->id` as the `instanceid` (which means that in this example one user can fill the survey in one module only once). In each case of using custom fields there should be a clear concept of an **instance**. `Instanceid` is required to save the data but it may be empty when we render the instance edit form (for example, the course is not yet created).
+
+Developer must add custom field callbacks to the instance edit form. If the instance is "made up" (like in `mod_surveybuilder`), a new form has to be created with `id` field in it that will refer to the `instanceid`.
+
+The following callbacks should be used in `form definition`, `definition_after_data`, `validation` and `after form submission`:
+
+```php
+$handler->instance_form_definition($mform)
+$handler->instance_form_before_set_data()
+$handler->instance_form_definition_after_data()
+$handler->instance_form_validation()
+$handler->instance_form_save($data)
+```
+
+The `instance_form_save()` method must be called after the form was saved as the `$data` parameter must have the `id` attribute.
+
+On deletion of an instance or on deletion of the whole item call:
+
+```php
+$handler->delete_instance()
+$handler->delete_all()
+```
+
+### Retrieving instances custom fields
+
+How custom fields are used depends entirely on the situation.
+
+```php title="Handler methods to retrieve custom fields values for the given instance(s)"
+$handler->export_instance_data()
+$handler->export_instance_data_object()
+$handler->display_custom_fields_data()
+```
+
+Additional methods for advanced usage:
+
+```php
+$handler->get_instance_data()
+$handler->get_instances_data()
+$handler->get_instance_data_for_backup()
+```
+
+Method `restore_instance_data_from_backup()` exists in the handler class but is not implemented.
+
+```php title="To retrieve the list of custom fields used in the given component/area/itemid"
+$handler->get_categories_with_fields()
+$handler->get_fields()
+```
+
+:::note
+The list of fields is cached in the handler and these two functions can be called multiple times.
+:::
+
+```php title="Example code for course custom fields. This function will return all the custom fields for a given courseid"
+function get_course_metadata($courseid) {
+ $handler = \core_customfield\handler::get_handler('core_course', 'course');
+ // This is equivalent to the line above.
+ //$handler = \core_course\customfield\course_handler::create();
+ $datas = $handler->get_instance_data($courseid);
+ $metadata = [];
+ foreach ($datas as $data) {
+ if (empty($data->get_value())) {
+ continue;
+ }
+ $cat = $data->get_field()->get_category()->get('name');
+ $metadata[$data->get_field()->get('shortname')] = $cat . ': ' . $data->get_value();
+ }
+ return $metadata;
+}
+```
+
+### Privacy API
+
+Custom fields API does not export or delete any data because it does not know how custom fields are used, what data is considered user data and if it is considered private or shared data.
+
+```php title="Plugins that store user information in custom fields should link subsystem in their get_metadata"
+$collection->link_subsystem('core_customfield', 'privacy:metadata:customfieldpurpose');
+```
+
+```php title="They can use the following methods in the export/delete functions"
+use core_customfield\privacy\provider as customfield_provider;
+
+customfield_provider::get_customfields_data_contexts()
+customfield_provider::export_customfields_data()
+customfield_provider::delete_customfields_data()
+customfield_provider::delete_customfields_data_for_context()
+```
+
+In case when custom fields configuration is considered to be user data (configuration means the definition of the fields, not the instance data), there are also couple of methods to help with privacy API implementations:
+
+```php
+customfield_provider::get_customfields_configuration_contexts()
+customfield_provider::delete_customfields_configuration()
+customfield_provider::delete_customfields_configuration_for_context()
+```
+
+:::info
+Export of configuration was not yet implemented at the time of writing this because of difficult implementation and very unclear use case. If it is needed please feel free to contribute to Moodle.
+:::
+
+## Custom fields plugins
+
+Custom fields plugin type was added to allow implement different types of custom fields (somehow similar to user profile fields plugin type). Plugins are located in `/customfield/field/` and the full frankenstyle name of the plugins start with `customfield_`.
+
+Except for common [Plugin files](../../commonfiles/index.mdx) and tests the following classes must be present in `customfield` plugins (in respective autoloaded locations):
+
+```php
+namespace customfield_;
+class field_controller extends \core_customfield\field_controller;
+class data_controller extends \core_customfield\data_controller;
+
+namespace customfield_\privacy;
+class provider implements \core_privacy\local\metadata\null_provider, \core_customfield\privacy\customfield_provider;
+```
+
+## See also
+
+- [MDL-64626](https://moodle.atlassian.net/browse/MDL-64626) - Custom fields API (Moodle 3.7+) implementations and improvements
+- [MDL-57898](https://moodle.atlassian.net/browse/MDL-57898) - Add custom field types plugin and course custom fields functionality
diff --git a/versioned_docs/version-5.1/apis/core/deprecation/index.md b/versioned_docs/version-5.1/apis/core/deprecation/index.md
new file mode 100644
index 0000000000..314604cf16
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/deprecation/index.md
@@ -0,0 +1,111 @@
+---
+title: Deprecation API
+tags:
+- deprecation
+---
+
+
+
+
+
+When deprecating a code feature, it is often desirable to include a reasonable amount of information, and to provide a consistent deprecation message.
+
+In some cases it is also desirable to check if a called class, or method, has been marked as deprecated.
+
+One way to simplify this is through use of the `\core\attribute\deprecated` PHP attribute, which can be used in conjunction with the `\core\deprecation` class.
+
+:::note
+
+Please note that the attribute does _not_ replace the `@deprecated` phpdoc annotation. They serve slightly different purposes.
+
+:::
+
+The attribute can be used to specify information including:
+
+- the replacement for that feature
+- the version that the feature was deprecated in
+- the relevant MDL
+- the reason for deprecation
+- whether the deprecation is final
+
+## The `deprecated` attribute
+
+The attribute is a Moodle PHP Attribute and can be applied to:
+
+- classes, traits, interfaces, and enums
+- enum cases
+- global functions
+- class constants, properties, and methods
+
+```php title="Example attribute usage"
+// On a global function:
+#[\core\attribute\deprecated('random_bytes', since: '4.3')]
+function random_bytes_emulate($length) {
+ // Replaced by random_bytes since Moodle 4.3.
+}
+
+// On a class:
+#[\core\attribute\deprecated(replacement: null, since: '4.4', reason: 'This functionality has been removed.')]
+class example {
+ #[\core\attribute\deprecated(
+ replacement: '\core\example::do_something',
+ since: '4.3',
+ reason: 'No longer required',
+ mdl: 'MDL-12345',
+ )]
+ public function do_something(): void {}
+}
+
+// On an enum case:
+enum example {
+ #[\core\attribute\deprecated('example::OPTION', since: '4.4', final: true)]
+ case OPTION;
+}
+```
+
+## Inspecting the attribute
+
+The `\core\deprecation` class contains helper methods to inspect for use of the deprecated attribute and allows usage including:
+
+- checking if a feature is deprecated
+- emitting a deprecation notice if a feature is deprecated
+- fetching an instance of the attribute to query further
+
+```php title="Examples of usage"
+// A method which has been initially deprecated, and replaced by 'random_bytes'. It should show debugging.
+/** @deprecated since 4.3 */
+#[\core\attribute\deprecated('random_bytes', since: '4.3')]
+function random_bytes_emulate($length) {
+ \core\deprecation::emit_deprecation_if_present(__FUNCTION__);
+ return random_bytes($length);
+}
+
+// A method which has been finally deprecated and should throw an exception.
+/** @deprecated since 2.7 */
+#[\core\attribute\deprecated(replacement: 'Events API', since: '2.3', final: true)]
+function add_to_log() {
+ \core\deprecation::emit_deprecation_if_present(__FUNCTION__);
+}
+
+// Checking when an enum case is deprecated:
+\core\deprecation::is_deprecated(\core\param::RAW); // Returns false.
+\core\deprecation::is_deprecated(\core\param::INTEGER); // Returns true.
+
+// On an collection of items.
+foreach ($values as $value) {
+ \core\deprecation::emit_deprecation_if_present($value);
+ $value->do_things();
+}
+
+// Checking if a class is deprecated:
+\core\deprecation::is_deprecated(\core\task\manager::class); // Returns false.
+
+// Checking if an instantiated class is deprecated:
+\core\deprecation::is_deprecated(new \moodle_url('/example/'));
+
+// Checking if a class method is deprecated:
+\core\deprecation::is_deprecated([\moodle_url::class, 'out']);
+\core\deprecation::is_deprecated([new \moodle_url('/example/'), 'out']);
+```
+
+This functionality is intended to simplify deprecation of features such as constants, enums, and related items which are called from centralised APIs and difficult to detect as deprecated.
diff --git a/versioned_docs/version-5.1/apis/core/di/index.md b/versioned_docs/version-5.1/apis/core/di/index.md
new file mode 100644
index 0000000000..3e16c7c58c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/di/index.md
@@ -0,0 +1,229 @@
+---
+title: Dependency Injection
+tags:
+ - DI
+ - Container
+ - PSR-11
+ - PSR
+description: The use of PSR-11 compatible Dependency Injection in Moodle
+---
+
+
+
+Moodle supports the use of [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible Dependency Injection, accessed using the `\core\di` class, which internally makes use of [PHP-DI](https://php-di.org).
+
+Most class instances can be fetched using their class name without any manual configuration. Support for configuration of constructor arguments is also possible, but is generally discouraged.
+
+Dependencies are stored using a string id attribute, which is typically the class or interface name of the dependency. Use of other arbitrary id values is strongly discouraged.
+
+## Fetching dependencies
+
+When accessing dependencies within a class, it is advisable to inject them into the constructor, for example:
+
+```php title="Fetching a instance of the \core\http_client class from within a class"
+class my_thing {
+ public function __construct(
+ protected readonly \core\http_client $client,
+ ) {
+ }
+}
+```
+
+For legacy code, or for scripts accessing an injected class, Moodle provides a wrapper around the PSR-11 Container implementation which can be used to fetch dependencies:
+
+```php title="Fetching dependencies using the DI container"
+// Fetching an instance of the \core\http_client class outside of a class.
+$client = \core\di::get(\core\http_client::class);
+
+// Fetching an instance of a class which is managed using DI.
+$thing = \core\di::get(my_thing::class);
+```
+
+:::tip Constructor Property Promotion and Readonly properties
+
+When using constructor-based injection, you can simplify your dependency injection by making use of [Constructor Property Promotion](https://stitcher.io/blog/constructor-promotion-in-php-8), and [Readonly properties](https://stitcher.io/blog/php-81-readonly-properties).
+
+The use of readonly properties is also highly recommended as it ensures that dependencies cannot be inadvertently changed.
+
+These language features are available in all Moodle versions supporting Dependency Injection.
+
+```php
+class example_without_promotion {
+ protected \core\http_client $client;
+
+ public function __construct(
+ \core\http_client $client,
+ ) {
+ $this->client = $client;
+ }
+}
+
+class example_with_promotion {
+ public function __construct(
+ protected readonly \core\http_client $client,
+ ) {
+ }
+}
+```
+
+:::
+
+## Configuring dependencies
+
+In some rare cases you may need to supply additional configuration for a dependency to work properly. This is usually in the case of legacy code, and can be achieved with the `\core\hook\di_configuration` hook.
+
+
+
+
+
+The callback must be linked to the hook by specifying a callback in the plugin's `hooks.php` file:
+
+```php title="mod/example/db/hooks.php"
+ \core\hook\di_configuration::class,
+ 'callback' => \mod_example\hook_listener::class . '::inject_dependenices',
+ ],
+];
+```
+
+
+
+
+
+The hook listener consists of a static method on a class.
+
+```php title="mod/example/classes/hook_listener.php"
+add_definition(
+ id: complex_client::class,
+ definition: function (
+ \moodle_database $db,
+ ): complex_client {
+ global $CFG;
+
+ return new complex_client(
+ db: $db,
+ name: $CFG->some_value,
+ );
+ }
+ )
+ }
+}
+```
+
+
+
+
+
+## Mocking dependencies in Unit Tests
+
+One of the most convenient features of Dependency Injection is the ability to provide a mocked version of the dependency during unit testing.
+
+Moodle resets the Dependency Injection Container between each unit test, which means that little-to-no cleanup is required.
+
+```php title="Injecting a Mocked dependency"
+ 'Colin'])),
+ ]));
+
+ // Inject the mock.
+ \core\di::set(
+ \core\http_client::class,
+ new http_client(['handler' => $handlerstack]),
+ );
+
+ // Call a method on the example class.
+ // This method uses \core\di to fetch the client and use it to fetch data.
+ $example = \core\di::get(example::class);
+ $result = $example->do_the_thing();
+
+ // The result will be based on the mock response.
+ $this->assertEquals('Colin', $result->get_name());
+ }
+}
+```
+
+## Injecting dependencies
+
+Dependencies can be usually be easily injected into classes which are themselves loaded using Dependency Injection.
+
+In most cases in Moodle, this should be via the class constructor, for example:
+
+```php title="Injecting via the constructor"
+class thing_manager {
+ public function __construct(
+ protected readonly \moodle_database $db,
+ ) {
+ }
+
+ public function get_things(): array {
+ return $this->db->get_records('example_things');
+ }
+}
+
+// Fetching the injected class from legacy code:
+$manager = \core\di::get(thing_manager::class);
+$things = $manager->get_things();
+
+// Using it in a child class:
+class other_thing {
+ public function __construct(
+ protected readonly thing_manager $manager,
+ ) {
+ }
+
+ public function manage_things(): void {
+ $this->manager->get_things();
+ }
+}
+```
+
+:::warning A note on injecting the Container
+
+It is generally inadvisable to inject the Container itself. Please do not inject the `\Psr\Container\ContainerInterface`.
+
+:::
+
+## Advanced usage
+
+All usage of the Container _should_ be via `\core\di`, which is a wrapper around the currently-active Container implementation. In normal circumstances it is not necessary to access the underlying Container implementation directly and such usage is generally discouraged.
+
+### Resetting the Container
+
+The Container is normally instantiated during the bootstrap phase of a script. In normal use it is not reset and there should be no need to reset it, however it is _possible_ to reset it if required. This usage is intended to be used for situations such as Unit Testing.
+
+:::tip Unit testing
+
+The container is already reset after each test when running unit tests. It is not necessary nor recommended to so manually.
+
+:::
+
+```php title="Resetting the Container"
+\core\di::reset_container();
+```
+
+:::danger
+
+Resetting an actively-used container can lead to unintended consequences.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png b/versioned_docs/version-5.1/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png
new file mode 100644
index 0000000000..4b1c683898
Binary files /dev/null and b/versioned_docs/version-5.1/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png differ
diff --git a/versioned_docs/version-5.1/apis/core/dml/_exceptions/Dml_exceptions.png b/versioned_docs/version-5.1/apis/core/dml/_exceptions/Dml_exceptions.png
new file mode 100644
index 0000000000..304093c414
Binary files /dev/null and b/versioned_docs/version-5.1/apis/core/dml/_exceptions/Dml_exceptions.png differ
diff --git a/versioned_docs/version-5.1/apis/core/dml/database-schema.md b/versioned_docs/version-5.1/apis/core/dml/database-schema.md
new file mode 100644
index 0000000000..834c03f675
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/dml/database-schema.md
@@ -0,0 +1,89 @@
+---
+title: Database schema
+tags:
+ - Database
+ - DB
+ - XMLDB
+---
+
+
+
+The files available here are in [DBDesigner4](http://fabforce.net/dbdesigner4/) format. DBDesigner4 is a schema drawing program released under GPL.
+
+The database schemas have been put together by Alberto Giampani, Dario Toledo, Isaac Cueto and Marcus Green. We are also discussing a means of creating them automatically from the new XMLDB definitions with an XML transform.
+
+Discussion of the database schemas happens either in the forum of the relevant module, or in the [General developer forum](https://moodle.org/mod/forum/view.php?id=55).
+
+## Moodle 4.0
+
+- [Web view of the Moodle 4.00 schema](http://www.examulator.com/er/4.0)
+
+## Moodle 3.11
+
+- [Web view of the Moodle 3.11 schema](http://www.examulator.com/er/3.11)
+
+## Moodle 3.10
+
+- [Web view of the Moodle 3.10 schema](http://www.examulator.com/er/3.10)
+
+## Moodle 3.9
+
+- [Web view of the Moodle 3.9 schema](http://www.examulator.com/er/3.9)
+
+## Moodle 3.7
+
+- [Web view of the Moodle 3.7 schema](https://www.examulator.com/er/3.7)
+
+## Moodle 3.6
+
+- [Web view of the Moodle 3.6 schema](https://www.examulator.com/er/3.6)
+
+## Moodle 3.5
+
+- [ER Diagram Moodle 3.5 schema](https://www.examulator.com/er/3.5)
+- [Downloadable Moodle 3.5 schema](https://www.examulator.com/er/3.5/moodle_35_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 3.4
+
+- [ER Diagram Moodle 3.4 schema](https://www.examulator.com/er/3.4)
+- [Downloadable Moodle 3.4 schema](https://www.examulator.com/er/3.4/moodle_34_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 3.3
+
+- [ER Diagram Moodle 3.3 schema](https://www.examulator.com/er/3.3)
+- [Downloadable Moodle 3.3 schema](https://www.examulator.com/er/3.3/moodle_33_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 3.2
+
+- [ER Diagram Moodle 3.2 schema](https://www.examulator.com/er/3.2)
+- [Downloadable Moodle 3.2 schema](https://www.examulator.com/er/3.2/moodle_32_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 3.1
+
+- [ER Diagram Moodle 3.1 schema](https://www.examulator.com/er/3.1)
+- [Downloadable Moodle 3.1 schema](https://www.examulator.com/er/3.1/moodle_31_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 3.0
+
+- [ER Diagram Moodle 3.0 schema](https://www.examulator.com/er/3.0)
+- [Downloadable Moodle 3.0 schema](https://www.examulator.com/er/3.0/moodle30_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 2.9
+
+- [ER Diagram Moodle 2.9 schema](https://www.examulator.com/er/2.9)
+- [Downloadable Moodle 2.9 schema](https://www.examulator.com/er/2.9/moodle29_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 2.8
+
+- [ER Diagram Moodle 2.8 schema](https://www.examulator.com/er/2.8)
+- [Downloadable Moodle 2.8 schema](https://www.examulator.com/er/2.8/moodle28_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 2.7
+
+- [ER Diagram Moodle 2.7 schema](https://www.examulator.com/er/2.7)
+- [Downloadable Moodle 2.7 schema](https://www.examulator.com/er/2.7/moodle2.7_erd.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
+
+## Moodle 2.2
+
+- [ER Diagram Moodle 2.2 schema](https://www.examulator.com/er/2.2)
+- [Downloadable Moodle 2.2 schema](https://www.examulator.com/er/2.2/moodle22.mwb) opened by [MYSQL Workbench](http://www.mysql.com/products/workbench)
diff --git a/versioned_docs/version-5.1/apis/core/dml/ddl.md b/versioned_docs/version-5.1/apis/core/dml/ddl.md
new file mode 100644
index 0000000000..6e1b866b6f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/dml/ddl.md
@@ -0,0 +1,131 @@
+---
+title: Data definition API
+tags:
+ - DB
+ - XMLDB
+ - API
+ - core_dml
+ - ddl
+ - core
+---
+
+
+
+In this page you'll access to the available functions under Moodle to be able to handle DB structures (tables, fields, indexes...).
+
+The objective is to have a well-defined group of functions able to handle all the DB structure (DDL statements) using one neutral description, being able to execute the correct SQL statements required by each RDBMS. All these functions are used **[exclusively by the installation and upgrade processes](https://docs.moodle.org/dev/Installing_and_upgrading_plugin_database_tables)**.
+
+In this page you'll see a complete list of such functions, with some explanations, tricks and examples of their use. If you are interested, it's also highly recommendable to take a look to the [DML functions page](./ddl.md) where everything about how to handle DB data (select, insert, update, delete i.e. DML statements) is defined.
+
+Of course, feel free to clarify, complete and add more info to all this documentation. It will be welcome, absolutely!
+
+## Main info
+
+- All the function calls in this page are public methods of the **database manager**, accessible from the $DB global object. You will need to "import" it within the upgrade function of your **upgrade.php** main function using the `global` keyword, for example:
+
+```php
+function xmldb_xxxx_upgrade {
+ global $DB;
+
+ // Load the DDL manager and xmldb API.
+ $dbman = $DB->get_manager();
+
+ // Your upgrade code goes here
+}
+```
+
+- All of the `$table`, `$field`, and `$index` parameters are XMLDB objects. Don't forget to read carefully the complete documentation about [creating new DDL functions](https://docs.moodle.org/dev/XMLDB_creating_new_DDL_functions) before playing with these functions. Everything is explained there, with one general example and some really useful tricks to improve the use of all the functions detailed below.
+- If you want real examples of the usage of these functions we recommend examining the various core **upgrade.php** scripts.
+
+:::tip
+
+Always use the [XMLDB Editor](/general/development/tools/xmldb) to modify your tables. It is capable of generating the PHP code required to make your definition changes.
+
+:::
+
+:::danger
+
+The use of these functions is **restricted** to the upgrade processes and it should not be used in any other parts of Moodle.
+
+:::
+
+## The functions
+
+### Handling tables
+
+```php
+// Detect if a table exists.
+$dbman->table_exists($table)
+
+// Create a table.
+$dbman->create_table($table, $continue = true, $feedback = true)
+
+// Drop a table.
+$dbman->drop_table($table, $continue = true, $feedback = true)
+
+// Rename a table.
+$dbman->rename_table($table, $newname, $continue = true, $feedback = true)
+```
+
+### Handling fields
+
+```php
+// Detect if a field exists.
+$dbman->field_exists($table, $field)
+
+// Create a field.
+$dbman->add_field($table, $field, $continue = true, $feedback = true)
+
+// Drop a field.
+$dbman->drop_field($table, $field, $continue = true, $feedback = true)
+
+// Change the type of a field.
+$dbman->change_field_type($table, $field, $continue = true, $feedback = true)
+
+// Change the precision of a field.
+$dbman->change_field_precision($table, $field, $continue = true, $feedback = true)
+
+// Change the signed/unsigned status of a field.
+$dbman->change_field_unsigned($table, $field, $continue = true, $feedback = true)
+
+// Make a field nullable or not.
+$dbman->change_field_notnull($table, $field, $continue = true, $feedback = true)
+
+// Change the default value of a field.
+$dbman->change_field_default($table, $field, $continue = true, $feedback = true)
+
+// Rename a field.
+$dbman->rename_field($table, $field, $newname, $continue = true, $feedback = true)
+```
+
+### Handling indexes
+
+```php
+// Detect if an index exists.
+$dbman->index_exists($table, $index)
+
+// Return the name of an index in DB.
+$dbman->find_index_name($table, $index)
+
+// Add an index.
+$dbman->add_index($table, $index, $continue = true, $feedback = true)
+
+// Drop an index.
+$dbman->drop_index($table, $index, $continue = true, $feedback = true)
+```
+
+## Some considerations
+
+1. The `$table`, `$field`, and `$index` parameters are, always, XMLDB objects.
+1. The `$newtablename`, and `$newfieldname` parameters are, always, simple strings.
+1. All the `*_exists()` functions always return a boolean value.
+1. If any issue is encountered during execution of these functions, an Exception will be thrown and the upgrade process will stop.
+1. Always use the [XMLDB Editor](/general/development/tools/xmldb) to generate the PHP code automatically.
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [XMLDB Documentation](https://docs.moodle.org/dev/XMLDB_Documentation): Main page of the whole XMLDB documentation, where all the process is defined and all the related information resides.
+- [XMLDB Defining one XML structure](https://docs.moodle.org/dev/XMLDB_Defining_one_XML_structure): Where you will know a bit more about the underlying XML structure used to define the DB objects, that is used continuously by the functions described in this page.
+- [Installing and upgrading plugin DB tables](https://docs.moodle.org/dev/Installing_and_upgrading_plugin_database_tables)
+- [DML functions](./index.md): Where all the functions used to handle DB data ([DML](https://docs.moodle.org/wikipedia/Data_Manipulation_Language)) are defined.
diff --git a/versioned_docs/version-5.1/apis/core/dml/delegated-transactions.md b/versioned_docs/version-5.1/apis/core/dml/delegated-transactions.md
new file mode 100644
index 0000000000..2dc3d7a03c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/dml/delegated-transactions.md
@@ -0,0 +1,78 @@
+---
+title: Transactions
+tags:
+ - core_dml
+ - DML
+ - core
+ - API
+---
+
+Moodle allows data manipulation to take place within a database transaction, known as a *Delegated transaction*. This allows you to perform CRUD[^1] operations, and roll them back if a failure takes place.
+
+## General principles
+
+1. These **delegated transactions** work in a way that, when nested, the outer levels have control over the inner ones.
+2. Code should **not** rely on a rollback happening. It is only a measure to reduce (not to eliminate) DB[^2] garbled information
+3. Any code using transactions that result in unfinished, unbalanced, or finished twice transactions will generate a `transaction_exception` and the DB will perform a rollback
+4. If one transaction (at any level) has been marked for `rollback()` there will not be any method to change it. Finally Moodle will perform the DB rollback
+5. If one transaction (at any level) has been marked for `allow_commit()` it will be possible to change that status to `rollback()` in any outer level
+6. It will be **optional** to catch exceptions when using transactions, but if they are caught, then it is mandatory to mark the transaction for `rollback()`
+7. Any explicit `rollback()` call will pass the exception originating from it, as in `rollback($exception)`, to be re-thrown
+
+## The API
+
+1. All the handling must go, exclusively, to a `moodle_database` object, leaving real drivers only implementing (protected) the old begin/commit/rollback_sql() functions
+2. One array of objects of type `moodle_transaction` will be stored / checked from `$DB`
+3. `$DB` will be the responsible to instantiate / accumulate / pair / compare `moodle_transaction`s
+4. Each `moodle_transaction` will be able to set the global mark for rollback. Commit won't change anything
+5. Inner-most commit/rollback will printout one complete stack of `moodle_transaction`s information if we are under `DEBUG_DEVELOPER` and the new setting `delegatedtransactionsdebug` is enabled
+6. Normal usage of the moodle_transaction will be:
+
+ ```php
+ $transaction = $DB->start_delegated_transaction();
+ // Perform some $DB stuff
+ $transaction->allow_commit();
+ ```
+
+7. If, for any reason, the developer needs to catch exceptions when using transactions, it will be mandatory to use it in this way:
+
+ ```php
+ try {
+ $transaction = $DB->start_delegated_transaction();
+ // Perform some $DB stuff.
+ $transaction->allow_commit();
+ } catch (Exception $e) {
+ // Extra cleanup steps.
+ // Re-throw exception after commiting.
+ $transaction->rollback($e);
+ }
+ ```
+
+8. In order to be able to keep some parts of code out from top transactions completely, if we know it can lead to problems, we can use:
+
+ ```php
+ // Check to confirm we aren't using transactions at this point.
+ // This will throw an exception if a transaction is found.
+ $DB->transactions_forbidden();
+ ```
+
+## The Flow
+
+
+
+1. Any default exception handler will:
+ 1. Catch uncaught transaction_exception exceptions
+ 2. Properly perform the DB rollback
+ 3. debug/error/log honouring related settings
+ 4. inform with as many details as possible (token, place... whatever)
+2. Any "footer" (meaning some place before ending `` output) will:
+ 1. Detect "in-transaction" status
+ 2. Let execution continue, transaction is automatically rolled back in `$DB->dispose()`
+ 3. inform with as many details as possible (token, place... whatever)
+3. `$DB->dispose()` will:
+ 1. Detect "in-transaction" status
+ 2. log error (not possible to honour settings!)
+ 3. Properly perform the full DB rollback
+
+[^1]: Create Read Update Delete
+[^2]: The Moodle database
diff --git a/versioned_docs/version-5.1/apis/core/dml/drivers.md b/versioned_docs/version-5.1/apis/core/dml/drivers.md
new file mode 100644
index 0000000000..efc34f1f6a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/dml/drivers.md
@@ -0,0 +1,47 @@
+---
+title: DML drivers
+tags:
+ - core_dml
+ - DML
+ - core
+ - API
+ - RDBM
+---
+
+A number of native drivers are included with Moodle, including those with support for:
+
+- MySQLi
+- MariaDB
+- PostgreSQL
+- Oracle
+- Microsoft SQL Server
+
+These drivers are supported using DML Database Driver layer, which has a number of discreet benefits:
+
+- more optimised and probably faster
+- consume less memory
+- better possibility to improve logging, debugging, profiling, etc.
+- less code, easier to fix and maintain
+- and more
+
+## Query logging
+
+The native DML drivers support logging of database queries to database table, which can be enabled in config.php:
+
+```php title="config.php"
+$CFG->dboptions = [
+ 'dbpersist' => 0,
+ //'logall' => true,
+ 'logslow' => 5,
+ 'logerrors' => true,
+];
+```
+
+- `logall` - log all queries - suitable only for developers, causes high server loads
+- `logslow` - log queries that take longer than specified number of seconds (float values are accepted)
+- `logerrors` - log all error queries
+
+## See also
+
+- [DML functions](./index.md): Where all the functions used to handle DB data ([DML](https://en.wikipedia.org/wiki/Data_Manipulation_Language)) are defined.
+- [DML exceptions](./exceptions.md): New DML code is throwing exceptions instead of returning false if anything goes wrong
diff --git a/versioned_docs/version-5.1/apis/core/dml/exceptions.md b/versioned_docs/version-5.1/apis/core/dml/exceptions.md
new file mode 100644
index 0000000000..f0b6a9b3c8
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/dml/exceptions.md
@@ -0,0 +1,33 @@
+---
+title: DML exceptions
+tags:
+ - core_dml
+ - DB
+ - API
+ - core
+ - exception
+---
+
+The [DML](./index.md) API uses a selection of exceptions to indicate errors.
+
+## Types of exception
+
+
+
+### dml_connection_exception
+
+Thrown when can not connect to database for any reason.
+
+### dml_read_exception
+
+Problem occurred during reading from database. Originally indicated be returning *false* - this value was often confused with *false* return value meaning *not found*.
+
+### dml_write_exception
+
+Problem occurred during writing to database. Originally indicated be returning *false*.
+
+## See also
+
+- [Exceptions](https://docs.moodle.org/dev/Exceptions): General guidelines for using of exceptions in Moodle 2.0
+- [DML functions](./index.md): Where all the functions used to handle DB data. ([DML](https://en.wikipedia.org/wiki/Data_manipulation_language)) are defined.
+- [DDL functions](./ddl.md): Where all the functions used to handle DB objects ([DDL](https://en.wikipedia.org/wiki/Data_Definition_Language)) are defined.
diff --git a/versioned_docs/version-5.1/apis/core/dml/index.md b/versioned_docs/version-5.1/apis/core/dml/index.md
new file mode 100644
index 0000000000..9666b7b177
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/dml/index.md
@@ -0,0 +1,1127 @@
+---
+title: Data manipulation API
+tags:
+ - DB
+ - XMLDB
+ - API
+ - core
+ - core_dml
+---
+
+This page describes the functions available to access data in the Moodle database. You should **exclusively** use these functions in order to retrieve or modify database content because these functions provide a high level of abstraction and guarantee that your database manipulation will work against different RDBMSes.
+
+Where possible, tricks and examples will be documented here in order to make developers' lives a bit easier. Of course, feel free to clarify, complete and add more information to this documentation. It will be welcome, absolutely!
+
+## General concepts
+
+### DB object
+
+- The data manipulation API is exposed via public methods of the $DB object.
+- Moodle core takes care of setting up the connection to the database according to values specified in the main config.php file.
+- The $DB global object is an instance of the moodle_database class. It is instantiated automatically during the bootstrap setup, i.e. as a part of including the main config.php file.
+- The DB object is available in the global scope right after including the config.php file:
+
+```php title="example.php"
+mdl_. This prefix is NOT to be used in the code itself.
+- All the `$table` parameters in the functions are meant to be the table name without prefixes:
+
+```php
+$user = $DB->get_record('user', ['id' => '1']);
+```
+
+- In custom SQL queries, table names must be enclosed between curly braces. They will be then automatically converted to the real prefixed table name. There is no need to access $CFG->prefix
+
+```php
+$user = $DB->get_record_sql('SELECT COUNT(*) FROM {user} WHERE deleted = 1 OR suspended = 1;');
+```
+
+### Conditions
+
+- All the `$conditions` parameters in the functions are arrays of `fieldname => fieldvalue` elements.
+- They all must be fulfilled - that is the logical AND is used to populate the actual WHERE statement
+
+```php
+$user = $DB->get_record('user', ['firstname' => 'Martin', 'lastname' => 'Dougiamas']);
+```
+
+### Placeholders
+
+- All the `$params` parameters in the functions are arrays of values used to fill placeholders in SQL statements.
+- Placeholders help to avoid problems with SQL-injection and/or invalid quotes in SQL queries. They facilitate secure and cross-db compatible code.
+- Two types of placeholders are supported - question marks (SQL_PARAMS_QM) and named placeholders (SQL_PARAMS_NAMED).
+- Named params **must be unique** even if the value passed is the same. If you need to pass the same value multiple times, you need to have multiple distinct named parameters.
+
+```php title="Example of using question-mark placeholders"
+$DB->get_record_sql(
+ 'SELECT * FROM {user} WHERE firstname = ? AND lastname = ?',
+ [
+ 'Martin',
+ 'Dougiamas',
+ ]
+);
+```
+
+```php title="Example of using named placeholders"
+$DB->get_record_sql(
+ 'SELECT * FROM {user} WHERE firstname = :firstname AND lastname = :lastname',
+ [
+ 'firstname' => 'Martin',
+ 'lastname' => 'Dougiamas',
+ ]
+);
+```
+
+### Strictness
+
+Some methods accept the $strictness parameter affecting the method behaviour. Supported modes are specified using the constants:
+
+- MUST_EXIST - In this mode, the requested record must exist and must be unique. An exception `dml_missing_record_exception` will be thrown if no record is found or `dml_multiple_records_exception` if multiple matching records are found.
+- IGNORE_MISSING - In this mode, a missing record is not an error. False boolean is returned if the requested record is not found. If more records are found, a debugging message is displayed.
+- IGNORE_MULTIPLE - This is not a recommended mode. The function will silently ignore multiple records found and will return just the first one of them.
+
+## Getting a single record
+
+### get_record
+
+Return a single database record as an object where all the given conditions are met.
+
+```php
+public function get_record(
+ $table,
+ array $conditions,
+ $fields = '*',
+ $strictness = IGNORE_MISSING
+);
+```
+
+### get_record_select
+
+Return a single database record as an object where the given conditions are used in the WHERE clause.
+
+```php
+public function get_record_select(
+ $table,
+ $select,
+ array $params = null,
+ $fields = '*',
+ $strictness = IGNORE_MISSING
+);
+```
+
+### get_record_sql
+
+Return a single database record as an object using a custom SELECT query.
+
+```php
+public function get_record_sql(
+ $sql,
+ array $params = null,
+ $strictness = IGNORE_MISSING
+);
+```
+
+## Getting a hashed array of records
+
+Each of the following methods return an array of objects. The array is indexed by the first column of the fields returned by the query. To assure consistency, it is a good practice to ensure that your query include an "id column" as the first field. When designing custom tables, make id their first column and primary key.
+
+### get_records
+
+Return a list of records as an array of objects where all the given conditions are met.
+
+```php
+public function get_records(
+ $table,
+ array $conditions = null,
+ $sort = '',
+ $fields = '*',
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_records_select
+
+Return a list of records as an array of objects where the given conditions are used in the WHERE clause.
+
+```php
+public function get_records_select(
+ $table,
+ $select,
+ array $params = null,
+ $sort = '',
+ $fields = '*',
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+The `$fields` parameter is a comma separated list of fields to return (optional, by default all fields are returned).
+
+### get_records_sql
+
+Return a list of records as an array of objects using a custom SELECT query.
+
+```php
+public function get_records_sql(
+ $sql,
+ array $params = null,
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_records_list
+
+Return a list of records as an array of objects where the given field matches one of the possible values.
+
+```php
+public function get_records_list(
+ $table,
+ $field,
+ array $values,
+ $sort = *,
+ $fields = '*',
+ $limitfrom = *,
+ $limitnum = ''
+)
+```
+
+## Getting data as key/value pairs in an associative array
+
+### get_records_menu
+
+Return the first two columns from a list of records as an associative array where all the given conditions are met.
+
+```php
+public function get_records_menu(
+ $table,
+ array $conditions = null,
+ $sort = '',
+ $fields = '*',
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_records_select_menu
+
+Return the first two columns from a list of records as an associative array where the given conditions are used in the WHERE clause.
+
+```php
+public function get_records_select_menu(
+ $table,
+ $select,
+ array $params = null,
+ $sort = '',
+ $fields = '*',
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_records_sql_menu
+
+Return the first two columns from a number of records as an associative array using a custom SELECT query.
+
+```php
+public function get_records_sql_menu(
+ $sql,
+ array $params = null,
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+## Counting records that match the given criteria
+
+### count_records
+
+Count the records in a table where all the given conditions are met.
+
+```php
+public function count_records(
+ $table,
+ array $conditions = null
+);
+```
+
+### count_records_select
+
+Count the records in a table where the given conditions are used in the WHERE clause.
+
+```php
+public function count_records_select(
+ $table,
+ $select,
+ array $params = null,
+ $countitem = "COUNT('x')"
+);
+```
+
+### count_records_sql
+
+Counting the records using a custom SELECT COUNT(...) query.
+
+```php
+public function count_records_sql(
+ $sql,
+ array $params = null
+);
+```
+
+## Checking if a given record exists
+
+### record_exists
+
+Test whether a record exists in a table where all the given conditions are met.
+
+```php
+public function record_exists(
+ $table,
+ array $conditions = null
+);
+```
+
+### record_exists_select
+
+Test whether any records exists in a table where the given conditions are used in the WHERE clause.
+
+```php
+public function record_exists_select(
+ $table,
+ $select,
+ array $params = null
+);
+```
+
+### record_exists_sql
+
+Test whether the given SELECT query would return any record.
+
+```php
+public function record_exists_sql(
+ $sql,
+ array $params = null
+);
+```
+
+## Getting a particular field value from one record
+
+### get_field
+
+Get a single field value from a table record where all the given conditions are met.
+
+```php
+public function get_field(
+ $table,
+ $return,
+ array $conditions,
+ $strictness = IGNORE_MISSING
+);
+```
+
+### get_field_select
+
+Get a single field value from a table record where the given conditions are used in the WHERE clause.
+
+```php
+public function get_field_select(
+ $table,
+ $return,
+ $select,
+ array $params = null,
+ $strictness = IGNORE_MISSING
+);
+```
+
+### get_field_sql
+
+Get a single field value (first field) using a custom SELECT query.
+
+```php
+public function get_field_sql(
+ $sql,
+ array $params = null,
+ $strictness = IGNORE_MISSING
+);
+```
+
+## Getting field values from multiple records
+
+### get_fieldset
+
+Return values of the given field from a table record as an array where all the given conditions are met.
+
+```php
+public function get_fieldset(
+ string $table,
+ string $return,
+ ?array $conditions = null
+);
+```
+
+### get_fieldset_select
+
+Return values of the given field as an array where the given conditions are used in the WHERE clause.
+
+```php
+public function get_fieldset_select(
+ $table,
+ $return,
+ $select,
+ array $params = null
+);
+```
+
+### get_fieldset_sql
+
+Return values of the first column as an array using a custom SELECT field FROM ... query.
+
+```php
+public function get_fieldset_sql(
+ $sql,
+ array $params = null
+);
+```
+
+## Setting a field value
+
+### set_field
+
+Set a single field in every record where all the given conditions are met.
+
+```php
+public function set_field(
+ $table,
+ $newfield,
+ $newvalue,
+ array $conditions = null
+);
+```
+
+### set_field_select
+
+Set a single field in every table record where the given conditions are used in the WHERE clause.
+
+```php
+public function set_field_select(
+ $table,
+ $newfield,
+ $newvalue,
+ $select,
+ array $params = null
+);
+```
+
+## Deleting records
+
+### delete_records
+
+Delete records from the table where all the given conditions are met.
+
+```php
+public function delete_records(
+ $table,
+ array $conditions = null
+);
+```
+
+### delete_records_select
+
+Delete records from the table where the given conditions are used in the WHERE clause.
+
+```php
+public function delete_records_select(
+ $table,
+ $select,
+ array $params = null
+);
+```
+
+## Inserting records
+
+### insert_record
+
+Insert the given data object into the table and return the "id" of the newly created record.
+
+```php
+public function insert_record(
+ $table,
+ $dataobject,
+ $returnid = true,
+ $bulk = false
+);
+```
+
+### insert_records
+
+Insert multiple records into the table as fast as possible. Records are inserted in the given order, but the operation is not atomic. Use transactions if necessary.
+
+```php
+public function insert_records(
+ $table,
+ $dataobjects
+);
+```
+
+### insert_record_raw
+
+For rare cases when you also need to specify the ID of the record to be inserted.
+
+## Updating records
+
+### update_record
+
+Update a record in the table. The data object must have the property "id" set.
+
+```php
+public function update_record(
+ $table,
+ $dataobject,
+ $bulk = false
+);
+```
+
+## Executing a custom query
+
+### execute
+
+- If you need to perform a complex update using arbitrary SQL, you can use the low level "execute" method. Only use this when no specialised method exists.
+
+```php
+public function execute(
+ $sql,
+ array $params = null
+);
+```
+
+:::danger
+
+Do NOT use this to make changes in database structure, use the `database_manager` methods instead.
+
+:::
+
+## Using recordsets
+
+If the number of records to be retrieved from DB is high, the 'get_records_xxx() functions above are far from optimal, because they load all the records into the memory via the returned array. Under those circumstances, it is highly recommended to use these `get_recordset_xxx()` functions instead. They return an iterator to iterate over all the found records and save a lot of memory.
+
+It is **absolutely important** to not forget to close the returned recordset iterator after using it. This is to free up a lot of resources in the RDBMS.
+
+A general way to iterate over records using the `get_recordset_xxx()` functions:
+
+```php
+$rs = $DB->get_recordset(....);
+foreach ($rs as $record) {
+ // Do whatever you want with this record
+}
+$rs->close();
+```
+
+Unlike get_record functions, you cannot check if $rs = = true or !empty($rs) to determine if any records were found. Instead, if you need to, you can use:
+
+```php
+if ($rs->valid()) {
+ // The recordset contains some records.
+}
+```
+
+### get_recordset
+
+Return a list of records as a moodle_recordset where all the given conditions are met.
+
+```php
+public function get_recordset(
+ $table,
+ array $conditions = null,
+ $sort = '',
+ $fields = '*',
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_recordset_select
+
+Return a list of records as a moodle_recordset where the given conditions are used in the WHERE clause.
+
+```php
+public function get_recordset_select(
+ $table,
+ $select,
+ array $params = null,
+ $sort = '',
+ $fields = '*',
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_recordset_sql
+
+Return a list of records as a moodle_recordset using a custom SELECT query.
+
+```php
+public function get_recordset_sql(
+ $sql,
+ array $params = null,
+ $limitfrom = 0,
+ $limitnum = 0
+);
+```
+
+### get_recordset_list
+
+Return a list of records as a moodle_recordset where the given field matches one of the possible values.
+
+```php
+public function get_recordset_list(
+ $table,
+ $field,
+ array $values,
+ $sort = *,
+ $fields = '*',
+ $limitfrom = *,
+ $limitnum = ''
+);
+```
+
+## Delegated transactions
+
+- Please note some databases do not support transactions (such as the MyISAM MySQL database engine), however all server administrators are strongly encouraged to migrate to databases that support transactions (such as the InnoDB MySQL database engine).
+- Previous versions supported only one level of transaction. Since Moodle 2.0, the DML layer emulates delegated transactions that allow nesting of transactions.
+- Some subsystems (such as messaging) do not support transactions because it is not possible to rollback in external systems.
+A transaction is started by:
+
+```php
+$transaction = $DB->start_delegated_transaction();
+```
+
+and finished by:
+
+```php
+$transaction->allow_commit();
+```
+
+Usually a transaction is rolled back when an exception is thrown:
+
+```php
+$transaction->rollback($ex);
+```
+
+which must be used very carefully because it might break compatibility with databases that do not support transactions. Transactions cannot be used as part of expected code flow; they can be used only as an emergency protection of data consistency.
+
+::: More information
+
+For more information see [DB layer 2.0 delegated transactions](https://docs.moodle.org/dev/DB_layer_2.0_delegated_transactions) or [MDL-20625](https://moodle.atlassian.net/browse/MDL-20625).
+
+:::
+
+### Example
+
+```php
+global $DB;
+try {
+ $transaction = $DB->start_delegated_transaction();
+ $DB->insert_record('foo', $object);
+ $DB->insert_record('bar', $otherobject);
+
+ // Assuming the both inserts work, we get to the following line.
+ $transaction->allow_commit();
+
+} catch (Exception $e) {
+ $transaction->rollback($e);
+}
+```
+
+## Cross-DB compatibility
+
+Moodle supports several SQL servers, including MySQL, MariaDB, PostgreSQL, MS-SQL and Oracle. These may have specific syntax in certain cases. In order to achieve cross-db compatibility of the code, the following functions must be used to generate the fragments of the query valid for the actual SQL server.
+
+### sql_bitand
+
+Return the SQL text to be used in order to perform a bitwise AND operation between 2 integers.
+
+```php
+public function sql_bitand(
+ $int1,
+ $int2
+);
+```
+
+### sql_bitnot
+
+Return the SQL text to be used in order to perform a bitwise NOT operation on the given integer.
+
+```php
+public function sql_bitnot(
+ $int1
+);
+```
+
+### sql_bitor
+
+Return the SQL text to be used in order to perform a bitwise OR operation between 2 integers.
+
+```php
+public function sql_bitor(
+ $int1,
+ $int2
+);
+```
+
+### sql_bitxor
+
+Return the SQL text to be used in order to perform a bitwise XOR operation between 2 integers.
+
+```php
+public function sql_bitxor(
+ $int1,
+ $int2
+);
+```
+
+### sql_null_from_clause
+
+Return an empty FROM clause required by some DBs in all SELECT statements.
+
+```php
+public function sql_null_from_clause()
+```
+
+### sql_ceil
+
+Return the correct CEIL expression applied to the given fieldname.
+
+```php
+public function sql_ceil(
+ $fieldname
+);
+```
+
+### sql_equal
+
+
+
+Return the query fragment to perform cross-db varchar comparisons when case-sensitiveness is important.
+
+```php
+public function sql_equal(
+ $fieldname,
+ $param,
+ $casesensitive = true,
+ $accentsensitive = true,
+ $notequal = false
+);
+```
+
+### sql_like
+
+Return the query fragment to perform the LIKE comparison.
+
+```php
+$DB->sql_like(
+ $fieldname,
+ $param,
+ $casesensitive = true,
+ $accentsensitive = true,
+ $notlike = false,
+ $escapechar = ' \\ '
+);
+```
+
+```php title='Example: Searching for records partially matching the given hard-coded literal'
+$likeidnumber = $DB->sql_like('idnumber', ':idnum');
+$DB->get_records_sql(
+ "SELECT id, fullname FROM {course} WHERE {$likeidnumber}",
+ [
+ 'idnum' => 'DEMO-%',
+ ]
+);
+```
+
+See below if you need to compare with a value submitted by the user.
+
+### sql_like_escape
+
+Escape the value submitted by the user so that it can be used for partial comparison and the special characters like '_' or '%' behave as literal characters, not wildcards.
+
+```php
+$DB->sql_like_escape(
+ $text,
+ $escapechar = '\\'
+);
+```
+
+```php title="Example: If you need to perform a partial comparison with a value that has been submitted by the user"
+$search = required_param('search', PARAM_RAW);
+
+$likefullname = $DB->sql_like('fullname', ':fullname');
+$DB->get_records_sql(
+ "SELECT id, fullname FROM {course} WHERE {$likefullname}",
+ [
+ 'fullname' => '%' . $DB->sql_like_escape($search) . '%',
+ ]
+);
+```
+
+### sql_length
+
+Return the query fragment to be used to calculate the length of the expression in characters.
+
+```php
+public function sql_length(
+ $fieldname
+);
+```
+
+### sql_modulo
+
+Return the query fragment to be used to calculate the remainder after division.
+
+```php
+public function sql_modulo(
+ $int1,
+ $int2
+);
+```
+
+### sql_position
+
+Return the query fragment for searching a string for the location of a substring. If both needle and haystack use placeholders, you must use named placeholders.
+
+```php
+public function sql_position(
+ $needle,
+ $haystack
+);
+```
+
+### sql_substr
+
+Return the query fragment for extracting a substring from the given expression.
+
+```php
+public function sql_substr(
+ $expr,
+ $start,
+ $length = false
+);
+```
+
+### sql_cast_char2int
+
+Return the query fragment to cast a CHAR column to INTEGER
+
+```php
+public function sql_cast_char2int(
+ $fieldname,
+ $text = false
+);
+```
+
+### sql_cast_char2real
+
+Return the query fragment to cast a CHAR column to REAL (float) number
+
+```php
+public function sql_cast_char2real(
+ $fieldname,
+ $text = false
+);
+```
+
+### sql_cast_to_char
+
+
+
+Return SQL for casting to char of given field/expression.
+
+```php
+public function sql_cast_to_char(string $field);
+```
+
+### sql_compare_text
+
+Return the query fragment to be used when comparing a TEXT (clob) column with a given string or a VARCHAR field (some RDBMs do not allow for direct comparison).
+
+```php
+public function sql_compare_text(
+ $fieldname,
+ $numchars = 32
+);
+```
+
+```php title="Example"
+$comparedescription = $DB->sql_compare_text('description');
+$comparedescriptionplaceholder = $DB->sql_compare_text(':description');
+$todogroups = $DB->get_records_sql(
+ "SELECT id FROM {group} WHERE {$comparedescription} = {$comparedescriptionplaceholder}",
+ [
+ 'description' => 'TODO',
+ ]
+);
+```
+
+### sql_order_by_text
+
+Return the query fragment to be used to get records ordered by a TEXT (clob) column. Note this affects the performance badly and should be avoided if possible.
+
+```php
+public function sql_order_by_text(
+ $fieldname,
+ $numchars = 32
+);
+```
+
+### sql_order_by_null
+
+
+
+Return the query fragment to be used to get records with a standardised return pattern of null values across database types to sort nulls first when ascending and last when descending.
+
+```php
+public function sql_order_by_null(
+ string $fieldname,
+ int $sort = SORT_ASC
+);
+```
+
+### sql_concat
+
+Return the query fragment to concatenate all given parameters into one string.
+
+```php
+public function sql_concat(...)
+```
+
+There is a gotcha if you are trying to concat fields which may be null which result in the entire result being null:
+
+
+
+```php
+public function sql_concat('requiredfield', 'optionalfield');
+```
+
+
+
+You must cast or coalesce every nullable argument, for example:
+
+
+
+```php
+public function sql_concat('requiredfield', "COALESCE(optionalfield, '')");
+```
+
+
+
+### sql_group_concat
+
+
+
+Return SQL for performing group concatenation on given field/expression.
+
+```php
+public function sql_group_concat(string $field, string $separator = ', ', string $sort = '')
+```
+
+### sql_concat_join
+
+Return the query fragment to concatenate all given elements into one string using the given separator.
+
+```php
+public function sql_concat_join(
+ $separator = "' '",
+ $elements = []]
+);
+```
+
+### sql_fullname
+
+Return the query fragment to concatenate the given $firstname and $lastname
+
+```php
+public function sql_fullname(
+ $first = 'firstname',
+ $last = 'lastname'
+);
+```
+
+### sql_isempty
+
+Return the query fragment to check if the field is empty
+
+```php
+public function sql_isempty(
+ $tablename,
+ $fieldname,
+ $nullablefield,
+ $textfield
+);
+```
+
+### sql_isnotempty
+
+Return the query fragment to check if the field is not empty
+
+```php
+public function sql_isnotempty(
+ $tablename,
+ $fieldname,
+ $nullablefield,
+ $textfield
+);
+```
+
+### get_in_or_equal
+
+Return the query fragment to check if a value is IN the given list of items (with a fallback to plain equal comparison if there is just one item)
+
+```php
+public function get_in_or_equal(
+ $items,
+ $type = SQL_PARAMS_QM,
+ $prefix = 'param',
+ $equal = true,
+ $onemptyitems = false
+);
+```
+
+Example:
+
+```php
+$statuses = ['todo', 'open', 'inprogress', 'intesting'];
+[$insql, $inparams] = $DB->get_in_or_equal($statuses);
+$sql = "SELECT * FROM {bugtracker_issues} WHERE status $insql";
+$bugs = $DB->get_records_sql($sql, $inparams);
+```
+
+An example using named params:
+
+```php
+[$insql, $params] = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
+$contextsql = "AND rc.contextid {$insql}";
+```
+
+### sql_regex_supported
+
+Does the current database driver support regex syntax when searching?
+
+```php
+public function sql_regex_supported()
+```
+
+### sql_regex
+
+Return the query fragment to perform a regex search.
+
+```php
+public function sql_regex(
+ $positivematch = true,
+ $casesensitive = false
+);
+```
+
+Example: Searching for Page module instances containing links.
+
+```php title="Example: Searching for Page module instances containing links."
+if ($DB->sql_regex_supported()) {
+ $select = 'content ' . $DB->sql_regex() . ' :pattern';
+ $params = ['pattern' => "(src|data)\ * = \ *[\\\"\']https?://"]
+} else {
+ $select = $DB->sql_like('content', ':pattern', false);
+ $params = ['pattern' => '% = %http%://%'];
+}
+
+$pages = $DB->get_records_select('page', $select, $params, 'course', 'id, course, name');
+```
+
+### sql_regex_get_word_beginning_boundary_marker
+
+
+
+Return the word-beginning boundary marker if the current database driver supports regex syntax when searching.
+
+Defaults to `[[:<:]]`. On MySQL `v8.0.4+`, it returns `\\b`.
+
+```php
+public function sql_regex_get_word_beginning_boundary_marker()
+```
+
+### sql_regex_get_word_end_boundary_marker
+
+
+
+Return the word-end boundary marker if the current database driver supports regex syntax when searching.
+
+Defaults to `[[:>:]]`. On MySQL `v8.0.4+`, it returns `\\b`.
+
+```php
+public function sql_regex_get_word_end_boundary_marker()
+```
+
+### sql_intersect
+
+
+
+Return the query fragment that allows to find intersection of two or more queries
+
+```php
+public function sql_intersect(
+ $selects,
+ $fields
+);
+```
+
+## Debugging
+
+### set_debug
+
+You can enable a debugging mode to make $DB output the SQL of every executed query, along with some timing information. This can be useful when debugging your code. Obviously, all such calls should be removed before code is submitted for integration.
+
+```php
+public function set_debug(bool $state)
+```
+
+## Special cases
+
+### get_course
+
+From Moodle 2.5.1 onwards, you should use the `get_course` function instead of using `get_record('course', ...)` if you want to get a course record based on its ID, especially if there is a significant possibility that the course being retrieved is either the current course for the page, or the site course. Those two course records have probably already been loaded, and using this function will save a database query.
+
+Additionally, the code is shorter and easier to read.
+
+### get_courses
+
+If you want to get all the current courses in your Moodle, use get_courses() without parameter:
+
+```php
+$courses = get_courses();
+```
+
+## See also
+
+- [SQL coding style](/general/development/policies/codingstyle/sql)
+- [Core APIs](../index.md)
+- [DML exceptions](./exceptions.md): New DML code is throwing exceptions instead of returning false if anything goes wrong
+- [DML drivers](./drivers.md): Database drivers for new DML layer
+- [DDL functions](./ddl.md): Where all the functions used to handle DB objects ([DDL](https://en.wikipedia.org/wiki/Data_Definition_Language)) are defined.
diff --git a/versioned_docs/version-5.1/apis/core/grading/index.md b/versioned_docs/version-5.1/apis/core/grading/index.md
new file mode 100644
index 0000000000..1a3112c82f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/grading/index.md
@@ -0,0 +1,97 @@
+---
+title: Advanced grading API
+tags:
+ - Plugins
+ - Grading
+ - Activity grading
+documentationDraft: true
+---
+
+Advanced grading was introduced in Moodle 2.2 for grading of assignments. It is intended to be used for grading of other types of activities in the later versions.
+
+## Glossary
+
+In advanced grading we operate with three main entities:
+
+- a grading area;
+- a grading form definition; and
+- a grading form instance.
+
+### Grading area
+
+The grading area is basically the area that can be graded, for example, a submission in an assignment module. Each grading area may have several grading definitions but only one for each grading method. In an assignment's edit form (or its Advanced grading page) the teacher may set one of the advanced grading methods as current. The class called **`grading_manager`** is responsible for grading method operations in the specified area.
+
+### Grading form definition
+
+Grading form definitions are the set of rules defining how the grading is performed. For example, in rubrics this is the set of criteria and levels and their display options. The basic information about definition is stored in the DB table grading_definitions. A separate entry is created for each grading area (that is for each module). Users with permission `moodle/grade:managegradingforms` are able to edit the definitions and mark them as "Ready".
+
+### Grading form instance
+
+A grading form instance is created for each evaluation of a submission, using advanced grading. One instance (usually the latest) has the status INSTANCE_STATUS_ACTIVE. Sometimes it may be the case that a teacher wants to change the definition after some students have already been graded. In this case their instances change status to `INSTANCE_STATUS_NEEDUPDATE`. The grade pushed to the gradebook remains unchanged but students will not see the grade in advanced grading format until teacher updates them. Plugins are also welcome to use these status levels.
+
+## Functions
+
+## Examples
+
+### Using advanced grading in grade-able modules
+
+The following example is drawn from **/mod/assignment/lib.php**.
+
+1. In order for module to support advanced grading, its function **`[activityname]_supports(FEATURE_ADVANCED_GRADING)`** must return true.
+
+ ```php title="mod/[activityname]/lib.php"
+ function [activityname]_supports(string $feature): ?bool {
+ switch ($feature) {
+ // ...
+ case FEATURE_ADVANCED_GRADING:
+ return true;
+ // ...
+ }
+ }
+ ```
+
+1. The module should define a function called **`[activityname]_grading_areas_list()`**.
+1. There needs to be a check to see if there is an advanced grading method for this area and it is available. If it is, retrieve it and set the grade range used in this module.
+
+ ```php
+ if ($controller = get_grading_manager(...)->get_active_controller()) {
+ $controller->set_grade_range(...);
+ ...
+ }
+ ```
+
+1. There are two ways to create an grading object instance. Choose one of the following.
+
+ ```php
+ $gradinginstance = $controller->get_current_instance(...);
+ $gradinginstance = $controller->get_or_create_instance(...);
+ ```
+
+1. During population of the grading form, simple grading elements can now be substituted with advanced grading element.
+
+ ```php
+ $mform->addElement(
+ 'grading',
+ 'ELEMENTNAME',
+ '...',
+ ['gradinginstance' => $gradinginstance]
+ );
+ ```
+
+1. On submission of a grading form, the advanced grading information shall be also submitted. The grade for the gradebook retrieved from plugin and saved to the gradebook.
+
+ ```php
+ $grade = $gradinginstance->submit_and_get_grade($data->ELEMENTNAME, ...)
+ ```
+
+1. When the grade is displayed to the student it is displayed using the grading objects renderer.
+
+ ```php
+ $output .= $controller->render_grade(...);
+ ```
+
+1. To show the grading method to students before they are actually graded:
+
+ ```php
+ $output .= $controller->render_preview($PAGE);
+ ```
diff --git a/versioned_docs/version-5.1/apis/core/hooks/index.md b/versioned_docs/version-5.1/apis/core/hooks/index.md
new file mode 100644
index 0000000000..cce86eb8c5
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/hooks/index.md
@@ -0,0 +1,538 @@
+---
+title: Hooks API
+tags:
+- hooks
+- API
+- core
+---
+
+
+
+This page describes the Hooks API which is a replacement for some of the lib.php based one-to-many
+[plugin callbacks](https://docs.moodle.org/dev/Callbacks) implementing on
+[PSR-14](https://www.php-fig.org/psr/psr-14/).
+
+The most common use case for hooks is to allow customisation of standard plugins or core functionality
+through hook callbacks in local plugins. For example adding a custom institution password
+policy that applies to all enabled authentication plugins through a new local plugin.
+
+## Hook Policies
+
+New hooks added to Moodle from Moodle 4.4 onwards must meet the following rules:
+
+- When describing a hook which happens before or after, the following modifiers are allowed:
+ - `before`
+ - `after`
+- When a modifier is used, it should be used as a _prefix_
+- Hooks belonging to a core subsystem should be located within that subsystem, and _not_ in the `core` subsystem
+- Hook descriptions **should** be in English, and **should not** use language strings or support translation
+
+
+
+- `\core_course\hook\before_course_visibility_changed`
+- `\core_form\hook\before_form_validation`
+- `\core_form\hook\after_form_validation`
+- `\core\hook\navigation\before_render`
+
+
+
+## General concepts
+
+### Mapping to PSR-14
+
+Moodle does not allow camelCase for naming of classes and method and Moodle already has events,
+however the PSR-14 adherence has higher priority here.
+
+| PSR-14 | Hooks |
+|-------------------|------------------------------------------------------|
+| Event | Hook |
+| Listener | Hook callback |
+| Emitter | Hook emitter |
+| Dispatcher | Hook dispatcher (implemented in Hook manager) |
+| Listener Provider | Hook callback provider (implemented in Hook manager) |
+
+### Hook emitter
+
+A _Hook emitter_ is a place in code where core or a plugin needs to send or receive information
+to/from any other plugins. The exact type of information flow facilitated by hooks is not defined.
+
+### Hook instance
+
+Information passed between subsystem and plugins is encapsulated in arbitrary PHP class instances.
+These can be in any namespace, but generally speaking they should be placed in the `some_component\hook\*`
+namespace.
+
+Hooks are encouraged to describe themselves and to provide relevant metadata to make them easier to use and discover. There are two ways to describe a hook:
+
+- implement the `\core\hook\described_hook` interface, which has two methods:
+ - `get_description(): string;`
+ - `get_tags(): array;`
+- add an instance of the following attributes to the class:
+ - `\core\attribute\label(string $description)`
+ - `\core\attribute\tags($a, $set, $of, $tags, ...)`
+ - `\core\attribute\hook\replaces_callbacks('a_list_of_legacy_callbacks', 'that_this_hook_replaces')`
+
+### Hook callback
+
+The code executing a hook does not know in advance which plugin is going to react to a hook.
+
+Moodle maintains an ordered list of callbacks for each class of hook. Any plugin is free to register
+its own hook callbacks by creating a `db/hooks.php` file. The specified plugin callback method is called
+whenever a relevant hook is dispatched.
+
+### Hooks overview page
+
+The **Hooks overview page** lists all hooks that may be triggered in the system together with all
+registered callbacks. It can be accessed by developers and administrators from the Site
+administration menu.
+
+This page is useful especially when adding custom local plugins with hook callbacks that modify
+standard Moodle behaviour.
+
+In special cases administrators may override default hook callback priorities or disable some unwanted
+callbacks completely:
+
+```php title="/config.php"
+$CFG->hooks_callback_overrides = [
+ \mod_activity\hook\installation_finished::class => [
+ 'test_otherplugin\\callbacks::activity_installation_finished' => ['disabled' => true],
+ ],
+];
+```
+
+The hooks overview page will automatically list any hook which is placed inside any `*\hook\*` namespace within any Moodle component.
+If you define a hook which is _not_ in this namespace then you **must** also define a new `\core\hook\discovery_agent` implementation in `[component]\hooks`.
+
+## Adding new hooks
+
+1. Developer first identifies a place where they need to ask or inform other plugins about something.
+1. Depending on the location a new class implementing `core\hook\described_hook` is added to `core\hook\*` or
+`some_plugin\hook\*` namespace as appropriate.
+1. Optionally the developer may wish to allow the callback to stop any subsequent callbacks from receiving the object.
+If so, then the object should implement the `Psr\EventDispatcher\StoppableEventInterface` interface.
+1. Optionally if any data needs to be sent to hook callbacks, the developer may add internal hook
+constructor, some instance properties for data storage and public methods for data access from callbacks.
+
+Hook classes may be any class you like. When designing a new Hook, you should think about how consumers may wish to change the data they are passed.
+
+All hook classes should be defined as final, if needed traits can help with code reuse in similar hooks.
+
+:::important Hooks not located in standard locations
+
+If you define a hook which is _not_ in the `[component]\hook\*` namespace then you **must** also define a new `\core\hook\discovery_agent` implementation in `[component]\hooks`.
+
+```php title="/mod/example/classes/hooks.php"
+ \mod_example\local\entitychanges\create_example::class,
+ 'description' => 'A hook fired when an example was created',
+ ],
+ ];
+ }
+}
+```
+
+:::
+
+### Example of hook creation
+
+Imagine mod_activity plugin wants to notify other plugins that it finished installation,
+then mod_activity plugin developer adds a new hook and calls it at the end of plugin
+installation process.
+
+
+
+
+
+```php title="/mod/activity/classes/hook/installation_finished.php"
+
+
+
+
+```php title="/mod/activity/classes/hook/installation_finished.php"
+
+
+
+
+```php title="/mod/activity/db/install.php"
+dispatch($hook);
+}
+
+```
+
+## Dispatching hooks
+
+Once a hook has been created, it needs to be _dispatched_. The dispatcher is responsible for ordering all listeners and calling them with the hook data.
+
+The hook manager is responsible for dispatching hook instances using the `dispatch(object $hook)` method.
+
+The `dispatch` method is an instance method and requires an instance of the hook manager.
+
+From Moodle 4.4 you should make use of the [Dependency Injection](../di/index.md) system, for example:
+
+```php title="Dispatching a hook with DI"
+\core\di::get(\core\hook\manager::class)->dispatch($hook);
+```
+
+Using DI for dependency injection has the benefit that the hook manager can use fixture callbacks to test a range of behaviours, for example:
+
+```php title="Mocking a hook listener"
+// Unit test.
+public function test_before_standard_footer_html_hooked(): void {
+ // Load the callback classes.
+ require_once(__DIR__ . '/fixtures/core_renderer/before_standard_footer_html_callbacks.php');
+
+ // Replace the version of the manager in the DI container with a phpunit one.
+ \core\di::set(
+ \core\hook\manager::class,
+ \core\hook\manager::phpunit_get_instance([
+ // Load a list of hooks for `test_plugin1` from the fixture file.
+ 'test_plugin1' => __DIR__ .
+ '/fixtures/core_renderer/before_standard_footer_html_hooks.php',
+ ]),
+ );
+
+ $page = new moodle_page();
+ $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL);
+
+ $html = $renderer->standard_footer_html();
+ $this->assertIsString($html);
+ $this->assertStringContainsString('A heading can be added', $html);
+}
+
+// fixtures/core_renderer/before_standard_footer_html_callbacks.php
+final class before_standard_footer_html_callbacks {
+ public static function before_standard_footer_html(
+ \core\hook\output\before_standard_footer_html $hook,
+ ): void {
+ $hook->add_html("
A heading can be added
");
+ }
+}
+
+// fixtures/core_renderer/before_standard_footer_html_hooks.php
+$callbacks = [
+ [
+ 'hook' => \core\hook\output\before_standard_footer_html::class,
+ 'callback' => [
+ \test_fixtures\core_renderer\before_standard_footer_html_callbacks::class,
+ 'before_standard_footer_html',
+ ],
+ ],
+];
+```
+
+:::note
+
+Prior to Moodle 4.3 the only way to dispatch a hook is by accessing the manager instance:
+
+```php
+\core\hook\manager::get_instance()->dispatch($hook);
+```
+
+This approach is harder to test in situ.
+
+:::
+
+## Registering of hook callbacks
+
+Any plugin is free to register callbacks for all core and plugin hooks.
+The registration is done by adding a `db/hooks.php` file to plugin.
+Callbacks may be provided as a PHP callable in either:
+
+- string notation, in the form of `some\class\name::static_method`; or
+- array notation, in the form of `[\some\class\name::class, 'static_method']`.
+
+:::danger Use of array notation
+
+
+
+Support for Array notated callbacks was introduced in Moodle 4.4. If you are writing a callback for a Moodle 4.3 site, you _must_ use the string notation.
+
+:::
+
+Hook callbacks are executed in the order of their priority from highest to lowest.
+Any guidelines for callback priority should be described in hook descriptions if necessary.
+
+:::caution
+
+Hooks may be dispatched at any time, _including during system installation and upgrade_ (for example `before_http_headers`). Callback methods for such hooks must take extra care to ensure the plugin is properly initialised and that the database is available if database calls are made (as the database does not exist during site installation).
+
+The `during_initial_install()` function can be used to check whether the the site is currently being installed, and `get_config('your_pluginname', 'version')` are two ways to conditionally make database queries or use API functions. `isset($CFG->upgraderunning)` can also be used to test if an upgrade is running. Failing to implement these checks may render the web install/upgrade page unusable.
+
+Please note that the legacy component callback system did _not_ call the `lib.php` callbacks during installation or upgrade. As such, when porting these callbacks to hooks, you may need to implement additional checks as described above.
+
+:::
+
+### Example of hook callback registration
+
+First developer needs to add a new static method to some class that accepts instance of
+a hook as parameter.
+
+```php title="/local/stuff/classes/local/hook_callbacks.php"
+ mod_activity\hook\installation_finished::class,
+ 'callback' => [\local_stuff\local\hook_callbacks::class, 'activity_installation_finished'],
+ 'priority' => 500,
+ ],
+];
+```
+
+Callback registrations are cached, so developers should to either increment the version number for the
+component they place the hook into. During development it is also possible to purge caches.
+
+In this particular example, the developer would probably also add some code to `db/install.php`
+to perform the necessary action in case the hook gets called before the `local_stuff` plugin
+is installed.
+
+## Deprecation of legacy lib.php callbacks
+
+Hooks are a direct replacement for one-to-many lib.php callback functions that were implemented
+using the `get_plugins_with_function()`, `plugin_callback()`, or `component_callback()` functions.
+
+If a hook implements the `core\hook\deprecated_callback_replacement` interface, and if deprecated `lib.php`
+callbacks can be listed in `get_deprecated_plugin_callbacks()` hook method
+then developers needs to only add extra parameter to existing legacy callback functions
+and the hook manager will trigger appropriated deprecated debugging messages when
+it detects plugins that were not converted to new hooks yet.
+
+:::important Legacy fallback
+
+Please note **it is** possible for plugin to contain both legacy `lib.php` callback and PSR-14 hook
+callbacks.
+
+This allows community contributed plugins to be made compatible with multiple Moodle branches.
+
+The legacy `lib.php` callbacks are automatically ignored if hook callback is present.
+
+:::
+
+## Example how to migrate legacy callback
+
+This example describes migration of `after_config` callback from the very end of `lib/setup.php`.
+
+First we need a new hook:
+
+
+
+
+
+```php title="/lib/classes/hook/after_config.php"
+
+
+
+
+```php title="/lib/classes/hook/after_config.php"
+
+
+
+
+The hook needs to be emitted immediately after the current callback execution code,
+and an extra parameter `$migratedtohook` must be set to true in the call to `get_plugins_with_function()`.
+
+```php title="/lib/setup.php"
+
+// Allow plugins to callback as soon possible after setup.php is loaded.
+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php', true, true);
+foreach ($pluginswithfunction as $plugins) {
+ foreach ($plugins as $function) {
+ try {
+ $function();
+ } catch (Throwable $e) {
+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace());
+ }
+ }
+}
+// Dispatch the new Hook implementation immediately after the legacy callback.
+\core\di::get(\core\hook\manager::class())->dispatch(new \core\hook\after_config());
+```
+
+## Hooks which contain data
+
+It is often desirable to pass a data object when dispatching hooks.
+
+This can be useful where you are passing code that consumers may wish to change.
+
+Since the hook is an arbitrary PHP object, it is possible to create any range of public data and/or method you like and for the callbacks to use those methods and properties for later consumption.
+
+```php title="/lib/classes/hook/block_delete_pre.php"
+ $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($instance);
+ }
+ }
+ }
+}
+$hook = new \core\hook\block_delete_pre($instance);
+\core\di::get(\core\hook\manager::class())->dispatch($hook);
+```
+
+## Hooks which can be stopped
+
+In some situations it is desirable to allow a callback to stop execution of a hook. This can happen in situations where the hook contains that should only be set once.
+
+The Moodle hooks implementation has support for the full PSR-14 specification, including Stoppable Events.
+
+To make use of Stoppable events, the hook simply needs to implement the `Psr\EventDispatcher\StoppableEventInterface` interface.
+
+```php title="/lib/classes/hook/block_delete_pre.php"
+stopped;
+ }
+
+ public function stop(): void {
+ $this->stopped = true;
+ }
+}
+```
+
+A callback will only be called if the hook was not stopped before-hand. Depending on the hook implementation, it can stop he
+
+```php title="/local/myplugin/classes/callbacks.php"
+stop();
+ }
+}
+```
+
+## Tips and Tricks
+
+Whilst not being formal requirements, you are encouraged to:
+
+- describe and tag your hook as appropriate using either:
+ - the `\core\hook\described_hook` interface; or
+ - the `\core\attribute\label` and `\core\attribute\tags` attributes
+- make use of constructor property promotion combined with readonly properties to reduce unnecessary boilerplate.
diff --git a/versioned_docs/version-5.1/apis/core/htmlwriter/index.md b/versioned_docs/version-5.1/apis/core/htmlwriter/index.md
new file mode 100644
index 0000000000..4740676734
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/htmlwriter/index.md
@@ -0,0 +1,62 @@
+---
+title: HTML Writer API
+tags:
+ - API
+ - HTML
+ - DOM
+---
+
+Moodle has a class called _HTML writer_ which allows you to output basic HTML tags. This is typically used within renderer functions, for example `question/type/*pluginname*/renderer.php`.
+
+:::tip
+
+Please consider using [templates](../../../guides/templates/index.md) as an alternative to the _HTML writer_.
+
+:::
+
+:::note
+
+There is no documentation for most of this class. Please read [HTML Writer Class Reference](https://phpdoc.moodledev.io/main/d4/d78/classhtml__writer.html) for further information.
+
+:::
+
+## Methods
+
+### div
+
+```php
+html_writer::div(content, class="", attributes="");
+```
+
+Example usage:
+
+```php
+html_writer::div('anonymous'); //
anonymous
+html_writer::div('kermit', 'frog'); //
kermit
+```
+
+Attributes can be set by an array with key-value pairs.
+
+```php
+html_writer::div('Mr', 'toad', array('id' => 'tophat'));
+//
Mr/div>
+```
+
+### span
+
+```php
+html_writer::start_span('zombie') . 'BRAINS' . html_writer::end_span();
+// BRAINS
+```
+
+### Generic tags
+
+```php
+html_writer::tag(tag_name, contents, attributes=null);
+html_writer::start_tag(tag_name, attributes=null;);
+html_writer::end_tag(tag_name);
+html_writer::empty_tag(tag_name, attributes=null);
+html_writer::nonempty_tag(tag_name, content, attributes=null);
+html_writer::attribute(name, value);
+html_writer::attributes(attributes_array);
+```
diff --git a/versioned_docs/version-5.1/apis/core/index.md b/versioned_docs/version-5.1/apis/core/index.md
new file mode 100644
index 0000000000..68027faa93
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/index.md
@@ -0,0 +1,8 @@
+---
+title: Core APIs
+tags:
+ - core
+ - API
+---
+
+Moodle provides a series of Core APIs which can be used by any part of Moodle.
diff --git a/versioned_docs/version-5.1/apis/core/lock/index.md b/versioned_docs/version-5.1/apis/core/lock/index.md
new file mode 100644
index 0000000000..7b80a348f2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/lock/index.md
@@ -0,0 +1,73 @@
+---
+title: Lock API
+tags:
+ - API
+ - Lock
+---
+
+Locking is required whenever you need to prevent two, or more, processes accessing the same resource at the same time. The prime candidate for locking in Moodle is cron. Locking allows multiple cron processes to work on different parts of cron at the same time with no risk that they will conflict (work on the same job at the same time).
+
+## When to use locking
+
+When you want to prevent multiple requests from accessing the same resource at the same time. Accessing a resource is a vague description, but it could be for example running a slow running task in the background, running different parts of cron etc.
+
+## Performance
+
+Locking is not meant to be fast. Do not use it in code that will be triggered many times in a single request (for example MUC). It is meant to be always correct - even for multiple nodes in a cluster. This implies that the locks are communicated among all the nodes in the cluster, and hence it will never be super quick.
+
+## Usage
+
+The locking API is used by getting an instance of a lock_factory, and then using it to retrieve locks, and eventually releasing them. You are required to release all your locks, even on the event of failures.
+
+```php
+$timeout = 5;
+
+// A namespace for the locks. Must be prefixed with the component name to prevent conflicts.
+$locktype = 'mod_assign_download_submissions';
+
+// Resource key - needs to uniquely identify the resource that is to be locked. E.g. If you
+// want to prevent a user from running multiple course backups - include the userid in the key.
+$resource = 'user:' . $USER->id;
+
+// Get an instance of the currently configured lock_factory.
+$lockfactory = \core\lock\lock_config::get_lock_factory($locktype);
+
+// Get a new lock for the resource, wait for it if needed.
+if ($lock = $lockfactory->get_lock($resource, $timeout)) {
+ // We have exclusive access to the resource, do the slow zip file generation...
+
+ if ($someerror) {
+ // Always release locks on failure.
+ $lock->release();
+ print_error('blah');
+ }
+
+ // Release the lock once finished.
+ $lock->release();
+
+} else {
+ // We did not get access to the resource in time, give up.
+ throw new moodle_exception('locktimeout');
+}
+```
+
+## Use a different lock type from the default
+
+Change the $CFG->lock_factory setting to one of the other lock types included with core. These are all documented in config-dist.php.
+
+## Implementing new lock types
+
+If you really want to do this you can. I probably wouldn't recommend it - because the core lock types should be very reliable - and the performance is not really a concern.
+
+Add a new local_XXX plugin with an autoloaded class that implements \core\lock\lock_factory.
+Set the site configuration variable "lock_factory" to the full namespaced path to your class in the config.php for example
+
+```php
+$CFG->lock_factory = '\local_redis\lock\redis_lock_factory';
+```
+
+:::note
+
+See `lib/tests/lock_test.php` for an example of unit tests which can be run on a custom lock instance to verify it for correctness (run_on_lock_factory).
+
+:::
diff --git a/versioned_docs/version-5.1/apis/core/message/index.md b/versioned_docs/version-5.1/apis/core/message/index.md
new file mode 100644
index 0000000000..7d2f24ed70
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/message/index.md
@@ -0,0 +1,206 @@
+---
+title: Message API
+tags:
+ - API
+ - Tutorial
+ - Plugins
+ - Messaging
+---
+
+## What is this document?
+
+This document describes how to make use of the Moodle Messaging API to send messages to Moodle users.
+
+If you are after a general introduction on using the Moodle Messaging system go to [messaging user documentation](https://docs.moodle.org/en/Messaging).
+
+If you are looking for details of how the Messaging system's internal structure was implemented, go to [Messaging 2.0](https://docs.moodle.org/dev/Messaging_2.0).
+
+If you are looking for instructions on the implementation of a custom message processor (a component that receives messages sent to a user), go to [Messaging custom components](https://docs.moodle.org/dev/Messaging_custom_components).
+
+If you are looking for instructions on sending messages programmatically within Moodle then read on...
+
+## Overview
+
+Moodle components have the ability to send messages to users via the Moodle messaging system. Any type of component, for example a plugin or block, can register as a message producer then send messages to users.
+
+## File locations
+
+The Message API code is contained within `lib/messagelib.php` and is automatically included for you during page setup.
+
+## Functions
+
+`message_send()` is the primary point of contact for the message API. Call it to send a message to a user. See the php documentation for a full description of the arguments that must be supplied. There is also an example below.
+
+## Message pop-up
+
+
+
+A JavaScript pop-up can be displayed through a link to invite a user to message another. In order to use this feature, you need to require the JavaScript libraries using `message_messenger_requirejs()` and create a link with the attributes returned by `message_messenger_sendmessage_link_params()`. More in the examples.
+
+## Examples
+
+### How to register as a message producer
+
+The messages produced by a message provider is defined in the `/db/messages.php` file of a component. Below is code from the quiz module's `messages.php` file, shown as an example.
+
+```php title="mod/quiz/db/messages.php"
+defined('MOODLE_INTERNAL') || die();
+$messageproviders = [
+ // Notify teacher that a student has submitted a quiz attempt
+ 'submission' => [
+ 'capability' => 'mod/quiz:emailnotifysubmission'
+ ],
+ // Confirm a student's quiz attempt
+ 'confirmation' => [
+ 'capability' => 'mod/quiz:emailconfirmsubmission'
+ ],
+];
+```
+
+The quiz can send two kinds of messages, quiz "submission" and "confirmation" notifications. Each message type is only available to users with the appropriate capability. Please note that the capability is checked at the system level context. Users who have this capability will have this message listed in their messaging preferences. You can omit the capability section if your message should be visible for all users. For example forum post notifications are available to all users.
+
+```php
+$messageproviders = [
+ // Ordinary single forum posts
+ 'posts' => [],
+];
+```
+
+When displaying your message types in a user's messaging preferences it will use a string from your component's language file called `messageprovider:messagename`. For example here are the relevant strings from the quiz's language file.
+
+```php
+$string['messageprovider:confirmation'] = 'Confirmation of your own quiz submissions';
+$string['messageprovider:submission'] = 'Notification of quiz submissions';
+```
+
+Once your `messages.php` is complete you need to increase the version number of your component in its `version.php`. That will cause Moodle to check `messages.php` looking for new or changed message definitions. Log in as an admin and go to `/admin/index.php` (the Notifications page) to start the upgrade process.
+
+### Setting defaults
+
+```php title="The default processor can be set using an element of the array"
+'mynotification' => [
+ 'defaults' => [
+ 'pop-up' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
+ 'email' => MESSAGE_PERMITTED,
+ ],
+],
+```
+
+With that setting email will be permitted but disabled for each user by default. It can be turned on by each user through the `preferences/notification` preferences options (`/message/notificationpreferences.php?userid=X`)
+The possible values are recorded in the lib.php file of messaging
+
+```php
+/**
+ * Define contants for messaging default settings population. For unambiguity of
+ * plugin developer intentions we use 4-bit value (LSB numbering):
+ * bit 0 - whether to send message when user is loggedin (MESSAGE_DEFAULT_LOGGEDIN)
+ * bit 1 - whether to send message when user is loggedoff (MESSAGE_DEFAULT_LOGGEDOFF)
+ * bit 2..3 - messaging permission (MESSAGE_DISALLOWED|MESSAGE_PERMITTED|MESSAGE_FORCED)
+ *
+ * MESSAGE_PERMITTED_MASK contains the mask we use to distinguish permission setting
+ */
+```
+
+Note that if you change the values in message.php and then upgrade the plugin the values will not automatically be changed in the `config_plugins` table where they are stored.
+
+### How to send a message
+
+
+
+Here is example code showing you how to actually send a notification message. The example shows the construction of a object with specific properties, which is then passed to the `message_send()` function that uses the information to send a message.
+
+```php title="Sending a message"
+$message = new \core\message\message();
+$message->component = 'mod_yourmodule'; // Your plugin's name
+$message->name = 'mynotification'; // Your notification name from message.php
+$message->userfrom = core_user::get_noreply_user(); // If the message is 'from' a specific user you can set them here
+$message->userto = $user;
+$message->subject = 'message subject 1';
+$message->fullmessage = 'message body';
+$message->fullmessageformat = FORMAT_MARKDOWN;
+$message->fullmessagehtml = '
message body
';
+$message->smallmessage = 'small message';
+$message->notification = 1; // Because this is a notification generated from Moodle, not a user-to-user message
+$message->contexturl = (new \moodle_url('/course/'))->out(false); // A relevant URL for the notification
+$message->contexturlname = 'Course list'; // Link title explaining where users get to for the contexturl
+// Extra content for specific processor
+$content = [
+ '*' => [
+ 'header' => ' test ',
+ 'footer' => ' test ',
+ ],
+];
+$message->set_additional_content('email', $content);
+
+// You probably don't need attachments but if you do, here is how to add one
+$usercontext = context_user::instance($user->id);
+$file = new stdClass();
+$file->contextid = $usercontext->id;
+$file->component = 'user';
+$file->filearea = 'private';
+$file->itemid = 0;
+$file->filepath = '/';
+$file->filename = '1.txt';
+$file->source = 'test';
+
+$fs = get_file_storage();
+$file = $fs->create_file_from_string($file, 'file1 content');
+$message->attachment = $file;
+
+// Actually send the message
+$messageid = message_send($message);
+```
+
+```php title="Before 2.9 message data used to be a stdClass object as shown below (This formation of a message will no longer work as of Moodle 3.6. Only a message object will be accepted):"
+
+$message = new stdClass();
+$message->component = 'mod_quiz'; //your component name
+$message->name = 'submission'; //this is the message name from messages.php
+$message->userfrom = $USER;
+$message->userto = $touser;
+$message->subject = $subject;
+$message->fullmessage = $message;
+$message->fullmessageformat = FORMAT_PLAIN;
+$message->fullmessagehtml = '';
+$message->smallmessage = '';
+$message->notification = 1; //this is only set to 0 for personal messages between users
+message_send($message);
+```
+
+### How to set-up the message pop-up
+
+Here is example code showing you how to set-up the JavaScript pop-up link.
+
+```php
+require_once('message/lib.php');
+$userid = 2;
+$userto = $DB->get_record('user', ['id' => $userid]);
+
+message_messenger_requirejs();
+$url = new moodle_url('message/index.php', ['id' => $userto->id]);
+$attributes = message_messenger_sendmessage_link_params($userto);
+echo html_writer::link($url, 'Send a message', $attributes);
+```
+
+## Changes in Moodle 3.5
+
+
+
+In Moodle 3.5, there were some moderately big changes. The only docs I have been able to find about them are in [upgrade.txt](https://github.com/moodle/moodle/blob/33a388eff737c049786ee42d7430db549568471c/message/upgrade.txt#L56) file. However, that is the details, here is an overview:
+
+The main `message_send()` API to send a message has not changed, so if your code is just sending messages, you don't need to do anything.
+
+Similarly, message_output plugins don't need to change, so no worries there.
+
+If you are doing things with messages, then you need to understand how the internals have changed.
+
+The database tables have changed. Messages from Moodle components to a user (e.g. mod_quiz), telling them that something has happened (e.g. an attempt was submitted) have always been 'Notifications'. In the past, this was just a column in the `mdl_message` table. Now, messages and notifications are stored in completely separate tables. Notifications are in `mdl_notifications`. The structure of this table is very similar to the old `mdl_message` table which is now not used at all. Messages are in `mdl_messages`, and related tables, that now exist to support group messaging. Those tables join together like this:
+
+```sql
+ SELECT *
+ FROM mdl_messages m
+ JOIN mdl_message_conversations con ON con.id = m.conversationid
+ JOIN mdl_message_conversation_members mem ON mem.conversationid = con.id
+LEFT JOIN mdl_message_user_actions act ON act.userid = mem.userid AND act.messageid = m.id
+ ORDER BY m.timecreated, m.id, mem.userid, act.id
+```
diff --git a/versioned_docs/version-5.1/apis/core/navigation/_index/Moodle-IA.png b/versioned_docs/version-5.1/apis/core/navigation/_index/Moodle-IA.png
new file mode 100644
index 0000000000..b081cfa530
Binary files /dev/null and b/versioned_docs/version-5.1/apis/core/navigation/_index/Moodle-IA.png differ
diff --git a/versioned_docs/version-5.1/apis/core/navigation/_index/assignmentmenu.png b/versioned_docs/version-5.1/apis/core/navigation/_index/assignmentmenu.png
new file mode 100644
index 0000000000..94abeec2bd
Binary files /dev/null and b/versioned_docs/version-5.1/apis/core/navigation/_index/assignmentmenu.png differ
diff --git a/versioned_docs/version-5.1/apis/core/navigation/index.md b/versioned_docs/version-5.1/apis/core/navigation/index.md
new file mode 100644
index 0000000000..e78efb91a0
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/navigation/index.md
@@ -0,0 +1,403 @@
+---
+title: Navigation API
+tags:
+ - API
+ - Navigation
+---
+
+The Navigation API allows for the manipulation of the navigation system used in Moodle.
+
+## What the navigation is
+
+It's very important to understand what the navigation is exactly within Moodle. One of the goals for Moodle 2.0 was to standardise navigation throughout Moodle and try to bring order to the structure of a Moodle site. Navigation is available through the page object `$PAGE`, against which you set the heading for the page, the title, any JavaScript requirements, etc. The navigation structure uses the information `$PAGE` contains to generate a navigation structure for the site. The navigation or settings blocks are interpretations of the navigation structure Moodle creates.
+
+This navigation structure is available through three variables:
+
+- `$PAGE->navigation` - The main navigation structure, it will contain items that will allow the user to browse to the other available pages
+- `$PAGE->settingsnav` - The settings navigation structure contains items that will allow the user to edit settings
+- `$PAGE->navbar` - A special structure for page breadcrumbs
+
+A conceptual view of the information architecture that sits behind the navigation tree is here:
+
+
+
+This diagram represents the major entities and how they are related to each other. Examples are given of the type of functions available on each kind of entity.
+
+## What the navigation is not
+
+The navigation is **NOT** the navigation block or the settings block! These two blocks were created to display the navigation structure. The navigation block looks at `$PAGE->navigation`, and the settings block looks at `$PAGE->settingsnav`. Both blocks interpret their data into an HTML structure and render it.
+
+1. The navigation is a back-end structure that is built behind the scenes and has no immediate method of display.
+1. The navigation and settings blocks display the back-end navigation structure but add nothing to it at all.
+
+In a model-view-controller pattern, `$PAGE->navigation`, `$PAGE->settingsnav`, and `$PAGE->navbar` are the models, and the blocks are views.
+
+The navbar is just the path to the active navigation or settings item. The navbar is not displayed by a block; instead it is added into the theme's layout files and displayed by the core renderer.
+
+## How the navigation works
+
+The main navigation structure can be accessed through `$PAGE->navigation`. The navigation and settings are contextual in that they will relate to the page that the user is viewing. This is determined by other `$PAGE` object properties:
+
+- `$PAGE->context` is a Moodle context that immediately outlines the nature of the page the user is viewing.
+- `$PAGE->course` is the course the user is viewing. This is essential if the context is CONTEXT_COURSE or anything within it. However, it is also useful in other contexts such as CONTEXT_USER.
+- `$PAGE->cm` is the course module instance. This is essential if the context is CONTEXT_MODULE.
+- `$PAGE->url` is used to match the active navigation item.
+
+Nearly every page sets `$PAGE->url` through a call to `$PAGE->set_url()` however not many explicitly set the context, course, or cm. When you call `require_login()` with a course or context_module, it automatically calls the following:
+
+```php title="Set up the global course"
+if ($cm) {
+ $PAGE->set_cm($cm, $course); // sets up global $COURSE
+} else {
+ $PAGE->set_course($course);// sets up global $COURSE
+```
+
+A page will only be required to explicitly set a context, course, or cm under one of these conditions:
+
+1. `require_login` is NOT being called correctly
+1. The page is using CONTEXT_SYSTEM, CONTEXT_COURSECAT, or CONTEXT_USER (call `$PAGE->set_context()`).
+1. The page is using a course or cm but it is also using one of the above contexts (call `$PAGE->set_course()` or `$PAGE->set_cm()`).
+
+:::important
+
+The navigation structure cannot be generated before the `$PAGE` object is configured. It is only generated when it is first used, either when something tries to access the structure or when code tries to add to it. The navigation is initialised in a specific order:
+
+1. Main navigation structure
+1. Settings navigation
+1. Navbar (does not need to be generated because of its simple contents and rendering)
+
+:::
+
+## Extending the navigation
+
+### Code extension
+
+This method of extending is when the code arbitrarily extends the navigation during its execution. Extending the navigation through this means allows you to extend the navigation anywhere easily, however it will only be shown on pages where your extending code gets called (you should probably put it in a function within `lib.php`).
+
+Navigation extensions that apply all the time (even when not on pages including your code) can be made by putting them in your plugin's settings.php file.
+
+These examples are taken from the [General Developer Forum: Moodle 2 - how to set up breadcrumbs for a module page](http://moodle.org/mod/forum/discuss.php?d=152391). It has further information that is well worth reading.
+
+#### Navigation
+
+##### Extending the main navigation structure
+
+```php
+$previewnode = $PAGE->navigation->add(
+ get_string('preview'),
+ new moodle_url('/a/link/if/you/want/one.php'),
+ navigation_node::TYPE_CONTAINER
+);
+$thingnode = $previewnode->add(
+ get_string('thingname'),
+ new moodle_url('/a/link/if/you/want/one.php')
+);
+$thingnode->make_active();
+```
+
+The above lines of code adds a preview node to the bottom of the navigation and then adds a thing node to the preview node (adding a leaf to our tree).
+The final line of code makes the thing node active so that the navbar finds it however if the URL you give it is the same as the url you set for the page it will automatically be marked active and you won't need this call.
+
+##### Extending the navigation for the course
+
+This example assumes that you already know the course ID and have already called `require_login()`. This loads the navigation data for the specified course.
+
+```php title="Extending the navigation for the course."
+$coursenode = $PAGE->navigation->find($courseid, navigation_node::TYPE_COURSE);
+$thingnode = $coursenode->add(
+ get_string('thingname'),
+ new moodle_url('/a/link/if/you/want/one.php')
+);
+$thingnode->make_active();
+```
+
+The first line of this code finds the course node using a combination of the ID of the course, and the node type `navigation_node::TYPE_COURSE`.
+
+This example relies on the navigation API to generate the navigation up to the course, and the example then adds to that structure.
+
+:::note
+
+Moodle loads plugins in alphabetical order. This means that plugin_b can find a node added by plugin_a but not the other way around. However, plugins must abide by the [Component communication principles](/general/development/policies/component-communication).
+
+:::
+
+#### Settings navigation
+
+Adding to the settings navigation is very similar to the general navigation, only using the `settingsnav` property of the `$PAGE` API.
+
+```php
+$settingnode = $PAGE->settingsnav->add(
+ get_string('setting'),
+ new moodle_url('/a/link/if/you/want/one.php'),
+ navigation_node::TYPE_CONTAINER
+);
+$thingnode = $settingnode->add(
+ get_string('thingname'),
+ new moodle_url('/a/link/if/you/want/one.php')
+);
+$thingnode->make_active();
+```
+
+##### Add Settings folder to navigation
+
+This example adds a settings folder to the navigation API at Site administration / Plugins / Activity modules / Assignment.
+
+
+
+An example of adding a navigation folder to a settings.php for a block with a link to the settings page and a external page is bellow.
+
+```php
+// Create a submenu in the block menu.
+// This can be found in:
+// - blocksettings for block plugins
+// - modsettings for activity modules
+// - localplugins for Local plugins
+// The default menus are defined in admin/settings/plugins.php.
+$ADMIN->add(
+ 'blocksettings',
+ new admin_category(
+ 'blocksamplefolder',
+ get_string('pluginname', 'mod_sample')
+ )
+);
+
+// Create settings block.
+$settings = new admin_settingpage($section, get_string('settings', 'block_sample'));
+if ($ADMIN->fulltree) {
+ $settings->add(
+ new admin_setting_configcheckbox(
+ 'block_sample_checkbox',
+ get_string('checkbox', 'block_sample'),
+ get_string('checkboxdescription', 'block_kronoshtml'),
+ 0
+ )
+ );
+}
+
+// This adds the settings link to the folder/submenu.
+$ADMIN->add('blocksamplefolder', $settings);
+
+// This adds a link to an external page.
+$ADMIN->add(
+ 'blocksamplefolder',
+ new admin_externalpage(
+ 'block_sample_page',
+ get_string('externalpage', 'block_sample'),
+ "{$CFG->wwwroot}/blocks/sample/sample.php"
+ )
+);
+
+// Prevent Moodle from adding settings block in standard location.
+$settings = null;
+```
+
+#### Navbar
+
+The following example extends the navbar navigation.
+
+```php
+$PAGE->navbar->ignore_active();
+$PAGE->navbar->add(
+ get_string('preview'),
+ new moodle_url('/a/link/if/you/want/one.php')
+);
+$PAGE->navbar->add(
+ get_string('name of thing'),
+ new moodle_url('/a/link/if/you/want/one.php')
+);
+```
+
+The above code tells the navbar to ignore the automatically detected _active page_ and to instead use what is manually added, at which point we add two items as shown.
+
+### Plugin Callbacks
+
+These are specific functions that the navigation looks for and calls if they exist for the plugin, presently only three plugin types can extend the navigation through these call-backs.
+
+Ideally all entries in "Administration / Site administration" tree should be done via settings.php files but sometimes it may be easier to directly modify the navigation structure created from the admin settings tree (such as when adding links to external pages).
+
+#### Module
+
+Modules have two call-back methods, first to extend the navigation, and second to extend the settings. These call-backs get called when ever the user is viewing a page within the module and should only extend the navigation for the module.
+
+```php
+function {modulename}_extend_navigation(
+ ${modulename}node,
+ $course,
+ $module,
+ $cm
+);
+function {modulename}_extend_settings_navigation(
+ $settings,
+ ${modulename}node
+);
+```
+
+You may be required to add a node in a specified order within the menu navigation menus. To do this you need to examine the node object as given in the respective parameters in the functions above, then find the key of the child node you wish to place the link before. For example, applying the code below will put a direct link to grade report.
+
+```php
+function my_plugin_extend_settings_navigation($settingsnav, $context){
+ $addnode = $context->contextlevel === 50;
+ $addnode = $addnode && has_capability('gradereport/grader:view', $context);
+ if ($addnode) {
+ $id = $context->instanceid;
+ $urltext = get_string('gradereportlink', 'myplugin');
+ $url = new moodle_url('/grade/report/grader/index.php',[
+ 'id' => $id,
+ ]);
+ // Find the course settings node using the 'courseadmin' key.
+ $coursesettingsnode = $settingsnav->find('courseadmin', null);
+ $node = $coursesettingsnode->create(
+ $urltext,
+ $url,
+ navigation_node::NODETYPE_LEAF,
+ null,
+ 'gradebook',
+ new pix_icon('i/report', 'grades')
+ );
+
+ // Add the new node _before_ the 'gradebooksetup' node.
+ $coursesettingsnode->add_node($node, 'gradebooksetup');
+ }
+
+ // ...
+}
+```
+
+#### Course Formats
+
+Course formats are able to completely redefine the way in which navigation is generated for a course, as well as this they also have several methods to ensure the navigation is generated correctly.
+
+#### Course Reports
+
+By default reports don't add themselves or anything else to the navigation however there is a call-back that can be implemented to allow them to do so.
+
+#### Local Plugins
+
+Local plugins have two call-back methods, first to extend the navigation, and second to extend the settings.
+
+```php
+function local_{pluginname}_extend_navigation(
+ global_navigation $nav
+);
+
+function local_{pluginname}_extend_settings_navigation(
+ settings_navigation $nav,
+ context $context
+);
+```
+
+#### Course settings
+
+Any plugin implementing the following callback in `lib.php` can extend the course settings navigation.
+
+```php
+function _extend_navigation_course(
+ navigation_node $parentnode,
+ stdClass $course,
+ context_course $context
+);
+```
+
+#### User settings
+
+Any plugin implementing the following callback in `lib.php` can extend the user settings navigation.
+
+```php
+function _extend_navigation_user_settings(
+ navigation_node $parentnode,
+ stdClass $user,
+ context_user $context,
+ stdClass $course,
+ context_course $coursecontext
+);
+```
+
+#### Category settings
+
+Any plugin implementing the following callback in `lib.php` can extend the category settings navigation.
+
+```php
+function _extend_navigation_category_settings(
+ navigation_node $parentnode,
+ context_coursecat $context
+);
+```
+
+#### Frontpage settings
+
+Any plugin implementing the following callback in `lib.php` can extend the frontpage settings navigation.
+
+```php
+function _extend_navigation_frontpage(
+ navigation_node $parentnode,
+ stdClass $course,
+ context_course $context
+);
+```
+
+#### User profile
+
+Any plugin implementing the following callback in `lib.php` can extend the user profile navigation.
+
+```php
+function _extend_navigation_user(
+ navigation_node $parentnode,
+ stdClass $user,
+ context_user $context,
+ stdClass $course,
+ context_course $coursecontext
+);
+```
+
+### Boost theme
+
+The navigation API is specifically about allowing the manipulation of nodes in an in-memory tree structure that is used as the basis of building navigation components in the page. The navigation and settings blocks are 2 examples of such components and the flat navigation and settings menus in the Boost theme are another example. The navigation component itself can decide to show all or only part of the navigation tree in order to not overwhelm the user viewing the page. Whether a node is actually displayed depends on where in the tree the node was added, what is the current page in the navigation tree, and the specific navigation components that are used to provide navigation functionality in the current theme.
+
+:::important
+
+If you are testing in the Boost theme - all nodes that are added to settings tree are displayed in the course or activity settings menu. This is shown as a cog on the front page of the course or activity.
+
+Only the most important pre-selected nodes are displayed in the flat-navigation drawer in order to provide consistency and avoid overwhelming the user with too many links.
+
+It is possible, but _not recommended_, for plugins to add nodes to the flat navigation (see [FAQ's and troubleshooting](#faqs-and-troubleshooting) for more information).
+
+:::
+
+## FAQ's and troubleshooting
+
+### **Q.** My page is on the navigation but it doesn't find it?
+
+The first thing to do here is check the URL you are setting for the page. It should match the URL your page has within the navigation. If it doesn't you have two options, first change your `$PAGE->set_url()` call, or second override the URL the navigation is using to find the active node as shown below:
+
+```php
+navigation_node::override_active_url(
+ new moodle_url('/your/url/here.php', [
+ 'param' => 'value',
+ ])
+);
+```
+
+### **Q.** How do I add a node to the "flat" navigation in the Boost theme?
+
+Adding a node to the "flat" navigation is only possible for Moodle versions before 4.0. After creating a node and adding it to the navigation tree - you can set the property `showinflatnavigation` to true in order for this node to be displayed in the flat navigation.
+
+```php
+$node = navigation_node::create(...);
+$node->showinflatnavigation = true;
+$navigation->add_node($node);
+```
+
+:::danger
+
+This is highly discouraged because the number of nodes in this flat navigation has been deliberately restricted to a very small number of the most important links that are applicable to all user roles.
+
+Adding more links to this menu will make it harder to use, inconsistent for different users, and inconsistent for different sites.
+
+Consider carefully if you really need to fill an additional 230x44 pixels of every single page in Moodle for every single user with a link to your thing. There are many other places to include links to your thing and most are automatically built from the navigation tree without forcing nodes to display in the flat navigation. For example, in the settings menu of a course, profile page, preferences page, reports, and so on.
+
+:::
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [Forum discussion - adding navigation to local plugins](https://moodle.org/mod/forum/discuss.php?d=170325&parent=753095)
diff --git a/versioned_docs/version-5.1/apis/core/preference/index.md b/versioned_docs/version-5.1/apis/core/preference/index.md
new file mode 100644
index 0000000000..b71b7a83fd
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/preference/index.md
@@ -0,0 +1,106 @@
+---
+title: Preference API
+tags:
+ - API
+ - User Preferences
+ - User
+---
+
+The Preference API is used for the storage and retrieval of user preferences. These preferences are stored in the database for users with an account, however for guests or users who are not currently logged in the preferences are stored in the Session.
+
+All of these functions operate on the current user by default, however you can specify the user you wish to operate on by passing a user ID or a moodle user object to the $user parameter. Normally this is used for state indicators, where it can be as simple as a yes or no, however you can also use it to store more complex user data, such as a serialized PHP object.
+
+It is considered good practice to abstain from storing default values as a user preference as this creates a lot of redundancy. Instead you should apply the default value at the code level if there is no stored value for a given preference.
+
+## Primary functions
+
+### get_user_preferences()
+
+This function can be used to fetch the value of a requested (via $name) preference, or if it doesn't exist the value given in the $default parameter will be returned. If you do not specify a $name then all preferences will be returned.
+
+```php
+get_user_preferences(
+ string $name = null,
+ mixed $default = null,
+ stdClass|int|null $user = null
+): mixed;
+```
+
+### set_user_preference()
+
+This function can be used to set the value of a preference named $name.
+
+```php
+set_user_preference(
+ string $name,
+ mixed $value,
+ stdClass|int|null $user = null
+): bool;
+```
+
+### set_user_preferences()
+
+This function takes an array or preferences containing the name and value for each preference. For each element in the array this function passes the keys and values of each element as the $name and $value parameters (respectively) in calls to `set_user_preferences()`.
+
+```php
+set_user_preferences(
+ array $preferences,
+ stdClass|int|null $user = null
+): bool;
+```
+
+### unset_user_preference()
+
+This deletes the requested preference, specified by the $name parameter.
+
+```php
+unset_user_preference(
+ string $name,
+ stdClass|int|null $user = null
+): bool;
+```
+
+## Example usage of the API
+
+```php title="Set a preference and then retrieve it"
+set_user_preference('foo_nameformat', 'long');
+
+// Returns the string - "long"
+get_user_preferences('foo_nameformat');
+```
+
+```php title="Set another preference and then retrieve all preferences"
+set_user_preference('foo_showavatars', 'no');
+
+// returns [
+// foo_nameformat => "long",
+// foo_showavatars => "no",
+// ];
+get_user_preferences();
+```
+
+```php title="Add an array of preferences and change foo_nameformat to short"
+$preferences = [
+ 'foo_displaynum' => '100',
+ 'foo_nameformat' => 'short',
+];
+
+set_user_preferences($preferences);
+
+// returns [
+// foo_nameformat => "short",
+// foo_showavatars => "no",
+// foo_displaynum => "100",
+// ];
+get_user_preferences();
+```
+
+```php title="Delete a preference"
+unset_user_preference('foo_showavatars');
+
+// returns [
+// foo_nameformat => "short",
+// foo_displaynum => "yes",
+// ];
+get_user_preferences();
+```
diff --git a/versioned_docs/version-5.1/apis/core/reportbuilder/_index/Actions.jpg b/versioned_docs/version-5.1/apis/core/reportbuilder/_index/Actions.jpg
new file mode 100644
index 0000000000..c8deb6d3b4
Binary files /dev/null and b/versioned_docs/version-5.1/apis/core/reportbuilder/_index/Actions.jpg differ
diff --git a/versioned_docs/version-5.1/apis/core/reportbuilder/filtering.md b/versioned_docs/version-5.1/apis/core/reportbuilder/filtering.md
new file mode 100644
index 0000000000..6a83eb1ad2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/reportbuilder/filtering.md
@@ -0,0 +1,329 @@
+---
+title: Setting default conditions
+tags:
+ - Report builder
+ - reports
+---
+
+When setting up a `datasource` it is possible to define the default condition values for it using the `get_default_condition_values()` method.
+
+The method requires that you return an array where the keys are formed in a specific way so it will link to the correct filter and condition form field. The names and number of condition form fields varies depending on both the specific filter type and the operator used.
+
+The keys used in the array all start with the name of the entity you are querying followed by a colon and the name of the filter, then an underscore followed by the form field you are defining; for example, assuming we are using default names, if you have a `course_category` entity and want to set a value for the `name` filter you would need to start the keys with `course_categotry:name`.
+
+Most filter types use an `operator` for field to define which of their operators is used, they are defined as constants in each filter type, for example the text filter type has `EQUAL_TO` so you will end up with `'course_categotry:name_operator' => text::EQUAL_TO`
+
+The operator you have defined will then determine which other form fields you will need to provide values for, in our example so far that would be `value` which would be defined as `'course_categotry:name_value' => 'The name of a category'` this would mean that only a category with that exact name would be returned by default.
+
+[Core report builder filter types](https://github.com/moodle/moodle/tree/main/reportbuilder/classes/local/filters) you can find what a filter type needs by looking at their `get_sql_filter()` method.
+
+## Text filters
+
+### Operators with no additional data
+
+- `ANY_VALUE`
+- `IS_EMPTY`
+- `IS_NOT_EMPTY`
+
+## Filtering types with a single value
+
+A string should be sent in `value` for:
+
+- `CONTAINS`
+- `DOES_NOT_CONTAIN`
+- `IS_EQUAL_TO`
+- `IS_NOT_EQUAL_TO`
+- `STARTS_WITH`
+- `ENDS_WITH`
+
+```php title="Examples of array key and value pairs for single value text filters"
+'my:text_operator' => text::CONTAINS,
+'my:text_value1' => 'Bob Dylan',
+'my:text2_operator' => text::IS_NOT_EQUAL_TO,
+'my:text2_value1' => 'This exact thing',
+```
+
+## Number filter
+
+### Operators with no additional data
+
+- `ANY_VALUE`
+- `IS_NOT_EMPTY`
+- `IS_EMPTY`
+
+### Operators with a single value
+
+A number should be passed in `value1` for:
+
+- `LESS_THAN`
+- `GREATER_THAN`
+- `EQUAL_TO`
+- `EQUAL_OR_LESS_THAN`
+- `EQUAL_OR_GREATER_THAN`
+
+```php title="Examples of array key and value pairs for single value number filters"
+'my:number_operator' => number::LESS_THAN,
+'my:number_value1' => 42,
+'my:number2_operator' => number::EQUAL_OR_GREATER_THAN,
+'my:number2_value1' => 15.6,
+```
+
+### Operators with complex data
+
+#### RANGE
+
+- `value1` number for the lower bound
+- `value2` number for the upper bound
+
+```php title="Examples of array key and value pairs for range values text filters"
+'my:number_operator' => number::RANGE,
+'my:number_value1' => 60,
+'my:number_value2' => 600,
+```
+
+## Date filter
+
+The date filter has several constants used to define the amount of time:
+
+- `DATE_UNIT_MINUTE`
+- `DATE_UNIT_HOUR`
+- `DATE_UNIT_DAY`
+- `DATE_UNIT_WEEK`
+- `DATE_UNIT_MONTH`
+- `DATE_UNIT_YEAR`
+
+### Operators with no additional data
+
+- `DATE_ANY`
+- `DATE_NOT_EMPTY`
+- `DATE_EMPTY`
+- `DATE_PAST`
+- `DATE_FUTURE`
+
+### Operators with one value
+
+The `unit` should be sent defining the amount of time that should be considered.
+
+- `DATE_CURRENT`
+
+```php title="Examples of array key and value pairs for single value date filters"
+'my:date_operator' => date::DATE_CURRENT,
+'my:date_unit' => date::DATE_UNIT_DAY,
+```
+
+### Operators with two values
+
+These operators can all accept two values:
+
+1. `value` - A integer greater than 0
+2. `unit` - The type of time unit
+
+- `DATE_LAST`
+- `DATE_NEXT`
+- `DATE_BEFORE`
+- `DATE_AFTER`
+
+```php title="Examples of array key and value pairs for two value date filters"
+'my:date_operator' => date::DATE_NEXT,
+'my:date_value' => 5,
+'my:date_unit' => date::DATE_UNIT_DAY,
+'my:date2_operator' => date::DATE_BEFORE,
+'my:date2_value' => 20,
+'my:date2_unit' => date::DATE_UNIT_MINUTE,
+```
+
+### Operators with complex values
+
+#### DATE_RANGE
+
+The date range should have a unix timestamp in one or more of the following:
+
+- `from`
+- `to`
+
+```php title="Examples of array key and value pairs for the range date filters"
+'my:date_operator' => date::DATE_RANGE,
+'my:date_from' => 1732593669,
+'my:date_to' => 1732893669,
+```
+
+## Select filter
+
+### Operators with no additional data
+
+- `ANY_VALUE`
+
+### Operators with one value
+
+The value of the select should be sent in `value` to:
+
+- `EQUAL_TO`
+- `NOT_EQUAL_TO`
+
+```php title="Examples of array key and value pairs for single value select filters"
+'my:select_operator' => select::EQUAL_TO,
+'my:select_value' => 10,
+'my:select2_operator' => select::NOT_EQUAL_TO,
+'my:select2_value' => 'textkey',
+```
+
+## Boolean select filter
+
+### Operators with no additional data
+
+- `ANY_VALUE`
+- `CHECKED`
+- `NOT_CHECKED`
+
+## Duration filter
+
+For units the filter uses the standard Moodle defines:
+
+- `MINSECS`
+- `HOURSECS`
+- `DAYSECS`
+- `WEEKSECS`
+
+It also allows the integer value 1 to represent seconds.
+
+### Operators with no additional data
+
+- `DURATION_ANY`
+
+### Operators with two values
+
+These operators can all accept two values:
+
+1. `value` - A integer greater than 0, this should be the number of the units that are included.
+2. `unit` - The type of time unit.
+
+- `DURATION_MAXIMUM`
+- `DURATION_MINIMUM`
+
+```php title="Examples of array key and value pairs for two value duration filters"
+'my:duration_operator' => duration::DURATION_MAXIMUM,
+'my:duration_value' => 10,
+'my:duration_unit' => 1,
+'my:duration2_operator' => duration::DURATION_MINIMUM,
+'my:duration_value' => 42,
+'my:duration_unit' => DAYSECS,
+```
+
+## Autocomplete filter
+
+This filter type does not use `operator`, it requires that `values` contains an array of keys that the autocompletion element would return.
+
+```php title="Examples of array key and value pairs for autocomplete filters"
+'my:autocomplete_values' => [1, 4, 6],
+'my:autocomplete2_values' => [42],
+```
+
+## Category filter
+
+### Operators with complex values
+
+Both the available operators for this have two values:
+
+1. `value` The database id of a `course_category` record
+2. `subcategories` A boolean to indicate if sub categories of the selected category should also be included.
+
+- `EQUAL_TO`
+- `NOT_EQUAL_TO`
+
+```php title="Examples of array key and value pairs for category filters"
+'my:category_operator' => category::EQUAL_TO,
+'my:category_value' => [142, 4753],
+'my:category_subcategories' => true,
+'my:category2_operator' => category::NOT_EQUAL_TO,
+'my:category2_value' => [1],
+'my:category2_subcategories' => false,
+```
+
+## Course_select filter
+
+This filter type does not use `operator`, it requires that `values` contains an array of database ids for courses.
+
+```php title="Examples of array key and value pairs for course filters"
+'my:course_values' => [1, 4, 6],
+'my:course2_values' => [42],
+```
+
+## Cohort filter
+
+This filter type does not use `operator`, it requires that `values` contains an array of database ids for cohorts.
+
+```php title="Examples of array key and value pairs for cohort filters"
+'my:cohort_values' => [1, 4, 6],
+'my:cohort2_values' => [42],
+```
+
+## Filesize filter
+
+The filter defines several units for filesize as constants:
+
+- `SIZE_UNIT_KILOBYTE`
+- `SIZE_UNIT_MEGABYTE`
+- `SIZE_UNIT_GIGABYTE`
+
+### Operators with no additional data
+
+- `ANY_VALUE`
+
+### Operators with two values
+
+1. `value1` The size of the file in the unit
+2. `unit` The type of unit (as defined by the filter constants)
+
+- `LESS_THAN`
+- `GREATER_THAN`
+
+```php title="Examples of array key and value pairs for two value file size filters"
+'my:filesize_operator' => filesize::LESS_THAN,
+'my:filesize_value1' => 50,
+'my:filesize_unit' => filesize::SIZE_UNIT_KILOBYTE,
+'my:filesize2_operator' => filesize::GREATER_THAN,
+'my:filesize2_value1' => 1,
+'my:filesize2_unit' => filesize::SIZE_UNIT_GIGABYTE,
+```
+
+## Tags filter
+
+### Operators with no additional data
+
+- `ANY_VALUE`
+- `NOT_EMPTY`
+- `EMPTY`
+
+### Operators with one value
+
+An array of tag database ids should be sent in `value` to:
+
+- `EQUAL_TO`
+- `NOT_EQUAL_TO`
+
+```php title="Examples of array key and value pairs for single value tag filters"
+'my:tags_operator' => tags::EQUAL_TO,
+'my:tags_value' => [142, 4753],
+'my:tags2_operator' => tags::NOT_EQUAL_TO,
+'my:tags2_value' => [1],
+```
+
+## User filter
+
+### Operators with no additional data
+
+- `USER_ANY`
+- `USER_CURRENT`
+
+### Operators with one value
+
+An array of user database ids should be sent in `value` to:
+
+- `USER_SELECT`
+
+```php title="Examples of array key and value pairs for single value user filters"
+'my:user_operator' => user::USER_SELECT,
+'my:user_value' => [142, 4753],
+'my:user2_operator' => user::USER_SELECT,
+'my:user2_value' => [1],
+```
diff --git a/versioned_docs/version-5.1/apis/core/reportbuilder/index.md b/versioned_docs/version-5.1/apis/core/reportbuilder/index.md
new file mode 100644
index 0000000000..30723aecf3
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/reportbuilder/index.md
@@ -0,0 +1,643 @@
+---
+title: Report builder API
+tags:
+ - Report builder
+ - reports
+---
+
+## Overview
+
+### Introduction
+
+The report builder API is a way of providing reporting data, with paging, filtering, exporting standardized across them in both system and custom reports. Once the groundwork is done in defining the report elements in entities, it's possible to implement them with minimal code just by adding entities to the report, and defining which elements you want to use from them.
+
+### Column
+
+#### Column overview
+
+Column instances define the data captured/displayed within a report column typically:
+
+- How the data is retrieved, either a simple SQL table.field fragment or an expression that returns a value
+- They type of data that is being retrieved (int, text, datetime, etc)
+- How that data should be presented in a report (for instance calling userdate() on datetime types)
+
+#### Column types
+
+- `Text`
+- `Integer` (Integer numbers)
+- `Float` (Decimal numbers)
+- `Timestamp` (Dates)
+- `Boolean` (Yes / No values)
+- `Longtext`
+
+#### Creating columns
+
+To create a new column, just create a new instance of [`reportbuilder/classes/local/report/column.php`](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/report/column.php) class with:
+
+```php
+* string $name
+* ?lang_string $title
+* string $entityname
+```
+
+And use:
+
+- `add_joins()` to add any extra SQL joins the column might need
+- `set_type()` to add the column type (All constant types are defined within the same column class)
+- `set_is_sortable()` to define if column can be sorted (For example we don't want to sort if the column shows just a picture)
+- `add_callback()` to format the output of the column
+- `add_field()` to add any db fields format callback might need
+- `set_is_deprecated()` used to mark a column as deprecated, indicating it will be removed in the future. This is required in core Moodle columns if you want delete or remove one from an entity because plugins may be using the fields.
+
+```php title="Example of code for creating a column"
+$columns[] = (new column(
+ 'starttime',
+ new lang_string('task_starttime', 'admin'),
+ $this->get_entity_name()
+ ))
+ ->add_joins($this->get_joins())
+ ->set_type(column::TYPE_TIMESTAMP)
+ ->add_field("{$tablealias}.timestart")
+ ->set_is_sortable(true)
+ ->add_callback([format::class, 'userdate']);
+```
+
+### Filter
+
+#### Filter overview
+
+Report filters can be defined for a report and allow users to narrow down (filter) the data that is displayed in a report:
+
+- They define the data being filtered, either a simple SQL fragment or expression.
+- The type of filtering being performed (int, text, datetime, etc).
+Filter types are extendable, allowing for the addition of many more as suit each use case.
+We have provided common ones that cover most use cases.
+
+:::note
+
+Filters & columns are entirely separate concepts in the report, and each can be used without a matching column/filter (that is to say, we can add a report filter for a user field without needing the column for the same field to be present in the report).
+
+:::
+
+#### Filter types
+
+- **Text** ([reportbuilder/classes/local/filters/text.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/text.php))
+- **Date** ([reportbuilder/classes/local/filters/date.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/date.php))
+- **Number** ([reportbuilder/classes/local/filters/number.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/number.php))
+- **Boolean Select** ([reportbuilder/classes/local/filters/boolean_select.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/boolean_select.php))
+- **Select** ([reportbuilder/classes/local/filters/select.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/select.php))
+- **Course selector** ([reportbuilder/classes/local/filters/course_selector.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/course_selector.php))
+- **Duration** ([reportbuilder/classes/local/filters/duration.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/duration.php))
+- **Tags** ([reportbuilder/classes/local/filters/tags.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/tags.php))
+- **Autocomplete** ([reportbuilder/classes/local/filters/autocomplete.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/autocomplete.php))
+- **Category** ([reportbuilder/classes/local/filters/category.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/filters/category.php))
+
+#### Creating filters
+
+To create a new filter, just create a new instance of **[reportbuilder/classes/local/report/filter.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/report/filter.php)** class with:
+
+```php
+* string $filterclass
+* string $name
+* lang_string $header
+* string $entityname
+* string $fieldsql = ''
+* array $fieldparams = []
+```
+
+```php title="Example of code for creating a filter"
+$filters[] = (new filter(
+ course_selector::class,
+ 'courseselector',
+ new lang_string('courses'),
+ $this->get_entity_name(),
+ "{$tablealias}.id"
+ ))
+ ->add_joins($this->get_joins());
+```
+
+### Entity
+
+#### Entity overview
+
+Entities are simply collections of report elements (currently columns and filters). They allow for common elements to be defined once, and then re-used in all reports - developers can choose to use as many or as few of the elements from each entity as required. We have provided user and course entities. They can be joined to reports using standard SQL query syntax.
+
+All report elements can be defined within the reports themselves - but entities mean it's much easier to create re-usable components, and will also help in the long term with custom reports.
+
+#### Create an entity
+
+To create an entity, the new entity class must extend **[reportbuilder/classes/local/entities/base.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/entities/base.php)** class and must include these methods:
+
+```php
+get_default_tables()
+get_default_entity_title()
+initialise()
+```
+
+If creating an entity for core Moodle data it should be located in one of the following namespaces:
+
+- `\core_reportbuilder\local\entities\`
+- `reportbuilder\local\entities` sub namespace of the subsystem the data is from, for example [`core_course\reportbuilder\local\entities\course_category`](https://github.com/moodle/moodle/blob/main/course/classes/reportbuilder/local/entities/course_category.php)
+
+When creating one for your own plugin you could technically put it anywhere, however to make it easy to find you should probably use a sub namespace like:
+
+- `reportbuilder\local\entities`
+- `reportbuilder\entities`
+
+For example if you were building a entity to show assignments you might make it's full namespace `\mod_assign\reportbuilder\entities\assignments`.
+
+##### get_default_tables()
+
+Defines all the database tables that must be present in the main SQL or joins added to the entity.
+
+##### get_default_entity_title()
+
+Defines the default title for this entity.
+
+##### initialise()
+
+This is where we **add** the entity columns and filters.
+
+#### Tips
+
+Always add all the entities joins to each of its columns and filters; also ensure you add them before any other joins.
+
+If you do not do add these joins when the entity is not being used as the main one there will be SQL errors when they are used.
+
+If you do not add them before other joins when the entity is not the main one in a report, you may find that any references to the primary table of the entity in your additional joins break.
+
+```php title="Adding entity joins to a column"
+$column->add_joins($this->get_joins())
+```
+
+When writing any SQL snippets you should always use the alias table aliases that are returned by the `get_table_alias()` method, this is because reports using the column can change the alias used by a table.
+
+```php title="Example of getting the alias for a table"
+$logalias = $this->get_table_alias('logstore_standard_log');
+$useralias = $this->get_table_alias('user');
+$fildname = "{$useralias).lastname";
+$join = "JOIN {user} {$useralias} ON {$useralias}.id = {$logalias}.relateduser"
+```
+
+#### Examples
+
+Check out these two entities as an example to start building reports:
+
+- **User entity**: [reportbuilder/classes/local/entities/user.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/entities/user.php)
+- **Course entity**: [reportbuilder/classes/local/entities/course.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/local/entities/course.php)
+
+### Actions
+
+
+
+Report actions can be defined in system reports to provide CTA links for each row in the report. Using `:placeholder` elements in the action URLs allows them to be specific to the row content. For example, to always provide a link to the current user/course of the current row
+
+```php
+ $this->add_action((new action(
+ new moodle_url('/admin/tasklogs.php', ['logid' => ':id']),
+ new pix_icon('e/search', ''),
+ [],
+ true,
+ new lang_string('viewtasklog', 'report_tasklogs')
+ )));
+```
+
+## System reports
+
+System reports are a consistent way of providing reporting data, with paging, filtering, exporting standardized across them. Once the groundwork is done in defining the report elements in entities, it's possible to implement them with minimal code just by adding entities to the report, and defining which elements you want to use from them
+
+### Create a new system report using entities
+
+To create a new system report just create a new class extending [reportbuilder/classes/system_report.php](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/system_report.php).
+
+The first method that we need is ***initialise()*** :
+
+```php
+/**
+* Initialise report, we need to set the main table, load our entities and set columns/filters
+*/
+protected function initialise(): void {
+```
+
+The initialise method needs to get the main entity, set the main table it needs to use and add the entity to the report:
+
+```php
+// Our main entity, it contains all of the column definitions that we need.
+$entitymain = new task_log();
+$entitymainalias = $entitymain->get_table_alias('task_log');
+
+$this->set_main_table('task_log', $entitymainalias);
+$this->add_entity($entitymain);
+```
+
+After that, if the report will have 'Actions', it needs to define the columns these actions will use:
+
+```php
+$this->add_base_fields("{$entitymainalias}.id");
+```
+
+Now, after adding our first entity, the report can use the columns and filters from it OR more entities can be added to the report using SQL joins:
+
+```php
+$entityuser = new user();
+$entituseralias = $entityuser->get_table_alias('user');
+$this->add_entity($entityuser->add_join(
+ "LEFT JOIN {user} {$entituseralias} ON {$entituseralias}.id = {$entitymainalias}.userid"
+));
+```
+
+Once all entities have been added it needs to define which columns it needs to show **in the order we need**:
+
+```php
+$columns = [
+ 'task_log:name',
+ 'task_log:type',
+ 'user:fullname',
+ 'task_log:starttime',
+];
+
+$this->add_columns_from_entities($columns);
+```
+
+After defining the columns, it needs to define all the filters (or empty array for no filters) that it will use:
+
+```php
+$filters = [
+ 'task_log:name',
+ 'task_log:result',
+ 'task_log:timestart',
+];
+
+$this->add_filters_from_entities($filters);
+```
+
+In case it needs actions for each report row, they can be defined like:
+
+```php
+// Action to download individual task log.
+$this->add_action((new action(
+ new moodle_url('/admin/tasklogs.php', ['logid' => ':id', 'download' => true]),
+ new pix_icon('t/download', ''),
+ [],
+ new lang_string('downloadtasklog', 'report_tasklogs')
+)));
+```
+
+:::info
+
+Note that the placeholders used here (:id in this example) have been previously added using **add_base_fields**();
+
+:::
+
+Once the whole report has been defined, is possible to set if the report will be downloadable or not:
+
+```php
+$this->set_downloadable(true);
+```
+
+### Use an entity
+
+### Override display name for a column
+
+It's possible to override the display name of a column, if you don't want to use the value provided by the entity.
+
+```php
+if ($column = $this->get_column('user:fullname')) {
+ $column->set_title(new lang_string('user', 'admin'));
+}
+```
+
+### Set a default initial sort direction
+
+It's possible to set a default initial sort direction for one column.
+
+```php
+$this->set_initial_sort_column('task_log:starttime', SORT_DESC);
+```
+
+### Examples
+
+Check out these two system reports as an example:
+
+- **Task logs**: [`admin/classes/reportbuilder/local/systemreports/task_logs.php`](https://github.com/moodle/moodle/blob/main/admin/classes/reportbuilder/local/systemreports/task_logs.php)
+- **Config changes**: [`report/configlog/classes/reportbuilder/local/systemreports/config_changes.php`](https://github.com/moodle/moodle/blob/main/report/configlog/classes/reportbuilder/local/systemreports/config_changes.php)
+
+## Custom reports
+
+The custom reporting interface allows reports to be built with a custom view for users, Moodle and plugins can define data sources that provide the basis for the reports that users can make using the system.
+
+### Create a new data source using entities
+
+To create a data source you need to extend [`\core_reportbuilder\datasource`](https://github.com/moodle/moodle/blob/main/reportbuilder/classes/datasource.php). Your class must be located in the `reportbuilder\datasource` namespace of your plugin or the Moodle subsystem it is for.
+
+The first method you need to build is `initialise()`
+
+```php
+/**
+* Initialise report, we need to set the main table, load our entities and set columns/filters
+*/
+protected function initialise(): void {
+```
+
+The initialise method needs to get the main entity, set the main table it needs to use and add the entity to the report:
+
+```php
+// Our main entity, it contains all of the column definitions that we need.
+$entitymain = new task_log();
+$entitymainalias = $entitymain->get_table_alias('task_log');
+
+$this->set_main_table('task_log', $entitymainalias);
+$this->add_entity($entitymain);
+```
+
+Now, after adding our first entity, the report can use the columns and filters from it OR more entities can be added to the report using SQL joins:
+
+```php
+$entityuser = new user();
+$entityuseralias = $entityuser->get_table_alias('user');
+$entityuser->add_join(
+ "LEFT JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = {$entitymainalias}.userid"
+);
+$this->add_entity($entityuser);
+```
+
+If you are adding an entity that does not directly join to the entity containing the main table you will need to add the joins to all the intermediate entities to it, without this if a user adds a column from the table to a custom report and has not also added a column from the intermediate table there will be an error.
+
+```php
+$entitycourse = new course();
+$entitycoursealias = $entityuser->get_table_alias('course');
+$entitycourse->add_join("JOIN {course} {$entitycoursealias} ON {$entitycoursealias}.id = {$entityactivity}.course");
+
+$entitycategory = new course_category();
+$entitycategoryalias = $entityuser->get_table_alias('course_category');
+$entitycategory->add_joins($entitycourse->get_joins());
+$entitycategory->add_join("JOIN {course_category} {$entitycategoryalias} ON {$entitycategoryalias}.id = {$entitycoursealias}.category");
+```
+
+If you are using the same sort of entity twice, or they happen to have clashing aliases in another entity, you can set the alias for a table in an entity:
+
+```php
+$entityuser2 = new user();
+$entityuser2->set_table_alias('user', 'u2');
+```
+
+Next you need to add the columns, filters and conditions that a user can add to the custom report. If you want everything you can use:
+
+```php
+$this->add_all_from_entities();
+```
+
+### Setup report name
+
+You will need to specify the name that is displayed to the end user for the data source.
+
+```php
+ /**
+ * Return user friendly name of the report source
+ *
+ * @return string
+ */
+ public static function get_name(): string {
+ return get_string('tasklogs', 'core_admin');
+ }
+```
+
+### Setup default columns
+
+Once all entities have been added you need to define which columns it will show by default **they will be displayed in the order you define them**, by implementing the `get_default_columns()` method:
+
+```php
+/**
+ * Return the columns that will be added to the report upon creation
+ *
+ * @return string[]
+ */
+public function get_default_columns(): array {
+ return [
+ 'task_log:name',
+ 'task_log:starttime',
+ 'task_log:duration',
+ 'task_log:result',
+ ];
+}
+```
+
+You may also optionally define the sorting that will be applied to the default report, **it must only use default columns** by overriding the `get_default_column_sorting()` method:
+
+```php
+/**
+ * Return the column sorting that will be added to the report upon creation
+ *
+ * @return int[]
+ */
+public function get_default_column_sorting(): array {
+ return [
+ 'task_log:starttime' => SORT_DESC,
+ ];
+}
+```
+
+### Setup default filters
+
+The filters allow the end user of the report to only see a subset of the data the report will normally show. You need to define the default setup for this using the `get_default_filters()` method.
+
+```php
+/**
+ * Return the filters that will be added to the report upon creation
+ *
+ * @return string[]
+ */
+public function get_default_filters(): array {
+ return [
+ 'task_log:timestart',
+ 'task_log:result',
+ ];
+}
+```
+
+### Setup conditions
+
+The conditions allow the user creating the report to define which data it will return. You need to define the default setup for this using the `get_default_conditions()` method.
+
+```php
+/**
+ * Return the conditions that will be added to the report upon creation
+ *
+ * @return string[]
+ */
+public function get_default_conditions(): array {
+ return [
+ 'task_log:type',
+ 'task_log:timestart',
+ 'task_log:result',
+ ];
+}
+```
+
+You may also optionally define the [initial values for any of the default conditions](./filtering.md) by overriding the `get_default_condition_values()` method.
+
+```php
+/**
+ * Return the condition values that will be set for the report upon creation
+ *
+ * @return array
+ */
+public function get_default_condition_values(): array {
+ return [
+ 'task_log:type_operator' => select::EQUAL_TO,
+ 'task_log:type_value' => \core\task\database_logger::TYPE_SCHEDULED,
+ ];
+}
+```
+
+### Adding unit tests
+
+Data sources have a specific type of testcase `\core_reportbuilder_testcase` that provides several useful utility methods that will help you ensure that the data source and it's entities are working correctly.
+
+```php
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
+
+/**
+ * Unit tests for course categories datasource
+ *
+ * @package core_course
+ * @covers \core_course\reportbuilder\datasource\categories
+ * @copyright 2023 Paul Holden
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class categories_test extends core_reportbuilder_testcase {
+```
+
+The `core_reportbuilder_testcase` will not autoload so we first ensure that it is loaded with the `require_once`. After this there are a few things you should look to test:
+
+1. That the default report setup works properly
+2. That you can add all the columns that are not defined in the default report
+3. That the filters work
+4. Add the stress tests
+
+#### Testing the default report
+
+For this you want a step that sets up will return enough data allows you test all the ordering you have configured for the report.
+
+```php
+ /**
+ * Test default datasource
+ */
+ public function test_datasource_default(): void {
+ $this->resetAfterTest();
+
+ $category = $this->getDataGenerator()->create_category(['name' => 'Zoo', 'idnumber' => 'Z01']);
+ $course = $this->getDataGenerator()->create_course(['category' => $category->id]);
+
+ /** @var core_reportbuilder_generator $generator */
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
+ $report = $generator->create_report(['name' => 'My report', 'source' => categories::class, 'default' => 1]);
+
+ $content = $this->get_custom_report_content($report->get('id'));
+ $this->assertCount(2, $content);
+
+ // Default columns are name, idnumber, coursecount. Sorted by name ascending.
+ $this->assertEquals([
+ [get_string('defaultcategoryname'), '', 0],
+ [$category->get_formatted_name(), $category->idnumber, 1],
+ ], array_map('array_values', $content));
+ }
+```
+
+In this example an additional category has been added to Moodle, the category has had a course added to it so that the count of courses in the category can also be tested.
+
+It then creates a custom report and gets it's data before testing:
+
+- It has the expected number of rows
+- Testing after stripping out the array keys that the data returned is in the correct order and formatted correctly
+
+#### Testing the non-default columns and filtering
+
+You can create blank report can be created by passing `'default' => 0` to the `create_report()` method of the `core_reportbuilder` data generator:
+
+```php
+$report = $generator->create_report(['name' => 'My report', 'source' => categories::class, 'default' => 0]);
+```
+
+to add columns of data to the report you would use the `create_column()` method:
+
+```php
+$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:path']);
+```
+
+it is possible to add sorting to the column by passing additional data in the array, for example:
+
+```php
+$generator->create_column(
+ [
+ 'reportid' => $report->get('id'),
+ 'uniqueidentifier' => 'course_category:path'
+ 'sortenabled' => true,
+ 'sortdirection' => SORT_ASC,
+ 'sortorder' => 1,
+ ]
+);
+```
+
+This would cause the column to be sorted ascending. You need to use `sortorder` to decide on the priority it is given in the sorting, with lower numbers being given priority.
+
+To add a filter to the report you would use the `create_filter()` method of the `core_reportbuilder` data generator:
+
+```php
+$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
+```
+
+This will add the filter for the user's firstname. When you generate the report you will need to pass an array of conditions as the third parameter:
+
+```php
+$filtervalues = [
+ 'user:firstname_operator' => text::IS_EQUAL_TO,
+ 'user:firstname_value' => 'Pedro',
+];
+$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
+```
+
+This would cause the report to return results for users with the first name of _Pedro_ only.
+
+#### The stress test
+
+The stress test uses helper methods which:
+
+- Add and remove every column individually from a report and ensures that is still returns data without error, it also tests the column can be sorted if that has been enabled.
+- Individually aggregates a report on each column
+- Individually applies each filter
+
+None of these tests checks that the data is what you want, but will ensure that all your joins are setup correctly for the `datasource` to work without errors when manipulating it via the custom report interface.
+
+```php
+ /**
+ * Stress test datasource
+ *
+ * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
+ */
+ public function test_stress_datasource(): void {
+ if (!PHPUNIT_LONGTEST) {
+ $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
+ }
+
+ $this->resetAfterTest();
+
+ $category = $this->getDataGenerator()->create_category(['name' => 'My category']);
+
+ $this->datasource_stress_test_columns(categories::class);
+ $this->datasource_stress_test_columns_aggregation(categories::class);
+ $this->datasource_stress_test_conditions(categories::class, 'course_category:idnumber');
+ }
+```
+
+The test first sets up some data that the data source can return, it then uses three helper methods provided by `core_reportbuilder_testcase`
+
+The first parameter for each method is the fully qualified class name of the `datasource` that should be tested. `datasource_stress_test_conditions()` has a second parameter that must be the name of a column from the `datasource`.
+
+#### Unit test examples
+
+- **Course categories** [`/course/tests/reportbuilder/datasource/categories_test.php`](https://github.com/moodle/moodle/blob/main/course/tests/reportbuilder/datasource/categories_test.php)
+- **Badges** [`/badges/tests/reportbuilder/datasource/badges_test.php`](https://github.com/moodle/moodle/blob/main/badges/tests/reportbuilder/datasource/badges_test.php)
diff --git a/versioned_docs/version-5.1/apis/core/user/index.md b/versioned_docs/version-5.1/apis/core/user/index.md
new file mode 100644
index 0000000000..06b30c1b59
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/core/user/index.md
@@ -0,0 +1,123 @@
+---
+title: User-related APIs
+tags:
+ - User
+documentationDraft: true
+---
+
+This is a collection of miscellaneous APIs that can help with doing things with lists of users.
+
+:::note
+
+Please note that, in many cases, more specific APIs may be more appropriate, for example:
+
+- [Access API](../../subsystems/access.md)
+- [Groups API](../../subsystems/group/index.md)
+- [Enrolment API](../../subsystems/enrol.md)
+
+:::
+
+## User field display
+
+The [User fields](https://docs.moodle.org/dev/User_fields) class is mainly used when displaying tables of data about users. It indicates which extra fields (e.g. email) should be displayed in the current context based on the permissions of the current user. It also provides ways to get the necessary data from a query, and to obtain other generally-useful fields for user names and pictures.
+
+
+
+### `core_user::get_profile_picture`
+
+Generate user picture.
+
+- **`user`** - The person to get details of.
+- **`context`** - The context will be used to determine the visibility of the user's picture.
+- **`options`** - Display options to be overridden:
+ - `courseid`: course id of user profile in link
+ - `size`: 35 (size of image)
+ - `link`: true (make image clickable - the link leads to user profile)
+ - `popup`: false (open in popup)
+ - `alttext`: true (add image alt attribute)
+ - `class`: image class attribute (default `userpicture`)
+ - `visibletoscreenreaders` true (whether to be visible to screen readers)
+ - `includefullname`: false (whether to include the user's full name together with the user picture)
+ - `includetoken`: false (whether to use a token for authentication. True for current user, int value for other user id)
+
+
+
+### `core_user::get_profile_url`
+
+Generate profile url.
+
+- **`user`** - The person to get details of.
+- **`context`** - The context will be used to determine the visibility of the user's full name.
+
+
+
+### `core_user::get_fullname`
+
+Generate user full name.
+
+- **`user`** - The person to get details of.
+- **`context`** - The context will be used to determine the visibility of the user's profile url.
+- **`options`** - Can include: override - if true, will not use forced firstname/lastname settings
+
+## User fields definition
+
+To guarantee the sanity of the data inserted into Moodle and avoid security bugs, new user fields definition methods have been created for use in Moodle 3.1 onwards. The purpose of these new methods is to create a central point of user fields data validation and guarantee all data inserted into Moodle will be cleaned against the correct parameter type. Another goal of this new API is to create consistency across Moodle core and avoid different parameter validations for the same user fields. For now on, user data must validate against the user field, not using clean_param() directly.
+
+### `$propertiescache`
+
+Cached information of each user field and its attributes filled by the fill_properties_cache.
+
+### `fill_properties_cache()`
+
+The main method of the user definition is to keep the definition of each user field and its properties. It verifies if the **`core_user::$propertiescache`** is already filled and caches all user fields attributes into this same attribute.
+Each field matches the exact field name on the user table. That said, every new field added to the user table should be added to fill_properties_cache $fields array, otherwise it won't be validated or cleaned.
+Each field has four possible properties, being choices and default optional:
+
+- **`null`** - Whether the field is NULL or NOT_NULL, it SHOULD NOT be used as form validation, as many fields in the user table have NOT_NULL property but have the default value as (``).
+- **`type`** - The expected parameter type (PARAM_*) to be used as validation and sanitizing.
+- **`choices`** - A list of accepted values of that field. For example the list of the available countries, timezones, calendar type etc.
+- **`default`** - The default value in case the user field didn't pass the validation or cleaned and we must set the default value. For example if the user country is invalid and it is not in the list of choices, set $CFG->country.
+
+### `validate()`
+
+A static method to validate user fields, accepts an array or the user object as parameter, validate each parameter individually and can return true if all user data is correct or an array of validation errors. The purpose of this method is to just validate the user data, it won't do any cleaning of the data.
+
+### `clean_data()`
+
+A static method that has the purpose of clean the user data and return the same user array/object. It receives an array with user data or a user object as parameter and it checks if the data is in the list of choices and if the property has a default value and clean the data if the user object doesn't have a choices property.
+It will display a debugging message if one the operations above has problems.
+
+### `clean_field()`
+
+A static method to clean a single user field. It has two parameters, the data to be cleaned and its user field. The behaviour of the method is similar to the clean_data. It will do the validations and cleaning and can display a debug message if an error has been found. It returns the cleaned data.
+
+### `get_property_type()`
+
+A helper method to get the type of the property. It receives the user field name as parameter and if it doesn't exist will throw an exception. If the property has been found, it will return its type.
+
+### `get_property_null()`
+
+A helper method to get the null property of the user field. It receives the user field name as parameter and if it doesn't exist it throws an exception. If the property has been found, it will return the null value.
+
+### `get_property_choices()`
+
+A helper method to get the list of choices of a user field. It receives the user field name as parameter and if it doesn't exist will throw an exception. If the property has been found, it will return the list of accepted values.
+
+### `get_property_default()`
+
+A helper method to get the default value of a property. It receives the user field name as parameter and if it doesn't exist or if it doesn't have a default attribute will throw an exception. If the property has been found, it will return its default value.
+
+## User selector
+
+The base class `user_selector_base` defined in `user/selector/lib.php`, which you can subclass to make a widget that lets you select users in an AJAX-y way. It is used, for example, on the Add group members page. The best way to learn how to use it is to search the code for other users, and see how they work. The base class also has good PHPdoc comments.
+
+## Sorting lists of users
+
+When you fetch a list of users from the database, they should always be sorted consistently, by using the `users_order_by_sql` function to generate the order-by clause. Again, the best way to see how that works is to search the code for existing uses.
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [Access API](../../subsystems/access.md)
+- [Groups API](../../subsystems/group/index.md)
+- [Enrolment API](../../subsystems/enrol.md)
diff --git a/versioned_docs/version-5.1/apis/plugintypes/ai/index.md b/versioned_docs/version-5.1/apis/plugintypes/ai/index.md
new file mode 100644
index 0000000000..d888c09fe2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/ai/index.md
@@ -0,0 +1,62 @@
+---
+title: AI Plugins
+tags:
+ - AI
+ - LLM
+ - Provider
+ - Placement
+---
+
+The [AI subsystem](/apis/subsystems/ai/index.md) provides a way for developers to integrate external AI services into Moodle LMS.
+
+The design of the AI subsystem features two distinct plugin types:
+
+- [Provider plugins](/apis/plugintypes/ai/provider.md)
+- [Placement plugins](/apis/plugintypes/ai/placement.md)
+
+This design allows for independent development of each plugin. The Provider plugin is not aware of the
+Placement plugin, and the Placement plugin is not aware of the Provider plugin. All communication between the two plugins
+travels through the Manager.
+
+```mermaid
+sequenceDiagram
+ User->>Placement: Input action
+ Placement->>Manager: Action data
+ Manager->>Provider: Formatted action data
+ Provider->>External AI: Send data
+ External AI-->>Provider: Receive response
+ Provider-->>Manager: Response data
+ Manager-->>Placement: Formatted response data
+ Placement-->>User: Action completed
+```
+
+### Provider plugins
+
+Providers are the interface between the [AI subsystem](/apis/subsystems/ai/index.md) and external AI.
+Their focus is on formatting actions, passing them to the external AI system, and providing the response.
+
+Currently, Moodle supports the following AI Providers in core:
+
+- OpenAI `aiprovider_openai`
+- Azure AI `aiprovider_azureai`
+
+See the [Providers](/apis/plugintypes/ai/provider.md) documentation for more information
+on developing Provider plugins.
+
+### Placement plugins
+
+Placements provide a consistent UX and UI for users when they use AI backed functionality (e.g. generating an image).
+
+Placement plugins leverage the functionality of the other components of the [AI subsystem](/apis/subsystems/ai/index.md).
+This means plugin authors can focus on how users interact with the AI functionality, without needing to
+implement the AI functionality itself.
+
+Because Placements are plugins in their own right, it allows for greater flexibility in how AI functionality is presented to users.
+
+Currently, Moodle supports the following AI Placements:
+
+- Course Assistance `aiplacement_courseassist`
+- HTML Text Editor `aiplacement_editor`
+
+See the [Placements](/apis/plugintypes/ai/placement.md) documentation for more information
+on developing Placement plugins.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/ai/placement.md b/versioned_docs/version-5.1/apis/plugintypes/ai/placement.md
new file mode 100644
index 0000000000..78d5d2592b
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/ai/placement.md
@@ -0,0 +1,170 @@
+---
+title: Placements
+tags:
+ - AI
+ - LLM
+ - Placement
+---
+
+Placements provide a consistent UX and UI for users when they use AI backed functionality (e.g. generating an image).
+
+Placement plugins leverage the functionality of the other components of the [AI subsystem](/apis/subsystems/ai/index.md).
+This means plugin authors can focus on how users interact with the AI functionality, without needing to
+implement the AI functionality itself.
+
+Because Placements are plugins in their own right, it allows for greater flexibility in how AI functionality is presented to users.
+
+Outgoing data from the Placement plugin travels via the Manager `core_ai\manager`.
+The Manager is the connective tissue between the [Provider](/apis/plugintypes/ai/provider.md) and the Placement plugins.
+Likewise, all responses from the Provider plugin are handed back to the Manager before being passed to the Placement plugin.
+
+:::warning The Golden Rule:
+
+Placements **do not** know about Providers, and Providers **do not** know about Placements.
+Everything should go via the Manager.
+
+:::
+
+## Class implementation
+
+Placements are defined as classes in their own namespace according to their plugin name.
+The naming convention for a Placement class is `aiplacement_`.
+For example: `aiplacement_editor` (with a corresponding namespace).
+
+Each Placement **must** inherit from the `\core_ai\placement` abstract class.
+They must also implement the following methods:
+
+**`get_action_list(): array`**
+
+This is the list of Actions that are supported by this Placement, for example the `aiplacement_editor` plugin defines this as:
+
+```php
+public function get_action_list(): array {
+ return [
+ \core_ai\aiactions\generate_text::class,
+ \core_ai\aiactions\generate_image::class,
+ ];
+}
+```
+
+## Capabilities and permissions
+
+Placements provide a way for a user to carry out an Action.
+Placements are responsible for determining who can use a particular Action, and where it can be used.
+It is not the job of Providers to determine access.
+
+```php
+$capabilities = [
+ 'aiplacement/editor:generate_image' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ 'teacher' => CAP_ALLOW,
+ 'student' => CAP_ALLOW,
+ ],
+ ],
+...
+```
+
+## Action processing
+
+The following is the basic workflow in order for a placement to have an action processed for a user request:
+
+```mermaid
+sequenceDiagram
+ Placement->>Manager: New action
+ Manager->>Provider: Process action
+ Provider->>Manager: Get response
+ Manager->>Placement: Pass response
+```
+
+### Step 1
+
+- The Placement instantiates a new Action object of type they wish to use.
+- The Action must be instantiated with the required data. Each action will define what configuration it needs. As an example:
+
+```php
+// Prepare the action.
+$action = new \core_ai\aiactions\generate_image(
+ contextid: $contextid,
+ userid: $USER->id,
+ prompttext: $prompttext,
+ quality: $quality,
+ aspectratio: $aspectratio,
+ numimages: $numimages,
+ style: $style,
+);
+```
+
+### Step 2
+
+- The Placement then instantiates the Manager class `core_ai\manager`.
+- The Manager calls `process_action()`, passing in the configured Action object. As an example:
+
+```php
+// Send the action to the AI manager.
+$manager = \core\di::get(\core_ai\manager::class);
+$response = $manager->process_action($action);
+```
+
+### Step 3
+
+- The `process_action()` method then returns a response object (instance of `responses\response_base`).
+- It is up to the Placement to check for success/failure of the response and pass the result back to the
+ user.
+
+## Plugin structure
+
+The Placement plugins reside in the `ai/placement` directory.
+
+Each Placement is in a separate subdirectory and consists of a number of mandatory and supportive files that will
+be necessary for development.
+
+
+ The typical directory layout for the Placement plugin, using the Editor Placement as an example:
+
+```console
+.
+├── classes
+│ ├── external
+│ │ ├── generate_image.php
+│ │ └── generate_text.php
+│ ├── placement.php
+│ ├── privacy
+│ │ └── provider.php
+│ └── utils.php
+├── db
+│ ├── access.php
+│ └── services.php
+├── lang
+│ └── en
+│ └── aiplacement_editor.php
+├── tests
+│ └── utils_test.php
+└── version.php
+```
+
+
+
+## Settings
+
+Settings for the Placement should be defined in the `settings.php` file.
+Each Placement plugin should create a new admin settings page using `core_ai\admin\admin_settingspage_provider` class.
+
+This is the same for Provider plugins, for example:
+
+```php
+use core_ai\admin\admin_settingspage_provider;
+
+if ($hassiteconfig) {
+ // Placement specific settings heading.
+ $settings = new admin_settingspage_provider(
+ 'aiplacement_courseassist',
+ new lang_string('pluginname', 'aiplacement_courseassist'),
+ 'moodle/site:config',
+ true,
+ );
+...
+```
diff --git a/versioned_docs/version-5.1/apis/plugintypes/ai/provider.md b/versioned_docs/version-5.1/apis/plugintypes/ai/provider.md
new file mode 100644
index 0000000000..0420c73874
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/ai/provider.md
@@ -0,0 +1,476 @@
+---
+title: Providers
+tags:
+ - AI
+ - LLM
+ - Provider
+---
+
+Providers are the interface between the [AI subsystem](/apis/subsystems/ai/index.md) and external AI.
+Their focus should be on converting the data requested into the format needed
+by the external AI, and then correctly providing the response back.
+
+Incoming data to the Provider plugin arrives via the Manager `core_ai\manager`.
+The Manager is the connective tissue between the Provider and the [Placement](/apis/plugintypes/ai/placement.md) plugins.
+Likewise, all responses from the Provider plugin are handed back to the Manager before being passed to the Placement plugin.
+
+A Provider plugin allows many "provider instances" to be defined, each of which can support a different set of configurations.
+This facilitates having providers for specific tasks. So, you can use a more efficient model for lightweight tasks like summarisation, and a more fully featured model for text generation. Another example is defaulting to using a cheaper model with a lower token limit, and then falling back to a more expensive model if a request is too large for the default model.
+
+:::warning The Golden Rule:
+
+Placements **do not** know about Providers, and Providers **do not** know about Placements.
+Everything should go via the Manager.
+
+:::
+
+## Class implementation
+
+Providers are defined as classes in their own namespace according to their plugin name.
+The naming convention for a Provider class is `aiprovider_`.
+For example: `aiprovider_openai`, or `aiprovider_azureai` (with a corresponding namespace).
+
+Each Provider **must** inherit from the `\core_ai\provider` abstract class.
+
+### Required Methods
+
+They must also implement the following methods:
+
+**`get_action_list(): array`**
+
+This is the list of Actions that are supported by this Provider, for example the `aiprovider_openai` plugin defines this as:
+
+```php
+public static function get_action_list(): array {
+ return [
+ \core_ai\aiactions\generate_text::class,
+ \core_ai\aiactions\generate_image::class,
+ \core_ai\aiactions\summarise_text::class,
+ ];
+}
+```
+
+**`is_provider_configured(): bool`**
+
+Each provider will need to specify what it takes to be considered as configured.
+It is likely that each provider will have a set of keys necessary to access the external AI API.
+
+The `is_provider_configured()` must return `true` for UI component visibility and functionality. If not overridden, it will
+return `false` by default.
+
+For example, the `aiprovider_azureai` provider checks values are set for `$this->apikey` and `$this->apiendpoint` and returns
+the result.
+
+```php
+public function is_provider_configured(): bool {
+ return !empty($this->config['apikey']) && !empty($this->config['endpoint']);
+}
+```
+
+## Process classes
+
+For each action supported by the provider, the provider plugin **must** implement a `process_` class,
+where `` is the name of the action. For example: `process_generate_image`.
+
+Every process action class **must** inherit from the `\core_ai\process_base` abstract class.
+
+The process action class **must** implement a `process()` method. This method is responsible for
+converting the data requested by an Action into the format needed by the external AI services API,
+and then correctly providing the response back from the AI in an Action Response object.
+
+The process action classes and process method are expected by the manager to exist and be callable.
+
+As most provider plugins will support more than one action, it is recommended to create an
+`abstract_processor` class that inherits from the `\core_ai\process_base` class and then have each
+process action class inherit from this abstract class.
+
+For example, the `aiprovider_openai` plugin defines an `abstract_processor` class that inherits from
+the `\core_ai\process_base` class and then the `process_generate_image`, `process_generate_text` and
+`process_summarise_text` classes inherit from this abstract class.
+
+This can be visualised as follows:
+
+```mermaid
+graph TD;
+ A[process_base] --> B[abstract_processor]
+ B[abstract_processor] --> C[process_generate_image]
+ B --> D[process_generate_text]
+ B --> E[process_summarise_text]
+ ```
+
+Apart from this, Providers are free to define their own structure. It should be kept in mind that Providers
+are designed to be a 'thin wrapper' around the external AI systems API. They shouldn't store data,
+or have their own UI elements (beyond what is required for configuration).
+
+## Plugin structure
+
+Provider plugins reside in the `ai/provider` directory.
+
+Each Provider is in a separate subdirectory and consists of a number of mandatory files and any other
+files the developer is going to use.
+
+
+ The typical directory layout for the Provider plugin, using OpenAI Provider as an example:
+
+```console
+.
+├── amd
+│ ├── build
+│ │ ├── modelchooser.min.js
+│ │ └── modelchooser.min.js.map
+│ └── src
+│ └── modelchooser.js
+├── classes
+│ ├── abstract_processor.php
+│ ├── aimodel
+│ │ ├── gpt4o.php
+│ │ └── o1.php
+│ ├── form
+│ │ ├── action_form.php
+│ │ ├── action_generate_image_form.php
+│ │ └── action_generate_text_form.php
+│ ├── helper.php
+│ ├── hook_listener.php
+│ ├── privacy
+│ │ └── provider.php
+│ ├── process_generate_image.php
+│ ├── process_generate_text.php
+│ ├── process_summarise_text.php
+│ └── provider.php
+├── db
+│ └── hooks.php
+├── lang
+│ └── en
+│ └── aiprovider_openai.php
+├── tests
+│ ├── fixtures
+│ │ ├── image_request_success.json
+│ │ ├── test.jpg
+│ │ └── text_request_success.json
+│ ├── process_generate_image_test.php
+│ ├── process_generate_text_test.php
+│ ├── process_summarise_text_test.php
+│ └── provider_test.php
+└── version.php
+
+```
+
+
+
+## Provider Settings
+
+Settings for the Provider are defined as a [Hook](/apis/core/hooks/index.md).
+Each Provider plugin should create a new `classes/hook_listener.php` file. This file should contain a class with a static method that defines the hook callback.
+create a new admin settings page using `core_ai\admin\admin_settingspage_provider` class. This class should implement a `set_form_definition_for_aiprovider_` method.
+
+This method should define the settings form for the provider plugin, using the provided `mform` object.
+
+For example, the `aiprovider_openai` plugin defines this:
+
+```php
+namespace aiprovider_openai;
+use core_ai\hook\after_ai_provider_form_hook;
+
+class hook_listener {
+
+ /**
+ * Hook listener for the Open AI instance setup form.
+ *
+ * @param after_ai_provider_form_hook $hook The hook to add to the AI instance setup.
+ */
+ public static function set_form_definition_for_aiprovider_openai(after_ai_provider_form_hook $hook): void {
+ if ($hook->plugin !== 'aiprovider_openai') {
+ return;
+ }
+
+ $mform = $hook->mform;
+
+ // Required setting to store OpenAI API key.
+ $mform->addElement(
+ 'passwordunmask',
+ 'apikey',
+ get_string('apikey', 'aiprovider_openai'),
+ ['size' => 75],
+ );
+ $mform->addHelpButton('apikey', 'apikey', 'aiprovider_openai');
+ $mform->addRule('apikey', get_string('required'), 'required', null, 'client');
+
+ // Setting to store OpenAI organization ID.
+ $mform->addElement(
+ 'text',
+ 'orgid',
+ get_string('orgid', 'aiprovider_openai'),
+ ['size' => 25],
+ );
+ $mform->setType('orgid', PARAM_TEXT);
+ $mform->addHelpButton('orgid', 'orgid', 'aiprovider_openai');
+
+ }
+
+}
+```
+
+Because a hook is used to define the settings, Provider plugins also need to have a `db/hooks.php` file to register its hook callback.
+The specified plugin callback method is called whenever the provider instance is chosen in the provider administration settings.
+
+For example, the `aiprovider_openai` plugin defines this:
+
+```php
+defined('MOODLE_INTERNAL') || die();
+
+$callbacks = [
+ [
+ 'hook' => \core_ai\hook\after_ai_provider_form_hook::class,
+ 'callback' => \aiprovider_openai\hook_listener::class . '::set_form_definition_for_aiprovider_openai',
+ ],
+];
+```
+
+## Action Settings
+
+Each of the actions that a provider plugin supports can have its own settings.
+If an action requires additional settings, the provider class for the plugin should override the `get_action_settings()` method.
+The method must return an instance of `core_ai\form\action_settings_form`.
+
+For example, the `aiprovider_openai` plugin defines this:
+
+```php
+#[\Override]
+public static function get_action_settings(
+ string $action,
+ array $customdata = [],
+): action_settings_form|bool {
+ $actionname = substr($action, (strrpos($action, '\\') + 1));
+ $customdata['actionname'] = $actionname;
+ $customdata['action'] = $action;
+ if ($actionname === 'generate_text' || $actionname === 'summarise_text') {
+ return new form\action_generate_text_form(customdata: $customdata);
+ } else if ($actionname === 'generate_image') {
+ return new form\action_generate_image_form(customdata: $customdata);
+ }
+
+ return false;
+}
+```
+
+The actual settings form for the actions should be defined in the `classes/form` directory.
+For example, the `aiprovider_openai` plugin defines `action_generate_text_form` and `action_generate_image_form` classes.
+These classes should extend the `core_ai\form\action_settings_form` class, and must implement the `definition()` method.
+
+For example, the `aiprovider_openai` plugin defines this:
+
+```php
+class action_generate_text_form extends action_settings_form {
+ #[\Override]
+ protected function definition() {
+ $mform = $this->_form;
+ $actionconfig = $this->_customdata['actionconfig']['settings'] ?? [];
+ $returnurl = $this->_customdata['returnurl'] ?? null;
+ $actionname = $this->_customdata['actionname'];
+ $action = $this->_customdata['action'];
+ $providerid = $this->_customdata['providerid'] ?? 0;
+
+ // Action model to use.
+ $mform->addElement(
+ 'text',
+ 'model',
+ get_string("action:{$actionname}:model", 'aiprovider_openai'),
+ 'maxlength="255" size="20"',
+ );
+ $mform->setType('model', PARAM_TEXT);
+ $mform->addRule('model', null, 'required', null, 'client');
+ $mform->setDefault('model', $actionconfig['model'] ?? 'gpt-4o');
+ $mform->addHelpButton('model', "action:{$actionname}:model", 'aiprovider_openai');
+
+...
+```
+
+## Predefined models
+
+Pre-defined models allow AI providers to define specific model configurations that can be selected by users.
+This provides a better user experience by offering known model options with appropriate default parameters rather than requiring manual configuration.
+
+### Creating Model Classes
+
+To implement pre-defined models, you will need to create model classes in the `[your_plugin]/classes/aimodel` directory and extend the `core_ai\aimodel\base` class.
+
+For example, `aiprovider_openai` plugin defines this:
+
+```php
+namespace aiprovider_openai\aimodel;
+
+use core_ai\aimodel\base;
+use MoodleQuickForm;
+
+class gpt4o extends base implements openai_base {
+ #[\Override]
+ public function get_model_name(): string {
+ return 'gpt-4o';
+ }
+
+ #[\Override]
+ public function get_model_display_name(): string {
+ return 'GPT-4o';
+ }
+
+ // Add other model-specific methods or properties
+}
+```
+
+### Implementing Per-Model Settings
+
+To add configurable settings for individual models:
+
+1. Override the `has_model_settings()` and `add_model_settings()` methods:
+
+ ```php
+ #[\Override]
+ public function has_model_settings(): bool {
+ return true;
+ }
+
+ #[\Override]
+ public function add_model_settings(MoodleQuickForm $mform): void {
+ $mform->addElement(
+ 'text',
+ 'top_p',
+ get_string('settings_top_p', 'aiprovider_openai'),
+ );
+ $mform->setType('top_p', PARAM_FLOAT);
+ $mform->addHelpButton('top_p', 'settings_top_p', 'aiprovider_openai');
+
+ // Add more model settings as needed
+ $mform->addElement(
+ 'text',
+ 'max_tokens',
+ get_string('settings_max_tokens', 'aiprovider_openai'),
+ );
+ $mform->setType('max_tokens', PARAM_INT);
+ }
+ ```
+
+2. Create a helper class to manage models. For example, the `aiprovider_openai` plugin defines this:
+
+ ```php
+ namespace aiprovider_openai;
+
+ class helper {
+ /**
+ * Get all model classes.
+ *
+ * @return array Array of model classes
+ */
+ public static function get_model_classes(): array {
+ $models = [];
+ $modelclasses = \core_component::get_component_classes_in_namespace('aiprovider_openai', 'aimodel');
+ foreach ($modelclasses as $class => $path) {
+ if (!class_exists($class) || !is_a($class, \core_ai\aimodel\base::class, true)) {
+ throw new \coding_exception("Model class not valid: {$class}");
+ }
+ $models[] = $class;
+ }
+ return $models;
+ }
+
+ /**
+ * Get a specific model class instance.
+ *
+ * @param string $modelname The model name
+ * @return \core_ai\aimodel\base|null The model class or null if not found
+ */
+ public static function get_model_class(string $modelname): ?\core_ai\aimodel\base {
+ foreach (self::get_model_classes() as $modelclass) {
+ $model = new $modelclass();
+ if ($model->get_model_name() === $modelname) {
+ return $model;
+ }
+ }
+ return null;
+ }
+ }
+ ```
+
+### Integrating with Action Settings Forms
+
+To add model settings to action forms, you will need to create a hook listener that adds the model settings to the action form.
+
+For example, the `aiprovider_openai` plugin does these:
+
+1. Create a hook listener that adds model settings to the action form:
+
+ ```php
+ public static function set_model_form_definition_for_aiprovider_openai(after_ai_action_settings_form_hook $hook): void {
+ if ($hook->plugin !== 'aiprovider_openai') {
+ return;
+ }
+
+ $mform = $hook->mform;
+ if (isset($mform->_elementIndex['modeltemplate'])) {
+ $model = $mform->getElementValue('modeltemplate');
+ if (is_array($model)) {
+ $model = $model[0];
+ }
+
+ // Handle custom model option.
+ if ($model == 'custom') {
+ $mform->addElement('header', 'modelsettingsheader', get_string('settings', 'aiprovider_openai'));
+ $mform->addElement(
+ 'textarea',
+ 'modelextraparams',
+ get_string('extraparams', 'aiprovider_openai'),
+ ['rows' => 5, 'cols' => 20],
+ );
+ $mform->setType('modelextraparams', PARAM_TEXT);
+ } else {
+ // Handle pre-defined model settings.
+ $targetmodel = helper::get_model_class($model);
+ if ($targetmodel && $targetmodel->has_model_settings()) {
+ $mform->addElement('header', 'modelsettingsheader', get_string('settings', 'aiprovider_openai'));
+ $targetmodel->add_model_settings($mform);
+ }
+ }
+ }
+ }
+ ```
+
+2. Register the hook callback in `db/hooks.php`:
+
+ ```php
+ $callbacks = [
+ [
+ 'hook' => \core_ai\hook\after_ai_action_settings_form_hook::class,
+ 'callback' => \aiprovider_openai\hook_listener::class . '::set_model_form_definition_for_aiprovider_openai',
+ ],
+ ];
+ ```
+
+### Model Selection UI
+
+To create a model selector UI for your action settings form, you will need to add the model selector element to your action settings form.
+
+For example, `aiprovider_openai` plugin defines this:
+
+```php
+// Create model selector options
+$models = [];
+foreach (helper::get_model_classes() as $modelclass) {
+ $model = new $modelclass();
+ $models[$model->get_model_name()] = $model->get_model_display_name();
+}
+$models['custom'] = get_string('custom_model', 'aiprovider_openai');
+
+$mform->addElement(
+ 'select',
+ 'modeltemplate',
+ get_string('model_template', 'aiprovider_openai'),
+ $models
+);
+$mform->setDefault('modeltemplate', 'gpt-4o');
+```
+
+## Rate limiting
+
+Provider plugins by default implement rate limiting to prevent abuse of the external AI services.
+This is inherited from the `core_ai\provider` class. Developers don't need to implement rate limiting themselves.
+
+This default rate limiting behaviour can be changed by overriding the `is_request_allowed()` method `core_ai\provider` class.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/antivirus/_files/scanner-php.mdx b/versioned_docs/version-5.1/apis/plugintypes/antivirus/_files/scanner-php.mdx
new file mode 100644
index 0000000000..0b614a3177
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/antivirus/_files/scanner-php.mdx
@@ -0,0 +1,13 @@
+
+
+
+The `classes/scanner.php` class must be defined in the correct namespace for your plugin, and must extend the `\core\antivirus\scanner` class.
+
+It is responsible for implementing the interface between Moodle and the antivirus scanning tool.
+
+The following methods are compulsory:
+
+- `is_configured(): bool` - returns true, if this antivirus plugin is configured.
+- `scan_file($file, $filename, $deleteinfected): void` - performs the **$file** scanning using antivirus functionality, using **$filename** as filename string in any reporting, deletes infected file if **$deleteinfected** is true.
+
+If a virus is found the `scan_file()` function _must_ throw an instance of the `\core\antivirus\scanner_exception` type.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/antivirus/_files/scanner-php.tsx b/versioned_docs/version-5.1/apis/plugintypes/antivirus/_files/scanner-php.tsx
new file mode 100644
index 0000000000..167d78e6ff
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/antivirus/_files/scanner-php.tsx
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { Props } from '../../../../_utils';
+import DefaultDescription from './scanner-php.mdx';
+
+const defaultExample = `
+namespace antivirus_scanmyfile;
+
+class scanner extends \\core\\antivirus\\scanner {
+
+ public function is_configured() {
+ // Note: You will likely want a more specific check.
+ // This example just checks whether configuration exists.
+ return (bool) $this->get_config('pathtoscanner');
+ }
+
+ public function scan_file($file, $filename, $deleteinfected) {
+ if (!is_readable($file)) {
+ // This should not happen.
+ debugging('File is not readable.');
+ return;
+ }
+
+ // Execute the scan using the fictitious scanmyfile tool.
+ // In this case the tool returns:
+ // - 0 if no virus is found
+ // - 1 if a virus was found
+ // - [int] on error
+ $return = $this->scan_file_using_scanmyfile_scanner_tool($file);
+
+ if ($return == 0) {
+ // Perfect, no problem found, file is clean.
+ return;
+ } else if ($return == 1) {
+ // Infection found.
+ if ($deleteinfected) {
+ unlink($file);
+ }
+ throw new \\core\\antivirus\\scanner_exception(
+ 'virusfounduser',
+ '',
+ ['filename' => $filename]
+ );
+ } else {
+ // Unknown problem.
+ debugging('Error occurred during file scanning.');
+ return;
+ }
+ }
+
+ public function scan_file_using_scanmyfile_scanner_tool($file): int {
+ // Scanning routine using antivirus own tool goes here..
+ // You should choose a return value appropriate for your tool.
+ // These must match the expected values in the scan_file() function.
+ // In this example the following are returned:
+ // - 0: No virus found
+ // - 1: Virus found
+ return 0;
+ }
+}
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/antivirus/index.mdx b/versioned_docs/version-5.1/apis/plugintypes/antivirus/index.mdx
new file mode 100644
index 0000000000..2b457d8e21
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/antivirus/index.mdx
@@ -0,0 +1,112 @@
+---
+title: Antivirus plugins
+tags:
+ - Plugins
+ - Antivirus
+description: Integrate your preferred Antivirus tool to with Moodle to automatically check new file uploads.
+---
+
+
+
+
+
+
+import {
+ Lang,
+ VersionPHP,
+ SettingsPHP,
+} from '../../_files';
+import ScannerPHP from './_files/scanner-php';
+
+Moodle supports automatic virus scanning of files as they are uploaded by users. To enable this developers can write an antivirus plugin, which acts as a bridge between Moodle and the antivirus tooling.
+
+A plugin to support the Open Source [ClamAV](https://www.clamav.net/) antivirus engine is included with Moodle core as standard.
+
+## File structure
+
+Antivirus plugins are located in the `/lib/antivirus` directory.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+
+ View an example directory layout for the `antivirus_scanmyfile` plugin.
+
+```console
+ lib/antivirus/scanmyfile/
+ |-- classes
+ | `-- scanner.php
+ |-- db
+ | `-- upgrade.php
+ |-- lang
+ | `-- en
+ | `-- antivirus_scanmyfile.php
+ |-- settings.php
+ |-- tests
+ | `-- scanner_test.php
+ `-- version.php
+```
+
+
+
+Some of the important files for the antivirus plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### version.php
+
+
+
+### settings.php
+
+export const settingsExample = `
+if ($ADMIN->fulltree) {
+ $settings->add(
+ new admin_setting_configexecutable(
+ 'antivirus_scanmyfile/pathtoscanner',",
+ new lang_string('pathtoscanner', 'antivirus_scanmyfile'),",
+ new lang_string('configpathtoscanner', 'antivirus_scanmyfile'),",
+ ''",
+ )
+ );
+}
+`;
+
+
+
+### lang/en/antivirus_scanmyfile.php
+
+export const langExample = `
+ $string['pluginname']= 'ScanMyFile antivirus';
+ $string['pathtoscanner'] = 'Path to scanner';
+ $string['configpathtoscanner'] = 'Define full path to scanner';
+`;
+
+
+
+### classes/scanner.php
+
+
+
+### tests/scanner_test.php (optional)
+
+Writing unit tests is strongly encouraged as it can help to identify bugs, or changes in behaviour, that you had not anticipated.
+
+Since antivirus plugins typically rely on an external dependency, it is usually a good idea to replace the real component with a test "double". You can see an example of this in Moodle in the [antivirus_clamav unit tests](https://github.com/moodle/moodle/blob/81407f18ecff1fded66a9d8bdc25bbf9d8ccd5ca/lib/antivirus/clamav/tests/scanner_test.php#L45-L56).
+
+The PHPUnit Manual contains a dedicated [section on Test Doubles](https://docs.phpunit.de/en/9.6/test-doubles.html).
+
+You may also wish to include some tests of the real system to ensure that upgrades to the Antivirus software do not break your plugin.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/assign/_files/submission_settings.php b/versioned_docs/version-5.1/apis/plugintypes/assign/_files/submission_settings.php
new file mode 100644
index 0000000000..60e7a7248f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/assign/_files/submission_settings.php
@@ -0,0 +1,22 @@
+// Note: This is on by default.
+$settings->add(
+ new admin_setting_configcheckbox('assignsubmission_file/default',
+ new lang_string('default', 'assignsubmission_file'),
+ new lang_string('default_help', 'assignsubmission_file'),
+ 1
+ )
+);
+
+if (isset($CFG->maxbytes)) {
+ $name = new lang_string('maximumsubmissionsize', 'assignsubmission_file');
+ $description = new lang_string('configmaxbytes', 'assignsubmission_file');
+
+ $element = new admin_setting_configselect(
+ 'assignsubmission_file/maxbytes',
+ $name,
+ $description,
+ 1048576,
+ get_max_upload_sizes($CFG->maxbytes)
+ );
+ $settings->add($element);
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/assign/feedback.md b/versioned_docs/version-5.1/apis/plugintypes/assign/feedback.md
new file mode 100644
index 0000000000..8b050a157a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/assign/feedback.md
@@ -0,0 +1,543 @@
+---
+title: Assign feedback plugins
+tags:
+ - Assign
+ - Assignment
+ - Feedback
+ - Subplugin
+toc_max_heading_level: 4
+description: Assign feedback plugins allow you to define different ways that a teacher can provide feedback to their students.
+---
+
+import {
+ SettingsPHP,
+ LocalLib,
+} from '../../_files';
+export const plugintype = 'assignfeedback';
+
+An assignment feedback plugin can do many things including providing feedback to students about a submission. The grading interface for the assignment module provides many hooks that allow plugins to add their own entries and participate in the grading workflow.
+
+:::tip
+
+For a good reference implementation, see the [file](https://github.com/moodle/moodle/tree/main/mod/assign/feedback/file) feedback plugin included with core because it uses most of the features of feedback plugins.
+
+:::
+
+## File structure
+
+Assignment Feedback plugins are located in the `/mod/assign/feedback` directory. A plugin should not include any custom files outside of it's own plugin folder.
+
+:::important Plugin naming
+
+The plugin name should be no longer than 38 (13 before Moodle 4.3) characters - this is because the database tables for a submission plugin must be prefixed with `assignfeedback_[pluginname]` (15 chars + X) and the table names can be no longer than 53 (28 before Moodle 4.3) chars due to a limitation with PostgreSQL.
+
+If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 53 character limit (28 before Moodle 4.3).
+
+Note: If your plugin is intended to work with versions of Moodle older than 4.3, then the plugin name must be 13 characters or shorter, and table names must be 28 characters or shorter.
+:::
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+:::important
+
+Some of the important files are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin.
+
+:::
+
+
+ View an example directory layout for the `assignfeedback_file` plugin.
+
+```console
+mod/assign/feedback/file
+├── backup
+│ └── moodle2
+│ ├── backup_assignfeedback_file_subplugin.class.php
+│ └── restore_assignfeedback_file_subplugin.class.php
+├── classes
+│ └── privacy
+│ └── provider.php
+├── db
+│ ├── access.php
+│ ├── install.php
+│ ├── install.xml
+│ └── upgrade.php
+├── importzipform.php
+├── importziplib.php
+├── lang
+│ └── en
+│ └── assignfeedback_file.php
+├── lib.php
+├── locallib.php
+├── settings.php
+├── uploadzipform.php
+└── version.php
+```
+
+
+
+### settings.php
+
+
+
+
+
+export const settingsExample = `
+$settings->add(
+ new admin_setting_configcheckbox(
+ 'assignfeedback_file/default',
+ new lang_string('default', 'assignfeedback_file'),
+ new lang_string('default_help', 'assignfeedback_file'),
+ 0
+ )
+);
+`;
+
+export const settingsExtra = `
+All feedback plugins should include one setting named 'default' to indicate if the plugin should be enabled by default when creating a new assignment.
+`;
+
+
+
+
+
+### locallib.php
+
+
+
+
+
+
+
+
+This is where all the functionality for this plugin is defined. We will step through this file and describe each part as we go.
+
+```php
+class assign_feedback_file extends assign_feedback_plugin {
+```
+
+#### get_name()
+
+All feedback plugins MUST define a class with the component name of the plugin that extends assign_feedback_plugin.
+
+```php
+public function get_name() {
+ return get_string('file', 'assignfeedback_file');
+}
+```
+
+This function is abstract in the parent class (feedback_plugin) and must be defined in your new plugin. Use the language strings to make your plugin translatable.
+
+#### get_settings()
+
+```php
+public function get_settings(MoodleQuickForm $mform) {
+ $mform->addElement(
+ 'assignfeedback_file_fileextensions',
+ get_string('allowedfileextensions', 'assignfeedback_file')
+ );
+ $mform->setType('assignfeedback_file_fileextensions', PARAM_FILE);
+}
+```
+
+This function is called when building the settings page for the assignment. It allows this plugin to add a list of settings to the form. Notice that the settings should be prefixed by the plugin name which is good practice to avoid conflicts with other plugins. (None of the core feedback plugins have any instance settings, so this example is fictional).
+
+#### save_settings()
+
+```php
+public function save_settings(stdClass $data) {
+ $this->set_config('allowedfileextensions', $data->allowedfileextensions);
+ return true;
+}
+```
+
+This function is called when the assignment settings page is submitted, either for a new assignment or when editing an existing one. For settings specific to a single instance of the assignment you can use the assign_plugin::set_config function shown here to save key/value pairs against this assignment instance for this plugin.
+
+#### get_form_elements_for_user()
+
+```php
+public function get_form_elements_for_user(
+ $grade,
+ MoodleQuickForm $mform,
+ stdClass $data,
+ $userid
+) {
+ $fileoptions = $this->get_file_options();
+ $gradeid = $grade ? $grade->id : 0;
+ $elementname = "files_{$userid}";
+
+ $data = file_prepare_standard_filemanager(
+ $data,
+ $elementname,
+ $fileoptions,
+ $this->assignment->get_context(),
+ 'assignfeedback_file',
+ ASSIGNFEEDBACK_FILE_FILEAREA,
+ $gradeid
+ );
+ $mform->addElement(
+ 'filemanager',
+ "{$elementname}_filemanager",
+ html_writer::tag(
+ 'span',
+ $this->get_name(),
+ ['class' => 'accesshide']
+ ),
+ null,
+ $fileoptions
+ );
+
+ return true;
+}
+```
+
+This function is called when building the feedback form. It functions identically to the get_settings function except that the grade object is available (if there is a grade) to associate the settings with a single grade attempt. This example also shows how to use a filemanager within a feedback plugin. The function must return true if it has modified the form otherwise the assignment will not include a header for this plugin. Notice there is an older version of this function "get_form_elements" which does not accept a userid as a parameter - this version is less useful - not recommended.
+
+#### is_feedback_modified()
+
+```php
+public function is_feedback_modified(stdClass $grade, stdClass $data) {
+ $commenttext = '';
+ if ($grade) {
+ $feedbackcomments = $this->get_feedback_comments($grade->id);
+ if ($feedbackcomments) {
+ $commenttext = $feedbackcomments->commenttext;
+ }
+ }
+
+ if ($commenttext == $data->assignfeedbackcomments_editor[]('text')) {
+ return false;
+ } else {
+ return true;
+ }
+}
+```
+
+This function is called before feedback is saved. If feedback has not been modified then the save() method is not called. This function takes the grade object and submitted data from the grading form. In this example we are comparing the existing text comments made with the new ones. This function must return a boolean; True if the feedback has been modified; False if there has been no modification made. If this method is not overwritten then it will default to returning True.
+
+#### save()
+
+```php
+public function save(stdClass $grade, stdClass $data) {
+ global $DB;
+
+ $fileoptions = $this->get_file_options();
+
+ $userid = $grade->userid;
+ $elementname = 'files_' . $userid;
+
+ $data = file_postupdate_standard_filemanager(
+ $data,
+ $elementname,
+ $fileoptions,
+ $this->assignment->get_context(),
+ 'assignfeedback_file',
+ ASSIGNFEEDBACK_FILE_FILEAREA,
+ $grade->id
+ );
+
+ $filefeedback = $this->get_file_feedback($grade->id);
+ if ($filefeedback) {
+ $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
+ return $DB->update_record('assignfeedback_file', $filefeedback);
+ } else {
+ $filefeedback = new stdClass();
+ $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
+ $filefeedback->grade = $grade->id;
+ $filefeedback->assignment = $this->assignment->get_instance()->id;
+ return $DB->insert_record('assignfeedback_file', $filefeedback) > 0;
+ }
+}
+```
+
+This function is called to save a graders feedback. The parameters are the grade object and the data from the feedback form. This example calls `file_postupdate_standard_filemanager` to copy the files from the draft file area to the filearea for this feedback. It then records the number of files in the plugin specific `assignfeedback_file` table.
+
+#### view_summary()
+
+```php
+public function view_summary(stdClass $grade, & $showviewlink) {
+ $count = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
+ // show a view all link if the number of files is over this limit
+ $showviewlink = $count > ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES;
+
+ if ($count <= ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES) {
+ return $this->assignment->render_area_files(
+ 'assignfeedback_file',
+ ASSIGNFEEDBACK_FILE_FILEAREA,
+ $grade->id
+ );
+ } else {
+ return get_string('countfiles', 'assignfeedback_file', $count);
+ }
+}
+```
+
+This function is called to display a summary of the feedback to both markers and students. It counts the number of files and if it is more that a set number, it only displays a count of how many files are in the feedback - otherwise it uses a helper function to write the entire list of files. This is because we want to keep the summaries really short so they can be displayed in a table. There will be a link to view the full feedback on the submission status page.
+
+#### view()
+
+```php
+public function view(stdClass $grade) {
+ return $this->assignment->render_area_files(
+ 'assignfeedback_file',
+ ASSIGNFEEDBACK_FILE_FILEAREA,
+ $grade->id
+ );
+}
+```
+
+This function is called to display the entire feedback to both markers and students. In this case it uses the helper function in the assignment class to write the list of files.
+
+#### can_upgrade()
+
+```php
+public function can_upgrade($type, $version) {
+
+ if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {
+ return true;
+ }
+ return false;
+}
+```
+
+This function is used to identify old "Assignment 2.2" subtypes that can be upgraded by this plugin. This plugin supports upgrades from the old "upload" and "uploadsingle" assignment subtypes.
+
+```php
+public function upgrade_settings(context $oldcontext, stdClass $oldassignment, &$log) {
+ // first upgrade settings (nothing to do)
+ return true;
+}
+```
+
+This function is called once per assignment instance to upgrade the settings from the old assignment to the new mod_assign. In this case it returns true as there are no settings to upgrade.
+
+```php
+public function upgrade(
+ context $oldcontext,
+ stdClass $oldassignment,
+ stdClass $oldsubmission,
+ stdClass $grade,
+ &$log
+) {
+ global $DB;
+
+ // now copy the area files
+ $this->assignment->copy_area_files_for_upgrade(
+ $oldcontext->id,
+ 'mod_assignment',
+ 'response',
+ $oldsubmission->id,
+ // New file area
+ $this->assignment->get_context()->id,
+ 'assignfeedback_file',
+ ASSIGNFEEDBACK_FILE_FILEAREA,
+ $grade->id
+ );
+
+ // now count them!
+ $filefeedback = new stdClass();
+ $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
+ $filefeedback->grade = $grade->id;
+ $filefeedback->assignment = $this->assignment->get_instance()->id;
+ if (!$DB->insert_record('assignfeedback_file', $filefeedback) > 0) {
+ $log .= get_string('couldnotconvertgrade', 'mod_assign', $grade->userid);
+ return false;
+ }
+ return true;
+}
+```
+
+This function upgrades a single submission from the old assignment type to the new one. In this case it involves copying all the files from the old filearea to the new one. There is a helper function available in the assignment class for this (Note: the copy will be fast as it is just adding rows to the files table). If this function returns false, the upgrade will be aborted and rolled back.
+
+#### is_empty()
+
+```php
+public function is_empty(stdClass $submission) {
+ return $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA) == 0;
+}
+```
+
+If a plugin has no data to show then this function should return true from the `is_empty()` function. This prevents a table row from being added to the feedback summary for this plugin. It is also used to check if a grader has tried to save feedback with no data.
+
+#### get_file_areas()
+
+```php
+public function get_file_areas() {
+ return [ASSIGNFEEDBACK_FILE_FILEAREA => $this->get_name()];
+}
+```
+
+A plugin should implement `get_file_areas` if it supports saving of any files to moodle - this allows the file areas to be browsed by the moodle file manager.
+
+#### delete_instance()
+
+```php
+public function delete_instance() {
+ global $DB;
+ // will throw exception on failure
+ $DB->delete_records('assignfeedback_file', [
+ 'assignment' => $this->assignment->get_instance()->id,
+ ]);
+
+ return true;
+}
+```
+
+This function is called when a plugin is deleted. Note only database records need to be cleaned up - files belonging to fileareas for this assignment will be automatically cleaned up.
+
+#### Gradebook features
+
+```php
+public function format_for_gradebook(stdClass $grade) {
+ return FORMAT_MOODLE;
+}
+
+public function text_for_gradebook(stdClass $grade) {
+ return '';
+}
+```
+
+Only one feedback plugin can push comments to the gradebook. Usually this is the feedback_comments plugin - but it can be configured to be any feedback plugin. If the current plugin is the plugin chosen to generate comments for the gradebook, the comment text and format will be taken from these two functions.
+
+```php
+/**
+ * Override to indicate a plugin supports quickgrading
+ *
+ * @return boolean - True if the plugin supports quickgrading
+ */
+public function supports_quickgrading() {
+ return false;
+}
+
+/**
+ * Get quickgrading form elements as html
+ *
+ * @param int $userid The user id in the table this quickgrading element relates to
+ * @param mixed $grade grade or null - The grade data. May be null if there are no grades for this user (yet)
+ * @return mixed - A html string containing the html form elements required for quickgrading or false to indicate this plugin does not support quickgrading
+ */
+public function get_quickgrading_html($userid, $grade) {
+ return false;
+}
+
+/**
+ * Has the plugin quickgrading form element been modified in the current form submission?
+ *
+ * @param int $userid The user id in the table this quickgrading element relates to
+ * @param stdClass $grade The grade
+ * @return boolean - true if the quickgrading form element has been modified
+ */
+public function is_quickgrading_modified($userid, $grade) {
+ return false;
+}
+
+/**
+ * Save quickgrading changes
+ *
+ * @param int $userid The user id in the table this quickgrading element relates to
+ * @param stdClass $grade The grade
+ * @return boolean - true if the grade changes were saved correctly
+ */
+public function save_quickgrading_changes($userid, $grade) {
+ return false;
+}
+```
+
+These 4 functions can be implemented to allow a plugin to support quick-grading. The feedback comments plugin is the only example of this in core.
+
+```php
+/**
+ * Run cron for this plugin
+ */
+public static function cron() {
+}
+```
+
+A plugin can run code when cron runs by implementing this method.
+
+```php
+/**
+ * Return a list of the grading actions supported by this plugin.
+ *
+ * A grading action is a page that is not specific to a user but to the whole assignment.
+ * @return array - An array of action and description strings.
+ * The action will be passed to grading_action.
+ */
+public function get_grading_actions() {
+ return [];
+}
+
+/**
+ * Show a grading action form
+ *
+ * @param string $gradingaction The action chosen from the grading actions menu
+ * @return string The page containing the form
+ */
+public function grading_action($gradingaction) {
+ return '';
+}
+```
+
+Grading actions appear in the select menu above the grading table and apply to the whole assignment. An example is "Upload grading worksheet". When a grading action is selected, the grading_action will be called with the action that was chosen (so plugins can have multiple entries in the list).
+
+```php
+/**
+ * Return a list of the batch grading operations supported by this plugin.
+ *
+ * @return array - An array of action and description strings.
+ * The action will be passed to grading_batch_operation.
+ */
+public function get_grading_batch_operations() {
+ return [];
+}
+
+/**
+ * Show a batch operations form
+ *
+ * @param string $action The action chosen from the batch operations menu
+ * @param array $users The list of selected userids
+ * @return string The page containing the form
+ */
+public function grading_batch_operation($action, $users) {
+ return '';
+}
+```
+
+These two callbacks allow adding entries to the batch grading operations list (where you select multiple users in the table and choose e.g. "Lock submissions" for every user). The action is passed to "grading_batch_operation" so that multiple entries can be supported by a plugin.
+
+## Other features
+
+### Add calendar events
+
+
+
+From Moodle 3.1 onwards, feedback plugins can add events to the Moodle calendar without side effects. These will be hidden and deleted in line with the assignment module. For example:
+
+```php
+// Add release date to calendar
+$calendarevent = new stdClass();
+$calendarevent->name = get_string('calendareventname', 'assignsubmission_something');
+$calendarevent->description = get_string('calendareventdesc', 'assignsubmission_something');
+$calendarevent->courseid = $courseid;
+$calendarevent->groupid = 0;
+$calendarevent->userid = $userid;
+$calendarevent->modulename = 'assign';
+$calendarevent->instance = $instanceid;
+$calendarevent->eventtype = 'something_release'; // For activity module's events, this can be used to set the alternative text of the event icon. Set it to 'pluginname' unless you have a better string.
+$calendarevent->timestart = $releasedate;
+$calendarevent->visible = true;
+$calendarevent->timeduration = 0;
+
+calendar_event::create($calendarevent);
+```
diff --git a/versioned_docs/version-5.1/apis/plugintypes/assign/index.md b/versioned_docs/version-5.1/apis/plugintypes/assign/index.md
new file mode 100644
index 0000000000..120ab233f0
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/assign/index.md
@@ -0,0 +1,13 @@
+---
+title: Assignment sub-plugins
+tags:
+ - Assign
+ - Assignment
+ - Subplugin
+ - Plugintype
+---
+
+The `mod_assign` activity can be extended using two sub-plugin types, namely:
+
+- submission plugins, used to provide different ways for students to submit their content
+- feedback plugins, used to extend the ways in which feedback may be provided to students on their submissions
diff --git a/versioned_docs/version-5.1/apis/plugintypes/assign/submission.md b/versioned_docs/version-5.1/apis/plugintypes/assign/submission.md
new file mode 100644
index 0000000000..ec9238decf
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/assign/submission.md
@@ -0,0 +1,683 @@
+---
+title: Assign submission plugins
+tags:
+ - Assign
+ - Assignment
+ - Submission
+ - Subplugin
+toc_max_heading_level: 4
+description: Assign submission plugins allow you to define different ways for a student to submit their work
+---
+
+import {
+ SettingsPHP,
+ LocalLib,
+} from '../../_files';
+export const plugintype = 'assignsubmission';
+
+An assignment submission plugin is used to display custom form fields to a student when they are editing their assignment submission. It also has full control over the display the submitted assignment to graders and students.
+
+:::tip
+
+For a good reference implementation, see the [onlinetext](https://github.com/moodle/moodle/tree/main/mod/assign/submission/onlinetext) submission plugin included with core because it uses most of the features of submission plugins.
+
+:::
+
+## File structure
+
+Assignment Feedback plugins are located in the `/mod/assign/submission` directory. A plugin should not include any custom files outside of it's own plugin folder.
+
+:::important Plugin naming
+
+The plugin name should be no longer than 36 (11 before Moodle 4.3) characters - this is because the database tables for a submission plugin must be prefixed with `assignsubmission_[pluginname]` (17 chars + X) and the table names can be no longer than 53 (28 before Moodle 4.3) chars due to a limitation with PostgreSQL.
+
+If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 53 character limit (28 before Moodle 4.3).
+
+Note: If your plugin is intended to work with versions of Moodle older than 4.3, then the plugin name must be 11 characters or shorter, and table names must be 28 characters or shorter.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+:::important
+
+Some of the important files are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin.
+
+:::
+
+
+ View an example directory layout for the `assignfeedback_file` plugin.
+
+```console
+mod/assign/submission/file
+├── backup
+│ └── moodle2
+│ ├── backup_assignsubmission_file_subplugin.class.php
+│ └── restore_assignsubmission_file_subplugin.class.php
+├── classes
+│ ├── event
+│ │ ├── assessable_uploaded.php
+│ │ ├── submission_created.php
+│ │ └── submission_updated.php
+│ └── privacy
+│ └── provider.php
+├── db
+│ ├── access.php
+│ └── install.xml
+├── lang
+│ └── en
+│ └── assignsubmission_file.php
+├── lib.php
+├── locallib.php
+├── settings.php
+└── version.php
+```
+
+
+
+### settings.php
+
+
+
+
+
+
+
+import settingsExample from '!!raw-loader!./_files/submission_settings.php';
+
+export const settingsExtra = `
+All submission plugins should include one setting named 'default' to indicate if the plugin should be enabled by default when creating a new assignment.
+`;
+
+
+
+
+
+:::info
+
+This example from the submission_file plugin also checks to see if there is a maxbytes setting for this moodle installation and, if found, adds a new admin setting to the settings page.
+
+:::
+
+### locallib.php
+
+
+
+
+
+
+
+
+This is where all the functionality for this plugin is defined. We will step through this file and describe each part as we go.
+
+```php
+class assign_submission_file extends assign_submission_plugin {
+```
+
+All submission plugins MUST define a class with the component name of the plugin that extends assign_submission_plugin.
+
+#### get_name()
+
+```php
+public function get_name() {
+ return get_string('file', 'assignsubmission_file');
+}
+```
+
+Get name is abstract in submission_plugin and must be defined in your new plugin. Use the language strings to make your plugin translatable.
+
+#### get_settings()
+
+```php
+public function get_settings(MoodleQuickForm $mform) {
+ global $CFG, $COURSE;
+
+ $defaultmaxfilesubmissions = $this->get_config('maxfilesubmissions');
+ $defaultmaxsubmissionsizebytes = $this->get_config('maxsubmissionsizebytes');
+
+ $settings = [];
+ $options = [];
+ for ($i = 1; $i <= ASSIGNSUBMISSION_FILE_MAXFILES; $i++) {
+ $options[$i] = $i;
+ }
+
+ $name = get_string('maxfilessubmission', 'assignsubmission_file');
+ $mform->addElement('select', 'assignsubmission_file_maxfiles', $name, $options);
+ $mform->addHelpButton(
+ 'assignsubmission_file_maxfiles',
+ 'maxfilessubmission',
+ 'assignsubmission_file'
+ );
+ $mform->setDefault('assignsubmission_file_maxfiles', $defaultmaxfilesubmissions);
+ $mform->disabledIf('assignsubmission_file_maxfiles', 'assignsubmission_file_enabled', 'notchecked');
+
+ $choices = get_max_upload_sizes(
+ $CFG->maxbytes,
+ $COURSE->maxbytes,
+ get_config('assignsubmission_file', 'maxbytes')
+ );
+
+ $settings[] = [
+ 'type' => 'select',
+ 'name' => 'maxsubmissionsizebytes',
+ 'description' => get_string('maximumsubmissionsize', 'assignsubmission_file'),
+ 'options'=> $choices,
+ 'default'=> $defaultmaxsubmissionsizebytes,
+ ];
+
+ $name = get_string('maximumsubmissionsize', 'assignsubmission_file');
+ $mform->addElement('select', 'assignsubmission_file_maxsizebytes', $name, $choices);
+ $mform->addHelpButton(
+ 'assignsubmission_file_maxsizebytes',
+ 'maximumsubmissionsize',
+ 'assignsubmission_file'
+ );
+ $mform->setDefault('assignsubmission_file_maxsizebytes', $defaultmaxsubmissionsizebytes);
+ $mform->disabledIf(
+ 'assignsubmission_file_maxsizebytes',
+ 'assignsubmission_file_enabled',
+ 'notchecked'
+ );
+}
+```
+
+The "get_settings" function is called when building the settings page for the assignment. It allows this plugin to add a list of settings to the form. Notice that the settings are prefixed by the plugin name which is good practice to avoid conflicts with other plugins.
+
+#### save_settings()
+
+```php
+public function save_settings(stdClass $data) {
+ $this->set_config('maxfilesubmissions', $data->assignsubmission_file_maxfiles);
+ $this->set_config('maxsubmissionsizebytes', $data->assignsubmission_file_maxsizebytes);
+ return true;
+}
+```
+
+The "save_settings" function is called when the assignment settings page is submitted, either for a new assignment or when editing an existing one. For settings specific to a single instance of the assignment you can use the assign_plugin::set_config function shown here to save key/value pairs against this assignment instance for this plugin.
+
+#### get_form_elements()
+
+```php
+public function get_form_elements($submission, MoodleQuickForm $mform, stdClass $data) {
+ if ($this->get_config('maxfilesubmissions') <= 0) {
+ return false;
+ }
+
+ $fileoptions = $this->get_file_options();
+ $submissionid = $submission ? $submission->id : 0;
+
+ $data = file_prepare_standard_filemanager(
+ $data,
+ 'files',
+ $fileoptions,
+ $this->assignment->get_context(),
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $submissionid
+ );
+
+ $mform->addElement(
+ 'filemanager',
+ 'files_filemanager',
+ html_writer::tag('span', $this->get_name(), ['class' => 'accesshide']),
+ null,
+ $fileoption
+ );
+
+ return true;
+}
+```
+
+The get_form_elements function is called when building the submission form. It functions identically to the get_settings function except that the submission object is available (if there is a submission) to associate the settings with a single submission. This example also shows how to use a filemanager within a submission plugin. The function must return true if it has modified the form otherwise the assignment will not include a header for this plugin.
+
+#### save()
+
+```php
+public function save(stdClass $submission, stdClass $data) {
+ global $USER, $DB;
+
+ $fileoptions = $this->get_file_options();
+
+ $data = file_postupdate_standard_filemanager(
+ $data,
+ 'files',
+ $fileoptions,
+ $this->assignment->get_context(),
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $submission->id
+ );
+
+ $filesubmission = $this->get_file_submission($submission->id);
+
+ // Plagiarism code event trigger when files are uploaded.
+
+ $fs = get_file_storage();
+ $files = $fs->get_area_files(
+ $this->assignment->get_context()->id,
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $submission->id,
+ 'id',
+ false
+ );
+
+ $count = $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA);
+
+ // Send files to event system.
+ // This lets Moodle know that an assessable file was uploaded (eg for plagiarism detection).
+ $eventdata = new stdClass();
+ $eventdata->modulename = 'assign';
+ $eventdata->cmid = $this->assignment->get_course_module()->id;
+ $eventdata->itemid = $submission->id;
+ $eventdata->courseid = $this->assignment->get_course()->id;
+ $eventdata->userid = $USER->id;
+ if ($count > 1) {
+ $eventdata->files = $files;
+ }
+ $eventdata->file = $files;
+ $eventdata->pathnamehashes = array_keys($files);
+ events_trigger('assessable_file_uploaded', $eventdata);
+
+ if ($filesubmission) {
+ $filesubmission->numfiles = $this->count_files($submission->id,
+ ASSIGNSUBMISSION_FILE_FILEAREA);
+ return $DB->update_record('assignsubmission_file', $filesubmission);
+ } else {
+ $filesubmission = new stdClass();
+ $filesubmission->numfiles = $this->count_files($submission->id,
+ ASSIGNSUBMISSION_FILE_FILEAREA);
+ $filesubmission->submission = $submission->id;
+ $filesubmission->assignment = $this->assignment->get_instance()->id;
+ return $DB->insert_record('assignsubmission_file', $filesubmission) > 0;
+ }
+```
+
+The "save" function is called to save a user submission. The parameters are the submission object and the data from the submission form. This example calls `file_postupdate_standard_filemanager` to copy the files from the draft file area to the filearea for this submission, it then uses the event api to trigger an assessable_file_uploaded event for the plagiarism api. It then records the number of files in the plugin specific "assignsubmission_file" table.
+
+#### get_files()
+
+```php
+public function get_files($submission) {
+ $result = [];
+ $fs = get_file_storage();
+
+ $files = $fs->get_area_files(
+ $this->assignment->get_context()->id,
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $submission->id,
+ 'timemodified',
+ false
+ );
+
+ foreach ($files as $file) {
+ $result[$file->get_filename()] = $file;
+ }
+ return $result;
+}
+```
+
+If this submission plugin produces one or more files, it should implement "get_files" so that the portfolio API can export a list of all the files from all of the plugins for this assignment submission. This is also used by the offline grading feature in the assignment.
+
+#### view_summary()
+
+```php
+public function view_summary(stdClass $submission, & $showviewlink) {
+ $count = $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA);
+
+ // Show we show a link to view all files for this plugin.
+ $showviewlink = $count > ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES;
+ if ($count <= ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES) {
+ return $this->assignment->render_area_files(
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $submission->id
+ );
+ }
+
+ return get_string('countfiles', 'assignsubmission_file', $count);
+}
+```
+
+The view_summary function is called to display a summary of the submission to both markers and students. It counts the number of files submitted and if it is more that a set number, it only displays a count of how many files are in the submission - otherwise it uses a helper function to write the entire list of files. This is because we want to keep the summaries really short so they can be displayed in a table. There will be a link to view the full submission on the submission status page.
+
+#### view()
+
+```php
+public function view($submission) {
+ return $this->assignment->render_area_files(
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $submission->id
+ );
+}
+```
+
+The view function is called to display the entire submission to both markers and students. In this case it uses the helper function in the assignment class to write the list of files.
+
+#### submission_summary_for_email()
+
+```php
+#[\Override]
+public function submission_summary_for_messages(stdClass $submission): array {
+ global $PAGE;
+
+ $onlinetextsubmission = $this->get_onlinetext_submission($submission->id);
+ if (!$onlinetextsubmission || !$onlinetextsubmission->onlinetext) {
+ return ['', ''];
+ }
+
+ $renderer = $PAGE->get_renderer('mod_assign');
+ $templatecontext = ['wordcount' => count_words($onlinetextsubmission->onlinetext)];
+ return [
+ // Mustache strips off all trailing whitespace, but we want a newline at the end.
+ $renderer->render_from_template(
+ 'assignsubmission_onlinetext/notification_text', $templatecontext) . "\n",
+ $renderer->render_from_template(
+ 'assignsubmission_onlinetext/notification_html', $templatecontext),
+ ];
+}
+```
+
+This method produces a summary of what was submitted, in a form suitable to include in messages (for example, the 'You have submitted' confirmation). Moodle messages can be plain text or HTML, so you need to prepare both. It is recommended to use templates so themes can override it if they want to. As you produce your output, think about the fact that multiple Submission plugins may be in use at the same time, in which case your summary will appear alongside other summaries.
+
+#### can_upgrade()
+
+```php
+public function can_upgrade($type, $version) {
+ $uploadsingle_type ='uploadsingle';
+ $upload_type ='upload';
+
+ if (($type == $uploadsingle_type || $type == $upload_type) && $version >= 2011112900) {
+ return true;
+ }
+ return false;
+}
+```
+
+The can_upgrade function is used to identify old "Assignment 2.2" subtypes that can be upgraded by this plugin. This plugin supports upgrades from the old "upload" and "uploadsingle" assignment subtypes.
+
+#### upgrade_settings()
+
+```php
+public function upgrade_settings(context $oldcontext, stdClass $oldassignment, &$log) {
+ global $DB;
+
+ if ($oldassignment->assignmenttype == 'uploadsingle') {
+ $this->set_config('maxfilesubmissions', 1);
+ $this->set_config('maxsubmissionsizebytes', $oldassignment->maxbytes);
+ return true;
+ }
+
+ if ($oldassignment->assignmenttype == 'upload') {
+ $this->set_config('maxfilesubmissions', $oldassignment->var1);
+ $this->set_config('maxsubmissionsizebytes', $oldassignment->maxbytes);
+
+ // Advanced file upload uses a different setting to do the same thing.
+ $DB->set_field(
+ 'assign',
+ 'submissiondrafts',
+ $oldassignment->var4,
+ ['id' => $this->assignment->get_instance()->id]
+ );
+
+ // Convert advanced file upload "hide description before due date" setting.
+ $alwaysshow = 0;
+ if (!$oldassignment->var3) {
+ $alwaysshow = 1;
+ }
+ $DB->set_field(
+ 'assign',
+ 'alwaysshowdescription',
+ $alwaysshow,
+ ['id' => $this->assignment->get_instance()->id]
+ );
+ return true;
+ }
+}
+```
+
+This function is called once per assignment instance to upgrade the settings from the old assignment to the new mod_assign. In this case it sets the `maxbytes`, `maxfiles` and `alwaysshowdescription` configuration settings.
+
+#### upgrade()
+
+```php
+public function upgrade($oldcontext, $oldassignment, $oldsubmission, $submission, &$log) {
+ global $DB;
+
+ $filesubmission = (object) [
+ 'numfiles' => $oldsubmission->numfiles,
+ 'submission' => $submission->id,
+ 'assignment' => $this->assignment->get_instance()->id,
+ ];
+
+ if (!$DB->insert_record('assign_submission_file', $filesubmission) > 0) {
+ $log .= get_string('couldnotconvertsubmission', 'mod_assign', $submission->userid);
+ return false;
+ }
+
+ // now copy the area files
+ $this->assignment->copy_area_files_for_upgrade(
+ $oldcontext->id,
+ 'mod_assignment',
+ 'submission',
+ $oldsubmission->id,
+ // New file area
+ $this->assignment->get_context()->id,
+ 'mod_assign',
+ ASSIGN_FILEAREA_SUBMISSION_FILES,
+ $submission->id
+ );
+
+ return true;
+}
+```
+
+The "upgrade" function upgrades a single submission from the old assignment type to the new one. In this case it involves copying all the files from the old filearea to the new one. There is a helper function available in the assignment class for this (Note: the copy will be fast as it is just adding rows to the files table). If this function returns false, the upgrade will be aborted and rolled back.
+
+#### get_editor_fields()
+
+```php
+public function () {
+ return [
+ 'onlinetext' => get_string('pluginname', 'assignsubmission_comments'),
+ ];
+}
+```
+
+This example is from assignsubmission_onlinetext. If the plugin uses a text-editor it is ideal if the plugin implements "get_editor_fields". This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. If a plugin supports multiple text areas it can return the name of each of them here.
+
+#### get_editor_text()
+
+```php
+public function get_editor_text($name, $submissionid) {
+ if ($name == 'onlinetext') {
+ $onlinetextsubmission = $this->get_onlinetext_submission($submissionid);
+ if ($onlinetextsubmission) {
+ return $onlinetextsubmission->onlinetext;
+ }
+ }
+
+ return '';
+}
+```
+
+This example is from assignsubmission_onlinetext. If the plugin uses a text-editor it is ideal if the plugin implements "get_editor_text". This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. The name is used to distinguish between multiple text areas in the one plugin.
+
+#### get_editor_format()
+
+```php
+public function get_editor_format($name, $submissionid) {
+ if ($name == 'onlinetext') {
+ $onlinetextsubmission = $this->get_onlinetext_submission($submissionid);
+ if ($onlinetextsubmission) {
+ return $onlinetextsubmission->onlineformat;
+ }
+ }
+
+ return 0;
+}
+```
+
+This example is from assignsubmission_onlinetext. For the same reason as the previous function, if the plugin uses a text editor, it is ideal if the plugin implements "get_editor_format". This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. The name is used to distinguish between multiple text areas in the one plugin.
+
+#### is_empty()
+
+```php
+public function is_empty(stdClass $submission) {
+ return $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA) == 0;
+}
+```
+
+If a plugin has no submission data to show - it can return true from the is_empty function. This prevents a table row being added to the submission summary for this plugin. It is also used to check if a student has tried to save an assignment with no data.
+
+#### submission_is_empty()
+
+```php
+public function submission_is_empty() {
+ global $USER;
+ $fs = get_file_storage();
+
+ // Get a count of all the draft files, excluding any directories.
+ $files = $fs->get_area_files(
+ context_user::instance($USER->id)->id,
+ 'user',
+ 'draft',
+ $data->files_filemanager,
+ 'id',
+ false
+ );
+
+ return count($files) == 0;
+}
+```
+
+Determine if a submission is empty. This is distinct from is_empty() in that it is intended to be used to determine if a submission made before saving is empty.
+
+#### get_file_areas()
+
+```php
+public function get_file_areas() {
+ return [ASSIGNSUBMISSION_FILE_FILEAREA=>$this->get_name()];
+}
+```
+
+A plugin should implement get_file_areas if it supports saving of any files to moodle - this allows the file areas to be browsed by the moodle file manager.
+
+#### copy_submission()
+
+```php
+public function copy_submission(stdClass $sourcesubmission, stdClass $destsubmission) {
+ global $DB;
+
+ // Copy the files across.
+ $contextid = $this->assignment->get_context()->id;
+ $fs = get_file_storage();
+ $files = $fs->get_area_files(
+ $contextid,
+ 'assignsubmission_file',
+ ASSIGNSUBMISSION_FILE_FILEAREA,
+ $sourcesubmission->id,
+ 'id',
+ false
+ );
+ foreach ($files as $file) {
+ $fieldupdates = ['itemid' => $destsubmission->id];
+ $fs->create_file_from_storedfile($fieldupdates, $file);
+ }
+
+ // Copy the assignsubmission_file record.
+ if ($filesubmission = $this->get_file_submission($sourcesubmission->id)) {
+ unset($filesubmission->id);
+ $filesubmission->submission = $destsubmission->id;
+ $DB->insert_record('assignsubmission_file', $filesubmission);
+ }
+ return true;
+}
+```
+
+Since Moodle 2.5 - a students submission can be copied to create a new submission attempt. Plugins should implement this function if they store data associated with the submission (most plugins).
+
+#### format_for_log()
+
+```php
+public function format_for_log(stdClass $submission) {
+ // Format the information for each submission plugin add_to_log
+ $filecount = $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA);
+ return ' the number of file(s) : ' . $filecount . " file(s). ";
+}
+```
+
+The format_for_log function lets a plugin produce a really short summary of a submission suitable for adding to a log message.
+
+#### delete_instance()
+
+```php
+public function delete_instance() {
+ global $DB;
+ // Will throw exception on failure
+ $DB->delete_records('assignsubmission_file', [
+ 'assignment'=>$this->assignment->get_instance()->id,
+ ]);
+
+ return true;
+}
+```
+
+The delete_instance function is called when a plugin is deleted. Note only database records need to be cleaned up - files belonging to fileareas for this assignment will be automatically cleaned up.
+
+## Useful classes
+
+A submission plugin has access to a number of useful classes in the assignment module. See the phpdocs (or the code) for more information on these classes.
+
+### assign_plugin
+
+This abstract class is the base class for all assignment plugins (feedback or submission plugins).
+
+It provides access to the assign class which represents the current assignment instance through "$this->assignment".
+
+### assign_submission_plugin
+
+This is the base class all assignment submission plugins must extend. It contains a small number of additional function that only apply to submission plugins.
+
+### assign
+
+This is the main class for interacting with the assignment module.
+
+It contains public functions that are useful for listing users, loading and updating submissions, loading and updating grades, displaying users etc.
+
+## Other features
+
+### Add calendar events
+
+
+
+Submission plugins can add events to the Moodle calendar without side effects. These will be hidden and deleted in line with the assignment module. For example:
+
+```php
+// Add release date to calendar.
+$calendarevent = new stdClass();
+$calendarevent->name = get_string('calendareventname', 'assignsubmission_something');
+$calendarevent->description = get_string('calendareventdesc', 'assignsubmission_something');
+$calendarevent->courseid = $courseid;
+$calendarevent->groupid = 0;
+$calendarevent->userid = $userid;
+$calendarevent->modulename = 'assign';
+$calendarevent->instance = $instanceid;
+$calendarevent->eventtype = 'something_release'; // For activity module's events, this can be used to set the alternative text of the event icon. Set it to 'pluginname' unless you have a better string.
+$calendarevent->timestart = $releasedate;
+$calendarevent->visible = true;
+$calendarevent->timeduration = 0;
+
+calendar_event::create($calendarevent);
+```
+
+This code should be placed in the `save_settings()` method of your assign_submission_plugin class.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/button.md b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/button.md
new file mode 100644
index 0000000000..e2b7e9c1b6
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/button.md
@@ -0,0 +1,4 @@
+
+The plugin must implement a YUI module that will be included by the editor when the page loads.
+
+That YUI module **must** be named `button` and must create a namespaced class in `Y.M.[plugin name]`.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/button.tsx b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/button.tsx
new file mode 100644
index 0000000000..011667eb0c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/button.tsx
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { Props } from '../../../../_utils';
+
+const defaultExample = `Y.namespace('M.atto_media').Button = Y.Base.create(
+ 'button',
+ Y.M.editor_atto.EditorPlugin,
+ [],
+ {
+ initializer: function() {
+ this.addButton({
+ callback: this._toggleMedia,
+ icon: 'e/media',
+ inlineFormat: true,
+
+ // Key code for the keyboard shortcut which triggers this button:
+ keys: '66',
+
+ // Watch the following tags and add/remove highlighting as appropriate:
+ tags: 'media'
+ });
+ },
+
+ _toggleMedia: function() {
+ // Handle the button click here.
+ // You can fetch any passed in parameters here as follows:
+ var langs = this.get('langs');
+ }
+ }, {
+ ATTRS: {
+ // If any parameters were defined in the 'params_for_js' function,
+ // they should be defined here for proper access.
+ langs: {
+ value: ['Default', 'Value']
+ }
+ }
+ }
+);
+`;
+import ButtonDescription from './button.md';
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/lib.md b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/lib.md
new file mode 100644
index 0000000000..b303cf986e
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/lib.md
@@ -0,0 +1,7 @@
+
+An optional file which can be used to implement optional component callbacks.
+
+The available callbacks are:
+
+- `atto_[pluginname]_strings_for_js` - To add strings required by the YUI code
+- `atto_[pluginname]_params_for_js` - To get the parameters required to instantiate the plugin
diff --git a/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/lib.php b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/lib.php
new file mode 100644
index 0000000000..ef29ea076f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/atto/_examples/lib.php
@@ -0,0 +1,37 @@
+/**
+ * Initialise the js strings required for this plugin.
+ */
+function atto_media_strings_for_js(): void {
+ global $PAGE;
+
+ $PAGE->requires->strings_for_js([
+ 'add',
+ 'width',
+ ], 'atto_media');
+}
+
+/**
+ * Sends the parameters to the JS module.
+ *
+ * @return array
+ */
+function atto_media_params_for_js(): array {
+ global $OUTPUT, $PAGE;
+ $currentlang = current_language();
+ $langsinstalled = get_string_manager()->get_list_of_translations(true);
+ $params = [
+ 'langs' => [
+ 'installed' => [],
+ ],
+ ];
+
+ foreach ($langsinstalled as $code => $name) {
+ $params['langs']['installed'][] = [
+ 'lang' => $name,
+ 'code' => $code,
+ 'default' => $currentlang == $code,
+ ];
+ }
+
+ return $params;
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/atto/index.md b/versioned_docs/version-5.1/apis/plugintypes/atto/index.md
new file mode 100644
index 0000000000..b357b9f1cf
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/atto/index.md
@@ -0,0 +1,112 @@
+---
+title: Atto
+tags: []
+---
+
+
+
+Atto is a JavaScript text editor built specifically for Moodle. It is the default text editor in Moodle from 2.7 onwards, and is implemented as a standard Moodle [text editor plugin](./../../subsystems/editor/index.md). Most of the code is written in JavaScript using YUI modules.
+
+All of the buttons in Atto are implemented as Moodle subplugins. This means that the subplugins can do anything a subplugin can do including, using language strings, database tables, other JavaScript, and more.
+
+:::caution Sunset of Atto
+
+A new Editor was created for Moodle 4.1 and later using the latest version of TinyMCE.
+
+Atto has been removed in Moodle 5.0.
+
+:::
+
+## File structure
+
+import {
+ Lang,
+ Lib,
+ VersionPHP,
+} from '../../_files';
+import Button from './_examples/button';
+
+Atto plugins are located in the `/lib/editor/atto/plugins` directory.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+
+ View an example directory layout for the `atto_media` plugin.
+
+```console
+ lib/editor/atto/plugins/media
+ |-- db
+ | └-- upgrade.php
+ |-- lang
+ | └-- en
+ | └-- atto_media.php
+ |-- yui
+ | └-- src
+ | └-- button
+ | └-- atto_media.php
+ | ├── build.json
+ | ├── js
+ | │ └── button.js
+ | └── meta
+ | └── button.json
+ |-- settings.php
+ └-- version.php
+```
+
+
+
+Some of the important files for the Atto plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### version.php
+
+
+
+
+
+### lib.php
+
+import LibExample from '!!raw-loader!./_examples/lib.php';
+import LibDescription from './_examples/lib.md';
+
+
+
+### yui/src/button/*
+
+
+
+:::note
+
+It is recommended that you extend the `EditorPlugin` class as described below.
+See: [YUI/Modules](../../../guides/javascript/yui/modules.md) for more information about YUI modules.
+
+:::
+
+The plugin:
+
+- **must** register a class at `Y.M.atto_PLUGINNAME.button`;
+- **must** provide a constructor; and
+- ***should*** extend [Y.M.editor_atto.EditorPlugin](https://github.com/moodle/moodle/blob/MOODLE_37_STABLE/lib/editor/atto/yui/src/editor/js/editor-plugin.js).
+
+#### EditorPlugin
+
+It is up to the plugin author to decide how best to write their plugin, but it is highly advisable to extend `EditorPlugin` class, which provides a number of useful functions for dealing with the Editor, Toolbars, Keyboard Navigation, and other related areas.
+
+Of particular interest are:
+
+- [addBasicButton](https://github.com/moodle/moodle/blob/MOODLE_37_STABLE/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js#L293) - to add a basic button which directly uses document.execCommand with minimal effort;
+- [addButton](https://github.com/moodle/moodle/blob/MOODLE_37_STABLE/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js#L161) - to add a button giving you a greater degree of control via your own callback;
+- [addToolbarMenu](https://github.com/moodle/moodle/blob/MOODLE_37_STABLE/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js#L337) - to add a dropdown toolbar menu;
+- [markUpdated](https://github.com/moodle/moodle/blob/MOODLE_37_STABLE/lib/editor/atto/yui/src/editor/js/editor-plugin.js#L91) - should be called after making changes to the content area; and
+- [getDialogue](https://github.com/moodle/moodle/blob/MOODLE_37_STABLE/lib/editor/atto/yui/src/editor/js/editor-plugin-dialogue.js#L54) - return a standard dialogue, creating one if it does not already exist.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/availability/_examples/lang.md b/versioned_docs/version-5.1/apis/plugintypes/availability/_examples/lang.md
new file mode 100644
index 0000000000..4e73de046c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/availability/_examples/lang.md
@@ -0,0 +1,14 @@
+
+
+Each plugin must define a set of language strings with, at a minimum, an English translation. These are specified in the plugin's lang/en directory in a file named after the plugin. For example the LDAP authentication plugin:
+
+Language strings for the plugin. Required strings:
+
+- **pluginname** - name of plugin.
+- **title** - text of button for adding this type of plugin.
+- **description** - explanatory text that goes alongside the button in the 'add restriction' dialog.
+
+You will usually need to add your own strings for two main purposes:
+
+- Creating suitable form controls for users who are editing the activity settings.
+- Displaying information about the condition.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/availability/_examples/lang.php b/versioned_docs/version-5.1/apis/plugintypes/availability/_examples/lang.php
new file mode 100644
index 0000000000..307d2e9ca6
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/availability/_examples/lang.php
@@ -0,0 +1,3 @@
+$string['description'] = 'Allow only students who belong to a group within a specified grouping.';
+$string['pluginname'] = 'Restriction by grouping';
+$string['title'] = 'Grouping';
diff --git a/versioned_docs/version-5.1/apis/plugintypes/availability/index.md b/versioned_docs/version-5.1/apis/plugintypes/availability/index.md
new file mode 100644
index 0000000000..516994caf1
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/availability/index.md
@@ -0,0 +1,338 @@
+---
+title: Availability conditions
+tags:
+ - Availability
+ - core_availability
+---
+
+Availability conditions allow teachers to restrict an activity or section so that only certain users can access it. These are accessed using the [Availability API](../../subsystems/availability/index.md).
+
+Some of the conditions included with Moodle are:
+
+- Date - users can only access activity after specified date
+- Grade - users can only access activity if they have a certain grade in another activity
+
+A relatively simple example is the grouping condition which can be found in `/availability/condition/grouping`. It is a good basis for a new plugin when starting to implement a new condition.
+
+To see this condition in action:
+
+- Go to a course and edit any section
+- Expand the **Restrict access** heading
+- Click the **Add restriction** button
+- Click **Grouping**
+
+## File structure
+
+import {
+ Lang,
+ Lib,
+} from '../../_files';
+
+All availability condition plugin files must be located inside the **/availability/condition/pluginname** folder.
+
+
+ View an example directory layout for the `availability_grouping` plugin.
+
+```console
+ availability/condition/grouping
+├── classes
+│ ├── condition.php
+│ ├── frontend.php
+├── lang
+│ └── en
+│ └── availability_grouping.php
+├── version.php
+└── yui
+ ├── build
+ │ └── moodle-availability_grouping-form
+ │ ├── moodle-availability_grouping-form-debug.js
+ │ ├── moodle-availability_grouping-form-min.js
+ │ └── moodle-availability_grouping-form.js
+ └── src
+ └── form
+ ├── build.json
+ ├── js
+ │ └── form.js
+ └── meta
+ └── form.json
+```
+
+
+
+Some of the important files for the format plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### lang/en/availability_name.php
+
+import langExample from '!!raw-loader!./_examples/lang.php';
+import langDescription from './_examples/lang.md';
+
+
+
+### classes/condition.php
+
+This PHP class implements the back-end of the condition; in other words, this class contains the code which decides whether a user is allowed to access an activity that uses this condition, or not.
+
+Here's an outline of the code (with standard PHPdoc comments omitted to save space) for a simple example in which there is a boolean value that controls whether access is allowed or not.
+
+```php
+// You must use the right namespace (matching your plugin component name).
+namespace availability_name;
+
+class condition extends \core_availability\condition {
+ // Any data associated with the condition can be stored in member
+ // variables. Here's an example variable:
+ protected $allow;
+
+ public function __construct($structure) {
+ // Retrieve any necessary data from the $structure here. The
+ // structure is extracted from JSON data stored in the database
+ // as part of the tree structure of conditions relating to an
+ // activity or section.
+ // For example, you could obtain the 'allow' value:
+ $this->allow = $structure->allow;
+
+ // It is also a good idea to check for invalid values here and
+ // throw a coding_exception if the structure is wrong.
+ }
+
+ public function save() {
+ // Save back the data into a plain array similar to $structure above.
+ return (object)array('type' => 'name', 'allow' => $this->allow);
+ }
+
+ public function is_available(
+ $not,
+ \core_availability\info $info,
+ $grabthelot,
+ $userid
+ ) {
+ // This function needs to check whether the condition is available
+ // or not for the user specified in $userid.
+
+ // The value $not should be used to negate the condition. Other
+ // parameters provide data which can be used when evaluating the
+ // condition.
+
+ // For this trivial example, we will just use $allow to decide
+ // whether it is allowed or not. In a real condition you would
+ // do some calculation depending on the specified user.
+ $allow = $this->allow;
+ if ($not) {
+ $allow = !$allow;
+ }
+ return $allow;
+ }
+
+ public function get_description(
+ $full,
+ $not,
+ \core_availability\info $info
+ ) {
+ // This function returns the information shown about the
+ // condition on editing screens.
+ // Usually it is similar to the information shown if the
+ // user doesn't meet the condition.
+ // Note: it does not depend on the current user.
+ $allow = $not ? !$this->allow : $this->allow;
+ return $allow ? 'Users are allowed' : 'Users not allowed';
+ }
+
+ protected function get_debug_string() {
+ // This function is only normally used for unit testing and
+ // stuff like that. Just make a short string representation
+ // of the values of the condition, suitable for developers.
+ return $this->allow ? 'YES' : 'NO';
+ }
+}
+```
+
+There are other functions you might also want to implement. For example, if your condition should apply to lists of users (in general, conditions which are 'permanent' such as group conditions apply to lists, whereas those which are 'temporary' such as date or grade conditions do not) then you should also implement is_applied_to_user_lists and filter_user_list functions. To see the full list, look at the PHPdoc for the condition and tree_node classes inside availability/classes.
+
+### classes/frontend.php
+
+You will also need to write a frontend.php class which defines the behaviour of your plugin within the editing form (when a teacher is editing the activity settings).
+
+The class is required, but all the functions are theoretically optional; you can leave them out if you don't need any special behaviour for that function. In practice it's likely you will need at least one of them.
+
+```php
+namespace availability_name;
+
+class frontend extends \core_availability\frontend {
+
+ protected function get_javascript_strings() {
+ // You can return a list of names within your language file and the
+ // system will include them here.
+ // Should you need strings from another language file, you can also
+ // call $PAGE->requires->strings_for_js manually from here.)
+ return [];
+ }
+
+ protected function get_javascript_init_params(
+ $course,
+ \cm_info $cm = null,
+ \section_info $section = null
+ ) {
+ // If you want, you can add some parameters here which will be
+ // passed into your JavaScript init method. If you don't include
+ // this function, there will be no parameters.
+ return ['frog'];
+ }
+
+ protected function allow_add(
+ $course,
+ \cm_info $cm = null,
+ \section_info $section = null
+ ) {
+ // This function lets you control whether the 'add' button for your
+ // plugin appears. For example, the grouping plugin does not appear
+ // if there are no groupings on the course. This helps to simplify
+ // the user interface. If you don't include this function, it will
+ // appear.
+ return true;
+ }
+}
+```
+
+### YUI
+
+The Availability API generates a dialogue to allow teachers to configure the availability conditions. Each availability plugin can add to this form by writing a JavaScript module in the YUI format which generates its form fields, errors, and configuration.
+
+:::note
+
+Although JavaScript standards in Moodle have moved on, the core availability system is implemented in YUI, so for now, the plugins need to use YUI too. (Please, someone, do [MDL-69566](https://moodle.atlassian.net/browse/MDL-69566)!)
+
+:::
+
+YUI does require more boilerplate configuration that AMD modules, but the same build toolset is used as for AMD modules and you can still make use of the `grunt watch` command.
+
+#### yui/src/form/meta/form.json
+
+The metadata file lists any dependencies that your YUI module has on other code.
+
+Typically this will include the `moodle-core_availability-form` dependency, and possibly some other YUI dependencies.
+
+```javascript title="availability/condition/example/yui/src/form/meta/form.json"
+{
+ "moodle-availability_name-form": {
+ "requires": [
+ "base",
+ "node",
+ "event",
+ "moodle-core_availability-form"
+ ]
+ }
+}
+```
+
+#### yui/src/form/build.json
+
+The build.json file describes how the YUI compiler will build your YUI module.
+
+YUI modules can be broken down into smaller, succint, pieces of code. This is very useful for larger modules, but rarely necessary in smaller code.
+
+Typically you should only need to set the name of your plugin in this file>
+
+```javascript title="availability/condition/example/yui/src/form/build.json"
+{
+ "name": "moodle-availability_name-form",
+ "builds": {
+ "moodle-availability_name-form": {
+ "jsfiles": [
+ "form.js"
+ ]
+ }
+ }
+}
+```
+
+#### yui/src/js/form.js
+
+This file contains the actual JavaScript code for your plugin. It should follow the below format in order to integrate with the core JavaScript. Additional feautres are available and you can add any extra functions you like to break your code down too.
+
+```javascript title="availability/condition/example/yui/src/js/form.js"
+M.availability_name = M.availability_name || {};
+
+M.availability_name.form = Y.Object(M.core_availability.plugin);
+
+M.availability_name.form.initInner = function(param) {
+ // The 'param' variable is the parameter passed through from PHP (you
+ // can have more than one if required).
+
+ // Using the PHP code above it'll show 'The param was: frog'.
+ console.log('The param was: ' + param);
+};
+
+M.availability_name.form.getNode = function(json) {
+ // This function does the main work. It gets called after the user
+ // chooses to add an availability restriction of this type. You have
+ // to return a YUI node representing the HTML for the plugin controls.
+
+ // Example controls contain only one tickbox.
+ var html = '';
+ var node = Y.Node.create('' + html + '');
+
+ // Set initial values based on the value from the JSON data in Moodle
+ // database. This will have values undefined if creating a new one.
+ if (json.allow) {
+ node.one('input').set('checked', true);
+ }
+
+ // Add event handlers (first time only). You can do this any way you
+ // like, but this pattern is used by the existing code.
+ if (!M.availability_name.form.addedEvents) {
+ M.availability_name.form.addedEvents = true;
+ var root = Y.one('#fitem_id_availabilityconditionsjson');
+ root.delegate('click', function() {
+ // The key point is this update call. This call will update
+ // the JSON data in the hidden field in the form, so that it
+ // includes the new value of the checkbox.
+ M.core_availability.form.update();
+ }, '.availability_name input');
+ }
+
+ return node;
+};
+
+M.availability_name.form.fillValue = function(value, node) {
+ // This function gets passed the node (from above) and a value
+ // object. Within that object, it must set up the correct values
+ // to use within the JSON data in the form. Should be compatible
+ // with the structure used in the __construct and save functions
+ // within condition.php.
+ var checkbox = node.one('input');
+ value.allow = checkbox.get('checked') ? true : false;
+};
+
+M.availability_name.form.fillErrors = function(errors, node) {
+ // If the user has selected something invalid, this optional
+ // function can be included to report an error in the form. The
+ // error will show immediately as a 'Please set' tag, and if the
+ // user saves the form with an error still in place, they'll see
+ // the actual error text.
+
+ // In this example an error is not possible...
+ if (false) {
+ // ...but this is how you would add one if required. This is
+ // passing your component name (availability_name) and the
+ // name of a string within your lang file (error_message)
+ // which will be shown if they submit the form.
+ errors.push('availability_name:error_message');
+ }
+};
+```
+
+### Testing
+
+We strongly recommend writing both unit tests, and functional tests for your availability conditions.
+
+## See also
+
+- Using the [Availability API as a consumer](../../subsystems/availability/index.md)
+- [Conditional activities API](../../core/conditionalactivities/index.md)
diff --git a/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/access.php b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/access.php
new file mode 100644
index 0000000000..0b702aa8af
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/access.php
@@ -0,0 +1,20 @@
+$capabilities = [
+ 'block/pluginname:myaddinstance' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_SYSTEM,
+ 'archetypes' => [
+ 'user' => CAP_ALLOW
+ ],
+ 'clonepermissionsfrom' => 'moodle/my:manageblocks'
+ ],
+ 'block/pluginname:addinstance' => [
+ 'riskbitmask' => RISK_SPAM | RISK_XSS,
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_BLOCK,
+ 'archetypes' => [
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW
+ ],
+ 'clonepermissionsfrom' => 'moodle/site:manageblocks'
+ ],
+];
diff --git a/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/block_pluginname.php b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/block_pluginname.php
new file mode 100644
index 0000000000..7482aa9f2c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/block_pluginname.php
@@ -0,0 +1,49 @@
+class block_pluginname extends block_base {
+
+ /**
+ * Initialises the block.
+ *
+ * @return void
+ */
+ public function init() {
+ $this->title = get_string('pluginname', 'block_pluginname');
+ }
+
+ /**
+ * Gets the block contents.
+ *
+ * @return string The block HTML.
+ */
+ public function get_content() {
+ global $OUTPUT;
+
+ if ($this->content !== null) {
+ return $this->content;
+ }
+
+ $this->content = new stdClass();
+ $this->content->footer = '';
+
+ // Add logic here to define your template data or any other content.
+ $data = ['YOUR DATA GOES HERE'];
+
+ $this->content->text = $OUTPUT->render_from_template('block_yourplugin/content', $data);
+
+ return $this->content;
+ }
+
+ /**
+ * Defines in which pages this block can be added.
+ *
+ * @return array of the pages where the block can be added.
+ */
+ public function applicable_formats() {
+ return [
+ 'admin' => false,
+ 'site-index' => true,
+ 'course-view' => true,
+ 'mod' => false,
+ 'my' => true,
+ ];
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/edit_form.php b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/edit_form.php
new file mode 100644
index 0000000000..0951869db3
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/edit_form.php
@@ -0,0 +1,10 @@
+class block_pluginname_edit_form extends block_edit_form {
+ protected function specific_definition($mform) {
+ // Section header title according to language file.
+ $mform->addElement('header', 'config_header', get_string('blocksettings', 'block'));
+ // A sample string variable with a default value.
+ $mform->addElement('text', 'config_text', get_string('blockstring', 'block_pluginname'));
+ $mform->setDefault('config_text', 'default value');
+ $mform->setType('config_text', PARAM_TEXT);
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml
new file mode 100644
index 0000000000..43bfafbab6
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml
@@ -0,0 +1,103 @@
+## This is an example recipe file that you can use as a template for your own plugins.
+## See the list of all files it would generate:
+##
+## php generate.php example.yaml --list-files
+##
+## View a particular file contents without actually writing it to the disk:
+##
+## php generate.php example.yaml --file=version.php
+##
+## To see the full list of options, run:
+##
+## php generate.php --help
+##
+---
+## Frankenstyle component name.
+component: block_pluginname
+
+## Human readable name of the plugin.
+name: Example block
+
+## Human readable release number.
+release: "0.1.0"
+
+## Plugin version number, e.g. 2016062100. Will be set to current date if left empty.
+#version: 2016121200
+
+## Required Moodle version, e.g. 2015051100 or "2.9".
+requires: "3.11"
+
+## Plugin maturity level. Possible options are MATURIY_ALPHA, MATURITY_BETA,
+## MATURITY_RC or MATURIY_STABLE.
+maturity: MATURITY_BETA
+
+## Copyright holder(s) of the generated files and classes.
+copyright: Year, You Name
+
+## Features flags can control generation of optional files/code fragments.
+features:
+ readme: true
+ license: true
+
+ ## Privacy API implementation
+privacy:
+ haspersonaldata: false
+ uselegacypolyfill: false
+
+block_features:
+ ## Creates the file edit_form.php
+ edit_form: true
+
+ ## Allows multiple instances of the block on the same course.
+ instance_allow_multiple: false
+
+ ## Choose where to display the block.
+ applicable_formats:
+ - page: all
+ allowed: false
+ - page: course-view
+ allowed: true
+ - page: course-view-social
+ allowed: false
+
+ ## Backup the block plugin.
+ backup_moodle2:
+ restore_task: true
+ restore_stepslib: true
+ backup_stepslib: true
+ settingslib: true
+ backup_elements:
+ - name: elt
+ restore_elements:
+ - name: elt
+ path: /path/to/file
+
+## Capabilities defined by the plugin.
+capabilities:
+ ## Required by block plugins.
+ - name: myaddinstance
+ title: Add a new pluginname block to the My dashboard
+ captype: write
+ contextlevel: CONTEXT_SYSTEM
+ archetypes:
+ - role: user
+ permission: CAP_ALLOW
+ clonepermissionsfrom: moodle/my:manageblocks
+
+ - name: addinstance
+ title: Add a new pluginname block
+ captype: write
+ contextlevel: CONTEXT_BLOCK
+ archetypes:
+ - role: editingteacher
+ permission: CAP_ALLOW
+ - role: manager
+ permission: CAP_ALLOW
+ clonepermissionsfrom: moodle/site:manageblocks
+
+## Explicitly added strings
+lang_strings:
+ - id: mycustomstring
+ text: You can add 'extra' strings via the recipe file.
+ - id: mycustomstring2
+ text: Another string with {$a->some} placeholder.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/blocks/index.md b/versioned_docs/version-5.1/apis/plugintypes/blocks/index.md
new file mode 100644
index 0000000000..a71a7df1f2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/blocks/index.md
@@ -0,0 +1,436 @@
+---
+title: Block plugins
+tags:
+ - Blocks
+ - Tutorial
+ - Plugins
+---
+
+import {
+ Lang,
+ VersionPHP,
+ DbAccessPHP,
+} from '../../_files';
+import { ComponentFileSummary } from '../../../_utils';
+
+Block plugins allow you to show supplemental information, and features, within different parts of Moodle.
+
+## File structure
+
+Blocks plugins are located in the `/blocks` directory.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+
+ View an example directory layout for the `block_pluginname` plugin.
+
+```console
+ blocks/pluginname/
+ |-- db
+ | `-- access.php
+ |-- lang
+ | `-- en
+ | `-- block_pluginname.php
+ |-- pix
+ | `-- icon.png
+ |-- block_pluginname.php
+ |-- edit_form.php (optional)
+ `-- version.php
+```
+
+
+
+### block_pluginname.php
+
+import BlockFile from '!!raw-loader!./_examples/block_pluginname.php';
+
+
+
+:::info
+
+The `init` method is essential for all blocks, and its purpose is to give values to any class member variables that need instantiating.
+
+:::
+
+### db/access.php
+
+import accessExample from '!!raw-loader!./_examples/access.php';
+
+
+
+### lang/en/block_pluginname.php
+
+export const langExample = `$string['pluginname'] = 'Pluginname block';
+$string['pluginname'] = 'Pluginname';
+$string['pluginname:addinstance'] = 'Add a new pluginname block';
+$string['pluginname:myaddinstance'] = 'Add a new pluginname block to the My Moodle page';`;
+
+
+
+### version.php
+
+
+
+### edit_form.php
+
+import EditForm from '!!raw-loader!./_examples/edit_form.php';
+
+
+
+The example below adds a text attribute to the block instance settings.
+
+:::caution
+
+All your field names need to start with **"config_"**, otherwise they will not be saved and will not be available within the block via $this->config.
+
+:::
+
+## Creating a new block plugin
+
+The easiest way to create a new block plugin is by using the latest version of [Tool Pluginskel](https://moodle.org/plugins/tool_pluginskel). You can use the following yaml file to generate a basic block skeleton.
+
+
+ View pluginskel recipe
+
+
+import PluginskelRecipe from '!!raw-loader!./_examples/pluginskel_recipe.yaml';
+
+{PluginskelRecipe}
+
+
+
+
+## Block base class API methods
+
+All blocks must provide a main class that extends the core block class. However, there are two different types of blocks:
+
+- `block_base` - The default base class for content blocks.
+- `block_list` - For blocks that displays a list items.
+
+Depending on your plugin needs your main class in `blocks/pluginname/block_pluginname.php` must extend either `block_base` or `block_list`.
+
+### Block class attributes
+
+Once the block instance is created, there are several $this attributes that can be used:
+
+- `$this->config` The block instance configuration. By default it is an empty object but if the block has an [edit_form.php](#edit_formphp) file, it will be an object with the form data.
+- `$this->content` This variable holds all the actual content that is displayed inside each block. Valid values for it are either NULL or an object of class stdClass, which must have specific member variables depending on the extended block base class.
+- `$this->page` The page object that the block is being displayed on.
+- `$this->context` The context object that the block is being displayed in.
+- `$this->title` The title of the block.
+
+### init()
+
+The init method is called before the block is displayed. It is essential for all blocks, and its purpose is to give values to any class member variables that need instantiating. However, it is called before $this->config is set, if your plugin needs some configation value to define global attributes like the block title, it should be done in the specialization method.
+
+### specialization()
+
+This function is called on your subclass right after an instance is loaded. It is used to customize the title and other block attributes depending on the page type, context, configuration, etc.
+
+
+ View example
+
+
+Example of a specialization method using the instance configuration.
+
+```php
+function specialization() {
+ if (isset($this->config->title)) {
+ $this->title = format_string($this->config->title, true, ['context' => $this->context]);
+ } else {
+ $this->title = get_string('newhtmlblock', 'block_html');
+ }
+}
+```
+
+
+
+
+### get_content(): string
+
+In order to get our block to actually display something on screen, we need to add one more method to our class (before the final closing brace in our file) inside of the block_pluginname.php script.
+
+
+
+
+```php
+class block_pluginname extends block_base {
+
+ // (...)
+
+ public function get_content() {
+ if ($this->content !== null) {
+ return $this->content;
+ }
+
+ $this->content = new stdClass;
+ $this->content->text = 'The content of pluginname block';
+ $this->content->footer = 'Footer here...';
+
+ return $this->content;
+ }
+}
+```
+
+
+
+
+```php
+class block_pluginname extends block_list {
+
+ // (...)
+
+ public function get_content() {
+ global $OUTPUT;
+ if ($this->content !== null) {
+ return $this->content;
+ }
+
+ $this->content = (object) [
+ 'items' => [],
+ 'icons' => [],
+ 'footer' => 'Footer here...',
+ ];
+
+ $this->content->items[] = 'An item of pluginname block';
+ $this->content->icons[] = $OUTPUT->pix_icon('i/course', get_string('course'));
+
+ // Add more list items here.
+
+ return $this->content;
+ }
+}
+```
+
+
+
+
+:::caution
+
+The get_content can be called several times during the page rendering. To prevent your class from calculating it every time your plugin should check if $this->content is already defined at the beginning of the method.
+
+:::
+
+:::tip
+
+If the block content is empty (an empty string) the block will not be displayed. In the case of an extending block_base block this means empty the `$this->content->text` and the `$this->content->footer` values. In a block_list block, the `$this->content->items` array should be empty. Moodle performs this check by calling the block's `is_empty()` method, and if the block is indeed empty then it is not displayed at all.
+
+:::
+
+### applicable_formats(): array
+
+Blocks can be added to any kind of page. However, some blocks may only be displayed on certain page types. This method is used to define the page types that the block can be displayed on. See [Limit the block to specific contexts](#limit-the-block-to-specific-contexts) section below for more information.
+
+### instance_allow_multiple()
+
+By default, only one instance of each block plugin can be added to a page. However, if your plugin allows multiple instances you can overrdie the instance_allow_multiple method.
+
+
+ View example
+
+
+
+:::note
+
+Even if a block itself allows multiple instances in the same page, the administrator still has the option of disallowing such behavior. This setting can be set separately for each block from the Administration / Configuration / Blocks page.
+
+:::
+
+### hide_header(): bool
+
+Using this method each block instance can decide if the standard block header is shown or not. This method will be ignored in edit mode.
+
+
+ View example
+
+
+
+### html_attributes(): array
+
+The block base class can inject extra HTML attributes to the block wrapper. This is useful for example to add a class to the block wrapper when the block is being displayed in a specific context.
+
+By default, each block section in the page will use a standard `block` class and the specific `block_pluginname` class. However, if you want to add a class to the block wrapper, you can override html_attributes to alter those attrributes.
+
+
+ View example
+
+
+```php
+public function html_attributes() {
+ // Get default values.
+ $attributes = parent::html_attributes();
+ // Append our class to class attribute.
+ $attributes['class'] .= ' block_'. $this->name();
+ return $attributes;
+}
+```
+
+
+
+
+This results in the block having all its normal HTML attributes, as inherited from the base block class, plus our additional class name. We can now use this class name to change the style of the block, add JavaScript events to it via YUI, and so on. And for one final elegant touch, we have not set the class to the hard-coded value "block_simplehtml", but instead used the Blocks/Appendix_A#name.28.29| name() method to make it dynamically match our block's name.
+
+### instance_config_save(): stdClass
+
+An optional method to modify the instance configuration before it is saved. See [add instance configuration settings](#add-instance-configuration-settings) section below for more information.
+
+### has_config(): bool
+
+An optional method to tell Moodle that the block has a global configuration settings form. See [enabling Global Configuration](#enabling-global-configuration) section below for more information.
+
+## Add instance configuration settings
+
+By default, block instances have no configuration settings. If you want to add some, you can add them by adding a few methods and classes to your block.
+
+### Create an edit_form.php file
+
+To have a configuration form, you need to add an [edit_form.php](#edit_formphp) file into your plugin. After defining the configuration, your block's base instance will have all your settings in its [$this->config attribute](#block-class-attributes). See the [edit_form.php section above](#edit_formphp) for an example.
+
+:::caution
+
+Note that $this->config is available in all block methods **except the init() one**. This is because init() is called immediately as the block is being created, with the purpose of setting things up. Use [specialization](#specialization) instead.
+
+:::
+
+:::note
+
+You cannot use the 'checkbox' element in the form (once set it will stay set). You must use advcheckbox instead.
+
+:::
+
+### Optional instance_config_save method
+
+By default, all config_* settings will be stored in the `block_instances` table. The complete form data will be encoded in base64 before storing it in the `configdata` field. Every time a block instance is initialized all that data will be decoded in the [$this->config attribute](#block-class-attributes).
+
+However, for some cases like the Atto HTML editor, you may want to store them in the database instead, or to alter the config data before storing it. In that case you can create a instance_config_save method.
+
+
+ View example
+
+
+```php title="Example of adding data before storing it
+public function instance_config_save($data,$nolongerused =false) {
+ // Example of add new data.
+ $data->somenewattribute = 'Some new value';
+
+ // Example of alter the current data.
+ $data->text = 'Some new text';
+
+ // Call the parent method to the data inside block_instance.configdata.
+ return parent::instance_config_save($data,$nolongerused);
+}
+```
+
+
+
+
+## Add global settings to the block plugin
+
+Apart from the specific block instance configuration, the block plugin can use global settings to customize its behavior. Those settings can only be set in the site administration and are a great way to customize the behavior of all blocks on a site.
+
+:::note
+
+Global settings are not part of the block instance and should be accessed via the global get_config method. For example:
+
+```php
+$settingvalue = get_config('block_pluginname', 'settingname');
+```
+
+:::
+
+### create a settings.php file
+
+Implementing such configuration for our block is quite similar to implementing the [instance configuration](#add-instance-configuration-settings). To enable global configuration for the block, your plugin should contain **/blocks/simplehtml/settings.php** file. This file will populate the global admin form with form field definitions for each setting. See [Common files: settings.php](../commonfiles#settingsphp) for more information.
+
+### Enabling Global Configuration
+
+While in other Moodle pulgins the existence of a settings.php is enough to enable global configuration, for the blocks plugins it is mandatory to override the has_config method in the base class.
+
+
+ View example
+
+
+
+## Limit the block to specific contexts
+
+Some blocks are useful in some circumstances, but not in others. An example of this would be the "Social Activities" block, which is useful in courses with the "social" course format, but not courses with the "weeks" format. Moodle allows us to declare in which pages a block is available on. The information is given to Moodle as a standard associative array, with each key corresponding to a page format and defining a boolean value (true/false) that declares whether the block should be allowed to appear in that page format.
+
+Each page in Moodle can define it's own page type name. However, there are some conventions:
+
+- `all` value is used as a catch-all option. This means that if a block returns `['all' => true]` it can be used in any kind of page.
+- `site-index` - Moodle frontpage.
+- `course-view` - Course page, independent from the course format.
+- `course-view-FORMATNAME` - Course page, with the "FORMATNAME" course format. For example, course-view-weeks is for courses with weeks format.
+- `mod` - Any activity page, independent from the module.
+- `mod-MODNAME-view` - Activity page, with the "MODNAME" activity. For example, mod-forum-view is for forums.
+- `my` - The Moodle dashboard page.
+- `admin` - Any administration page.
+
+
+ View example
+
+
diff --git a/versioned_docs/version-5.1/apis/plugintypes/communication/index.md b/versioned_docs/version-5.1/apis/plugintypes/communication/index.md
new file mode 100644
index 0000000000..80b89180dd
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/communication/index.md
@@ -0,0 +1,158 @@
+---
+title: Communication plugin
+tags:
+ - communication
+ - provider
+ - Communication provider
+ - Communication room
+ - Communication service
+ - Chat
+documentationDraft: true
+---
+
+Communication plugin allows you to create a communication provider plugin, which can be added as a part of course or any other instances.
+For example, if you want to create a new communication room and add users to that room when a new course or instance is created, you can
+create a new communication plugin and or use the existing ones and when a instance is created, updated or deleted, the communication api
+will align those changes in the provider plugin asynchronously using a scheduled task.
+
+import {
+Lang,
+} from '../../_files';
+
+## File structure
+
+Communication plugins are located in the /communication/provider directory. A plugin should not include any custom files outside its own
+plugin folder.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+:::important
+
+Some important files are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other
+files which may be useful in your plugin.
+
+:::
+
+
+ The directory layout for the `communication` plugin.
+
+```console
+communication/provider/example
+├── classes
+│ ├── communication_feature.php
+│ └── privacy
+│ └── provider.php
+├── lang
+│ └── en
+│ └── communication_example.php
+├── settings.php
+└── version.php
+```
+
+
+
+:::info
+
+You will notice that there are a couple of classes named as example as a prefix, these are feature classes and can be named however
+you like. These are feature classes which will be defined from the communication_feature.php from the plugin.
+
+:::
+
+## Key files
+
+There are a number of key files within the plugin, described below.
+
+### communication_feature.php
+
+Each plugin must implement this class and should have the exact class name. The core communication api will pick the features and actions from this class. There is no strict rule
+on which interfaces should be implemented, plugins can choose which features they support and implement accordingly. Exception is the `communication_provider` interface which
+is mandatory for all the plugins. Check below for more details on the interfaces.
+
+```php
+class communication_feature implements
+ \core_communication\communication_provider,
+ \core_communication\user_provider,
+ \core_communication\room_chat_provider,
+ \core_communication\room_user_provider {
+
+ // All the methods from interfaces should live here.
+
+}
+```
+
+## Interfaces
+
+### communication_provider
+
+This is the base communication provider interface. This interface should be used to declare the support for the instantiation method for communication providers.
+Every provider plugin must implement this interface as a bare minimum. This interface will have the following methods.
+
+#### load_for_instance()
+
+This method will have the base communication processor(core_communication\processor) object which will allow loading the communication provider for the communication api.
+
+### user_provider
+
+This is the user provider interface. This interface should be used to declare the support for the for user creation for a provider. For example, Matrix allows creation of users
+via API and the `communication_matrix` plugin can support the creation of users in Matrix, in that case, `communication_matrix` plugin should implement this interface. Some APIs might
+not need to create user as they might have been created in a different way, in that case this interface can be excluded. This interface will have the following methods.
+
+#### create_members()
+
+All the necessary code and API calls to create members for the communication room should live here.
+
+### room_chat_provider
+
+This interface will define the features for creating a room. For example, if a communication provider allows creating a room via API, this interface should be implemented.
+Let's look at the methods of this interface to get a better idea.
+
+#### create_chat_room()
+
+#### update_chat_room()
+
+All the necessary actions to create/update a provider room should live here. It is highly recommended to add necessary checking to compare the
+data passed and previous data to ensure something is changed and an update is required to make sure no unnecessary api calls are made. A bool
+value should be returned to indicate if the room is created or updated or something went wrong.
+
+#### delete_chat_room()
+
+!!Danger zone!! Any deletion or related action for the communication room should live here. Please be-careful with your actions here. A bool
+value should be returned to indicate if the room is deleted or something went wrong.
+
+#### generate_room_url()
+
+Generate a room url according to the room information, web client url or any other required information. This is an important one to allow users access the room from the UI.
+Course has an icon to access the room if a room is created for the course, this method will be used to generate the url for the room.
+
+### room_user_provider
+
+This interface will define the features for adding/removing/updating members to the room. Room members should be added when a user is enrolled or a role changes. If a provider
+allows addition of users to a room via API, this interface should be implemented. Let's look at the methods of this class to get a better idea.
+
+#### add_members_to_room()
+
+All the necessary actions to add members to a room should live here. The array of user ids must be passed here.
+
+#### update_room_membership()
+
+Updating the membership might be necessary in some cases where a user is capability changed, this method will come into play in those cases.
+
+#### remove_members_from_room()
+
+All the necessary actions to remove members from a room should live here. The array of user ids must be passed here.
+
+### synchronise_provider
+
+Communication API has a scheduled task `core_communication\task\synchronise_providers_task` which will synchronise the data from the communication provider to the current Moodle
+instance. For example, it can compare the users in the communication room and users enrolled in a course and add/remove users accordingly. The scheduled task runs and adds an
+ad-hoc task for each instance where the provider implements this interface. This feature will help keep the data on-sync between the provider and Moodle.
+
+#### synchronise_room_members()
+
+All the necessary code to synchronise the room members should live here.
+
+:::info
+
+For a real plugin example, please look at the [Matrix plugin](https://github.com/moodle/moodle/tree/main/communication/provider/matrix).
+
+:::
diff --git a/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/data_controller.php b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/data_controller.php
new file mode 100644
index 0000000000..8e2d30416a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/data_controller.php
@@ -0,0 +1,53 @@
+namespace customfield_checkbox;
+
+class data_controller extends \core_customfield\data_controller {
+
+ /**
+ * Return the name of the field where the information is stored
+ * @return string
+ */
+ public function datafield(): string {
+ return 'intvalue';
+ }
+
+ /**
+ * Add fields for editing a checkbox field.
+ *
+ * @param \MoodleQuickForm $mform
+ */
+ public function instance_form_definition(\MoodleQuickForm $mform) {
+ $field = $this->get_field();
+ $config = $field->get('configdata');
+ $elementname = $this->get_form_element_name();
+
+ // If checkbox is required (i.e. "agree to terms") then use 'checkbox' form element.
+ // The advcheckbox element cannot be used for required fields because advcheckbox elements always provide a value.
+ $isrequired = $field->get_configdata_property('required');
+ $mform->addElement($isrequired ? 'checkbox' : 'advcheckbox', $elementname, $this->get_field()->get_formatted_name());
+ $mform->setDefault($elementname, $config['checkbydefault']);
+ $mform->setType($elementname, PARAM_BOOL);
+
+ if ($isrequired) {
+ $mform->addRule($elementname, null, 'required', null, 'client');
+ }
+ }
+
+ /**
+ * Returns the default value as it would be stored in the database (not in human-readable format).
+ *
+ * @return mixed
+ */
+ public function get_default_value() {
+ return $this->get_field()->get_configdata_property('checkbydefault') ? 1 : 0;
+ }
+
+ /**
+ * Returns value in a human-readable format
+ *
+ * @return mixed|null value or null if empty
+ */
+ public function export_value() {
+ $value = $this->get_value();
+ return $value ? get_string('yes') : get_string('no');
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/data_controller.tsx b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/data_controller.tsx
new file mode 100644
index 0000000000..abc3371585
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/data_controller.tsx
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { ComponentFileSummaryProps } from '../../../../_utils';
+
+export default (initialProps: ComponentFileSummaryProps): JSX.Element => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/field_controller.php b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/field_controller.php
new file mode 100644
index 0000000000..f2572e41d6
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/field_controller.php
@@ -0,0 +1,28 @@
+namespace customfield_myfield;
+
+class field_controller extends \core_customfield\field_controller {
+
+ /** @var string Plugin type */
+ const TYPE = 'radio';
+
+ /**
+ * Add fields for editing a checkbox field.
+ *
+ * @param \MoodleQuickForm $mform
+ */
+ public function config_form_definition(\MoodleQuickForm $mform) {
+ $mform->addElement(
+ 'header',
+ 'header_specificsettings',
+ get_string('specificsettings', 'customfield_checkbox')
+ );
+ $mform->setExpanded('header_specificsettings', true);
+
+ $mform->addElement(
+ 'selectyesno',
+ 'configdata[checkbydefault]',
+ get_string('checkedbydefault', 'customfield_checkbox')
+ );
+ $mform->setType('configdata[checkbydefault]', PARAM_BOOL);
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/field_controller.tsx b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/field_controller.tsx
new file mode 100644
index 0000000000..e4ace539f7
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/customfield/_files/field_controller.tsx
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { ComponentFileSummaryProps } from '../../../../_utils';
+
+export default (initialProps: ComponentFileSummaryProps): JSX.Element => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/customfield/index.md b/versioned_docs/version-5.1/apis/plugintypes/customfield/index.md
new file mode 100644
index 0000000000..da5467c7d9
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/customfield/index.md
@@ -0,0 +1,131 @@
+---
+title: Custom fields
+tags:
+ - customfield
+ - Course
+ - Custom field
+---
+
+Custom fields allow you to create field types to be used for custom fields. Instances of these field types can be added to the respective areas that implement [Custom fields API](../../core/customfields/index.md). Currently in Moodle core only courses implement this API, however custom fields are also used in addon plugins for other areas. For example, if you want to display radio buttons on the course edit page, then you can add an instance of a radio custom field plugin to the Course custom fields configuration.
+
+import {
+ Lang,
+} from '../../_files';
+import FieldController from './_files/field_controller';
+import DataController from './_files/data_controller';
+
+## File structure
+
+Custom field plugins are located in the `/customfield/field` directory. A plugin should not include any custom files outside of it's own plugin folder.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+:::important
+
+Some of the important files are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin.
+
+:::
+
+
+ View an example directory layout for the `customfield_checkbox` plugin.
+
+```console
+customfield/field/checkbox
+├── classes
+│ ├── data_controller.php
+│ ├── field_controller.php
+│ └── privacy
+│ └── provider.php
+├── lang
+│ └── en
+│ └── customfield_checkbox.php
+└── version.php
+```
+
+
+
+A custom field plugin requires two _controller_ classes:
+
+- a _field_ controller, which describes the field itself; and
+- a _data_ controller, which describes with interface within the context of the instance (i.e. course).
+
+### Field Controller
+
+The field controller defines the available configuration options that an administrator can select within the user interface to configure the field.
+
+Examples might include the prompt to show alongside the custom field element, and whether the element is required.
+
+:::note Class naming
+
+The class must be named `field_controller` within your plugin's namespace (for example `customfield_myfield`) and must extend the `\core_customfield\field_controller` class.
+
+:::
+
+
+
+
+import fieldExample from '!!raw-loader!./_files/field_controller.php';
+
+
+
+
+
+The `\core_customfield\field_controller` class is an abstract class and defines a number of functions which you can choose to override. At a minimum, the following two items are required:
+
+- the `TYPE` constant to match the name of the plugin; and
+- the `config_form_definition()` function.
+
+:::danger Element names
+
+All element names must be in the format `$configdata[configname]` for values to be saved, for example `configdata[cfgdefault]`.
+
+:::
+
+In addition to these requried a functions a number of other functions exist and can be overridden, with the following being particularly useful:
+
+- `config_form_validation($formdata, $formfiles)` - control the form validation
+
+Details of all available functions can be found in the `\core_customfield\field_controller` class defined in `/customfield/classes/field_controller.php`.
+
+### Data Controller
+
+The data controller defines the user interface that teachers use within the course edit form.
+
+:::note Class naming
+
+The class must be named `data_controller` within your plugin's namespace (for example `customfield_myfield`) and must extend the `\core_customfield\data_controller` class.
+
+:::
+
+
+
+
+import dataExample from '!!raw-loader!./_files/data_controller.php';
+
+
+
+
+
+The `\core_customfield\data_controller` class is an abstract class and defines a number of functions which you can choose to override. At a minimum, the following two items are required:
+
+- the `datafield(): string` function; and
+- the `instance_form_definition()` function.
+
+#### datafield()
+
+The `datafield()` function returns an enumerated string and describes which database field the data for the custom field is stored in. The possible options are:
+
+- `intvalue` - can store integer values, this field is indexed
+- `decvalue` - can store decimal values
+- `shortcharvalue` - can store character values up to 255 characters long, this field is indexed
+- `charvalue` - can store character values up to 1333 characters long, this field is not indexed
+- `value` - can store character values of unlimited length ("text" field in the db)
+
+#### instance_form_definition()
+
+The `instance_form_definition()` function adds any required field elements that are displayed on the instance editing page (i.e. on the course settings page).
+
+## See Also
+
+- [Custom files API](../../core/customfields/index.md)
+- [User Profile Fields](https://docs.moodle.org/dev/User_profile_fields)
diff --git a/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/access.php b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/access.php
new file mode 100644
index 0000000000..c7e1d3581e
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/access.php
@@ -0,0 +1,39 @@
+$capabilities = [
+
+ // Enrol anybody.
+ 'enrol/pluginname:enrol' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+
+ // Manage enrolments of users.
+ 'enrol/pluginname:manage' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+
+ // Unenrol anybody (including self) - watch out for data loss.
+ 'enrol/pluginname:unenrol' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+
+ // Unenrol self - watch out for data loss.
+ 'enrol/pluginname:unenrolself' => [
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [],
+ ],
+];
diff --git a/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/enrol_lang.php b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/enrol_lang.php
new file mode 100644
index 0000000000..b9e70e318a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/enrol_lang.php
@@ -0,0 +1,7 @@
+$string['fee:config'] = 'Configure enrolment on payment enrol instances';
+$string['fee:manage'] = 'Manage enrolled users';
+$string['fee:unenrol'] = 'Unenrol users from course';
+$string['fee:unenrolself'] = 'Unenrol self from course';
+$string['pluginname'] = 'Enrolment on payment';
+$string['pluginname_desc'] = 'The enrolment on payment enrolment method allows you to set up courses requiring a payment. If the fee for any course is set to zero, then students are not asked to pay for entry. There is a site-wide fee that you set here as a default for the whole site and then a course setting that you can set for each course individually. The course fee overrides the site fee.';
+$string['privacy:metadata'] = 'The enrolment on payment enrolment plugin does not store any personal data.';
diff --git a/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/lib.php b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/lib.php
new file mode 100644
index 0000000000..bb1b237d6b
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/lib.php
@@ -0,0 +1,5 @@
+class enrol_pluginname_plugin extends enrol_plugin {
+
+ // Enrolment plugins can define many workflows to handle enrolment
+ // depending on the overridden methods. See the methods section for more information.
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/message_lib.php b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/message_lib.php
new file mode 100644
index 0000000000..8855bcafde
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/message_lib.php
@@ -0,0 +1,48 @@
+class enrol_pluginname_plugin extends enrol_plugin {
+
+ // (...)
+
+ public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+ $mform->addElement(
+ 'select',
+ 'customint4',
+ get_string('sendcoursewelcomemessage', 'enrol_pluginname'),
+ enrol_send_welcome_email_options()
+ );
+ }
+
+ /**
+ * Enrol a user using a given enrolment instance.
+ *
+ * @param stdClass $instance the plugin instance
+ * @param int $userid the user id
+ * @param int $roleid the role id
+ * @param int $timestart enrolment start timestamp
+ * @param int $timeend enrolment end timestamp
+ * @param int $status default to ENROL_USER_ACTIVE for new enrolments
+ * @param bool $recovergrades restore grade history
+ */
+ public function enrol_user(
+ stdClass $instance,
+ $userid,
+ $roleid = null,
+ $timestart = 0,
+ $timeend = 0,
+ $status = null,
+ $recovergrades = null
+ ) {
+ parent::enrol_user(
+ $instance,
+ $userid,
+ $roleid,
+ $timestart,
+ $timeend,
+ $status,
+ $recovergrades
+ );
+ // Send welcome message.
+ if ($instance->customint4 != ENROL_DO_NOT_SEND_EMAIL) {
+ $this->email_welcome_message($instance, core_user::get_user($userid));
+ }
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/ui_lib.php b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/ui_lib.php
new file mode 100644
index 0000000000..d56a1a65f3
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/enrol/_examples/ui_lib.php
@@ -0,0 +1,100 @@
+record_exists('enrol', ['courseid' => $courseid, 'enrol' => 'pluginname'])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add elements to the edit instance form.
+ *
+ * @param stdClass $instance
+ * @param MoodleQuickForm $mform
+ * @param context $context
+ * @return bool
+ */
+ public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+ $options = [
+ 'example1' => get_string('example1', 'enrol_pluginname'),
+ 'example2' => get_string('example2', 'enrol_pluginname'),
+ ];
+ $mform->addElement(
+ 'select',
+ 'customchar1',
+ get_string('something', 'enrol_pluginname'),
+ $options
+ );
+ $mform->setDefault('customchar1', $this->get_config('something'));
+
+ $mform->addElement(
+ 'text',
+ 'customtext1',
+ get_string('extraname', 'enrol_pluginname')
+ );
+ }
+
+ /**
+ * Perform custom validation of the data used to edit the instance.
+ *
+ * @param array $data array of ("fieldname"=>value) of submitted data
+ * @param array $files array of uploaded files "element_name"=>tmp_file_path
+ * @param object $instance The instance loaded from the DB
+ * @param context $context The context of the instance we are editing
+ * @return array of "element_name"=>"error_description" if there are errors,
+ * or an empty array if everything is OK.
+ */
+ public function edit_instance_validation($data, $files, $instance, $context) {
+ $errors = [];
+
+ // Do some validation.
+ if ($data['customchar1'] != 'example2' && empty($data['customtext1'])) {
+ $errors['customtext1'] = get_string('missing_extraname', 'enrol_pluginname');
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Add new instance of enrol plugin.
+ * @param object $course the course object
+ * @param array $fields instance fields
+ * @return int id of new instance, null if can not be created
+ */
+ public function add_instance($course, array $fields = null) {
+ // Add $fields calculations here.
+ $instanceid = parent::add_instance($course, $fields);
+ // Insert elements to the enrolment plugins tables if needed.
+ return $instanceid;
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/enrol/index.md b/versioned_docs/version-5.1/apis/plugintypes/enrol/index.md
new file mode 100644
index 0000000000..d2e96fcfca
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/enrol/index.md
@@ -0,0 +1,467 @@
+---
+title: Enrolment plugins
+tags:
+ - Enrolment
+ - Plugins
+---
+
+
+
+
+
+import {
+ Lang,
+ Lib,
+ VersionPHP,
+ DbAccessPHP,
+} from '../../_files';
+
+Moodle provides a number of ways of managing course enrolment, called enrolment plugins. Each course can decide its enabled enrolment plugins instances and any enrolment plugin can define a workflow the user must follow in order to enrol in the course.
+
+Course enrolment information is stored in tables **enrol**, **user_enrolments** and optionally other custom database tables defined by individual enrolment plugins. By default user enrolments are protected and can not be modified manually by teachers but only via the specific enrolment plugin.
+
+Enrolment gives users following privileges:
+
+- User with active enrolment may enter course, other users need either temporary guest access right or moodle/course:view capability.
+- "My courses" shows list of active enrolments for current user.
+- Course participation - some activities restrict participation to enrolled users only. The behaviour is defined independently by each activity, for example only enrolled users with submit capability may submit assignments, the capability alone is not enough.
+- Only enrolled users may be members of groups.
+- Gradebook tracks grades of all enrolled users, visibility of grades is controlled by role membership.
+
+:::caution
+
+Enrolments and role assignments are separate concepts, you may be enrolled and not have any role and you may have a role in course and not be enrolled. Roles at course context level and below may be controlled by enrolment plugins.
+
+:::
+
+## File structure
+
+All enrolment plugin files must be located inside the **enrol/pluginname** folder.
+
+
+ View an example directory layout for the `enrol_pluginname` plugin.
+
+```console
+ enrol/pluginname/
+ |-- db
+ | `-- access.php
+ |-- lang
+ | `-- en
+ | `-- enrol_pluginname.php
+ `-- lib.php
+ `-- version.php
+```
+
+
+
+Some of the important files for the format plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### lib.php
+
+import LibExample from '!!raw-loader!./_examples/lib.php';
+
+
+The plugin lib.php must contain the plugin base class.
+
+
+Enrolment plugins must extend `enrol_plugin` base class which is defined at the end of lib/enrollib.php. This base class contains all standard methods to define the plugin workflow.
+
+### lang/en/enrol_pluginname.php
+
+import langExample from '!!raw-loader!./_examples/enrol_lang.php';
+
+
+
+### db/access.php
+
+import RepositoryAccessExample from '!!raw-loader!./_examples/access.php';
+
+
+
+Depending on the enrolment workflow, the access.php file should define the following capabilities:
+
+- **enrol/xxx:enrol** - used when `enrol_plugin::allow_enrol()` returns true.
+- **enrol/xxx:unenrol** - used when `enrol_plugin::allow_unenrol()` or `enrol_plugin::allow_unenrol_user()` returns true.
+- **enrol/xxx:manage** - used when `enrol_plugin::allow_manage()` returns true.
+- **enrol/xxx:unenrolself** - used when plugin support self-unenrolment.
+- **enrol/xxx:config** - used when plugin allows user to modify instance properties. Automatic synchronisation plugins do not usually need this capability.
+
+See [enrolment API methods](#enrolment-api-methods) for more information.
+
+### version.php
+
+
+
+## User enrolment process
+
+Manual enrolment plugins are the simplest way to handle user enrolments. In the core *enrol_manual*, users with necessary permissions may enrol or unenrol users manually. In the *enrol_flatfile* plugin allows automation of enrolment and unenrolment actions.
+
+Fully automatic plugins are configured at the system level, they synchronise user enrolments with information stored in external systems (for example *enrol_ldap*, *enrol_database* and *enrol_category*). Some non-interactive plugins may require configuration of enrolment instances (for example *enrol_cohort*).
+
+Interactive enrolment plugins require user interaction during enrolment (for example: *enrol_self* and *enrol_fee*). These plugins need to override `enrol_plugin::show_enrolme_link()`, `enrol_plugin::enrol_page_hook()` and to implement adding and editing of enrol instance.
+
+## Enrolment expiration and suspending
+
+User has active enrolment if all following conditions are met:
+
+- User has record in `user_enrolments` table,
+- User enrolment already started,
+- User enrolment is not past timeend,
+- User enrolment has active status,
+- Enrol instance has active status in `enrol` table,
+- Enrol plugin is enabled.
+
+Most synchronisation plugins include a setting called *External unenrol action*. It is used to decide what happens when previously enrolled user is not supposed to be enrolled any more. Enrol plugins can provide schedulled tasks to synchronize enrolments.
+
+Plugins that set `timeend` in `user_enrolments` table may want to specify expiration action and optional expiration notification using `enrol_plugin::process_expirations()` and `enrol_plugin::send_expiry_notifications()` methods.
+
+## Enrolment API methods.
+
+Each enrolment plugin can define the enrolment workflow by overriding some of the `enrol_plugin` methods.
+
+### enrol_plugin::get_user_enrolment_actions(): array
+
+By default, all enrolment plugins will have *editing enrolment* and *user unenrolment* actions. However, some plugins may override this method to add extra actions.
+
+
+ View example
+
+
+```php
+/**
+ * Gets an array of the user enrolment actions
+ *
+ * @param course_enrolment_manager $manager
+ * @param stdClass $userenrolment
+ * @return array An array of user_enrolment_actions
+ */
+public function get_user_enrolment_actions(course_enrolment_manager $manager, $userenrolment) {
+ $actions = parent::get_user_enrolment_actions($manager, $userenrolment);
+ $context = $manager->get_context();
+ $instance = $userenrolment->enrolmentinstance;
+ $params = $manager->get_moodlepage()->url->params();
+ $params['ue'] = $userenrolment->id;
+
+ // Edit enrolment action.
+ if ($this->allow_manage($instance) && has_capability("enrol/{$instance->enrol}:something", $context)) {
+ $title = get_string('newaction', 'enrol');
+ $icon = new pix_icon('t/edit', '');
+ $url = new moodle_url('/enrol/pluginname/something.php', $params);
+ $actions[] = new user_enrolment_action($icon, $title, $url);
+ }
+
+ return $actions;
+}
+```
+
+
+
+
+### enrol_plugin::allow_unenrol(): bool
+
+This method returns true if other code allowed to unenrol everybody from one instance. This method is used on course reset and manual unenrol.
+
+:::note
+
+The unenrol action will allow resetif all following conditions are met:
+
+- The method `enrol_plugin::allow_unenrol()` returns true
+- The current user has the `enrol/pluginname:unenrol` capability.
+
+:::
+
+
+ View example
+
+
+```php
+public function allow_unenrol(stdClass $instance) {
+ // Add any extra validation here.
+ return true;
+}
+```
+
+
+
+
+### enrol_plugin::allow_unenrol_user(): bool
+
+This method returns true if other code allowed to unenrol a specific user from one instance.
+
+:::tip
+
+If `allow_unenrol_user` is not overridden, the default behaviour is to call `allow_unenrol()` method.
+
+:::
+
+:::note
+
+The unenrol action will be displayed if all following conditions are met:
+
+- The method `enrol_plugin::allow_unenrol_user()` returns true
+- The current user has the `enrol/pluginname:unenrol` capability.
+
+:::
+
+
+ View example
+
+
+```php
+public function allow_unenrol_user(stdClass $instance, stdClass $userenrolment) {
+ // Add any extra validation here.
+ return true;
+}
+
+```
+
+
+
+
+It is quite common in enrolment plugins to allow unenrol only if the user enrolment is suspended (for example: *enrol_database*, *enrol_flatfile*, *enrol_meta*).
+
+
+ View suspended enrolment example
+
+
+
+### enrol_plugin::allow_enrol(): bool
+
+Define if the enrol plugin is compatible with manual enrolments.
+
+:::note
+
+The edit manual enrolment action will be displayed if if all following conditions are met:
+
+- The method `enrol_plugin::allow_enrol()` returns true
+- The current user has the `enrol/pluginname:enrol` capability.
+
+:::
+
+
+ View example
+
+
+```php
+public function allow_enrol(stdClass $instance) {
+ // Add any extra validation here.
+ return true;
+}
+```
+
+
+
+
+### enrol_plugin::enrol_user()
+
+This method is the plugin enrolment hook. It will be called when user is enrolled in the course using one of the plugin instances. It is used to alter the enrolment data (for example altering the dates or the role) and also to throw exceptions if some external condions are not met.
+
+
+ View example
+
+
+```php
+/**
+ * Enrol a user using a given enrolment instance.
+ *
+ * @param stdClass $instance the plugin instance
+ * @param int $userid the user id
+ * @param int $roleid the role id
+ * @param int $timestart enrolment start timestamp
+ * @param int $timeend enrolment end timestamp
+ * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
+ * @param bool $recovergrades restore grade history
+ */
+public function enrol_user(
+ stdClass $instance,
+ $userid,
+ $roleid = null,
+ $timestart = 0,
+ $timeend = 0,
+ $status = null,
+ $recovergrades = null
+) {
+ // Add validations here.
+
+ parent::enrol_user(
+ $instance,
+ $userid,
+ $roleid,
+ $timestart,
+ $timeend,
+ $status,
+ $recovergrades
+ );
+}
+```
+
+
+
+
+### enrol_plugin:allow_manage(): bool
+
+Return true if plugin allows manual modification of user enrolments from other code. False is usually returned from plugins that synchronise data with external systems, otherwise the manual changes would be reverted immediately upon synchronisation.
+
+:::note
+
+The edit enrolment action in the participants list will be displayed if if all following conditions are met:
+
+- The method `allow_manage` returns true
+- The current user has the `enrol/pluginname:manage` capability.
+
+:::
+
+
+ View example
+
+
+```php
+public function allow_manage(stdClass $instance) {
+ // Add any extra validation here.
+ return true;
+}
+```
+
+
+
+
+### enrol_plugin::roles_protected(): bool
+
+Enrolment plugins can protect roles from being modified by any other plugin. Returning false will allow users to remove all roles assigned by this plugin. By default, this method returns true.
+
+:::
+
+
+ View example
+
+
+```php
+public function roles_protected() {
+ // Add any extra validation here if necessary.
+ return false;
+}
+```
+
+
+
+
+### enrol_plugin:find_instance(): stdClass {#enrol_pluginfind_instance-stdclass}
+
+Returns enrolment instance in a given course matching provided data. If enrol plugin implements this method, then it is supported
+ in CSV course upload.
+
+:::note
+
+So far following enrol plugins implement this method: *enrol_manual*, *enrol_self*, *enrol_guest*, *enrol_cohort*.
+ Method must be capable to uniquely identify instance in a course using provided data in order to implement this method. For example, *enrol_cohort* uses
+ `cohortID` together with `roleID` to identify instance. For some methods (like *enrol_lti* or *enrol_payment*) it is not possible
+ to uniquely identify instance in a course using provided data, so such methods will not be supported in CSV course upload.
+
+The only exception is *enrol_self* - although it is not possible to uniquely identify enrolment instance in a course using provided data, it is still supported in CSV course upload. For *enrol_self* method `find_instance()` returns the first available enrolment instance in a course.
+
+:::
+
+
+ View example
+
+
+
+## Standard Editing UI
+
+Moodle participants page has a standard editing UI for manual enrolments. To integrate a plugin into the start UI you need to implement the following methods:
+
+- `enrol_plugin::use_standard_editing_ui()`
+- `enrol_plugin::edit_instance_form()`
+- `enrol_plugin::edit_instance_validation()`
+- `enrol_plugin::can_add_instance()`
+- `enrol_plugin::add_instance()`
+
+This means that the following functions from the plugin will be called to build the add/edit form, perform validation of the data and add standard navigation links to the manage enrolments page and course navigation.
+
+
+ View example
+
+
+import UiLib from '!!raw-loader!./_examples/ui_lib.php';
+
+{UiLib}
+
+
+
+
+## Sending a welcome email
+
+Some enrol methods has the support for sending welcome mesages to users. To grant the enrol messages are consistent acorrs enrolments methods, the enrol API provides the `enrol_send_welcome_email_options` function. This method returns a list of all possible options for sending welcome email when the user enrol in a course and each option has a respective constant defined on **enrollib.php**:
+
+```php
+define('ENROL_DO_NOT_SEND_EMAIL', 0); // Do not send the welcome email.
+define('ENROL_SEND_EMAIL_FROM_COURSE_CONTACT', 1); // Send welcome email from course contact.
+define('ENROL_SEND_EMAIL_FROM_KEY_HOLDER', 2); // Send welcome email from course key holder.
+define('ENROL_SEND_EMAIL_FROM_NOREPLY', 3); // Send welcome email from no reply.
+```
+
+
+ View example
+
+
+import MessageLib from '!!raw-loader!./_examples/message_lib.php';
+
+{MessageLib}
+
+
+
+
+## See also
+
+- [Enrolment API](../../subsystems/enrol.md)
diff --git a/versioned_docs/version-5.1/apis/plugintypes/fileconverter/_files/converter.md b/versioned_docs/version-5.1/apis/plugintypes/fileconverter/_files/converter.md
new file mode 100644
index 0000000000..da4cf2ea22
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/fileconverter/_files/converter.md
@@ -0,0 +1,6 @@
+
+
+
+The `classes/converter.php` class must be defined in the correct namespace for your plugin, and must implement the `\core_files\converter_interface` interface.
+
+It is responsible for converting files
diff --git a/versioned_docs/version-5.1/apis/plugintypes/fileconverter/_files/converter.tsx b/versioned_docs/version-5.1/apis/plugintypes/fileconverter/_files/converter.tsx
new file mode 100644
index 0000000000..a1a3702eac
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/fileconverter/_files/converter.tsx
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { Props } from '../../../../_utils';
+import DefaultDescription from './converter.md';
+
+const defaultExample = `
+namespace fileconverter_myexample;
+
+class converter implements \core_files\converter_interface {
+ // ...
+}
+`;
+
+export default (initialProps: Props): typeof ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/fileconverter/index.md b/versioned_docs/version-5.1/apis/plugintypes/fileconverter/index.md
new file mode 100644
index 0000000000..c6fb303638
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/fileconverter/index.md
@@ -0,0 +1,100 @@
+---
+title: File Converters
+tags:
+ - File
+ - core_file
+ - file_converter
+ - API
+ - PDF
+ - Conversion
+ - Document
+---
+
+File converters are an important tool to support other plugins with file conversion supported between a wide range of file formats. File converters are accessed using the [File conversion API](../../subsystems/files/converter.md) and are typically consumed by other plugins rather than by the user directly.
+
+## File structure
+
+File converter plugins are located in the `/files/converter` directory.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+
+ View an example directory layout for the `fileconverter_unoconv` plugin.
+
+```console
+files/converter/unoconv
+├── classes
+│ ├── converter.php
+│ └── privacy
+│ └── provider.php
+├── lang
+│ └── en
+│ └── fileconverter_unoconv.php
+├── settings.php
+└── version.php
+```
+
+
+
+Some of the important files for the fileconverter plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### Converter class
+
+import Converter from './_files/converter';
+
+
+
+#### are_requirements_met()
+
+This function informs the File Converter API whether the system requirements of the plugin are met. That is whether appropriate API keys are present, and the API might be available.
+
+It should be lightweight to call and cache where required.
+
+```php title="Example implementation"
+public static function are_requirements_met() {
+ return extension_loaded('my_php_extension');
+}
+```
+
+#### start_document_conversion() and poll_conversion_status()
+
+The `start_document_conversion()` function starts a conversion, whilst `poll_conversion_status` should poll for any status update. The following apply:
+
+- If any failures occur, it should set the conversion status to `\core_files\conversion::STATUS_FAILED` and immediately return. There is no need to update the `$conversion` record in this situation.
+- When the conversion process starts, the status should be set to `\core_files\conversion::STATUS_IN_PROGRESS` and the record **must** be updated. This ensures that, should the process take a long time, the current status is accurately reflected.
+- Upon successful completion, the status should be updated to `\core_files\conversion::STATUS_COMPLETE` and the newly created `\stored_file` should be stored against the conversion using either the `store_destfile_from_string` or `store_destfile_from_path` function as appropriate.
+
+#### supports()
+
+This function allows the plugin to answer whether it supports conversion between two formats. It is typically only used internally by the File Conversion subsystem.
+
+```php title="Example implementation"
+class converter implements \core_files\converter_interface {
+ // ...
+ public static function supports($from, $to) {
+ // This plugin supports conversion from doc and docx to pdf only.
+ if ($from !== 'doc' && $from !== 'docx') {
+ return false;
+ }
+
+ return $to === 'pdf';
+ }
+}
+```
+
+```php title="Example usage"
+if (\fileconverter_example::supports('jpg', 'pdf')) {
+ // ...
+}
+```
+
+#### get_supported_conversion()
+
+This function is used purely for information purposes to display possible conversions to an administrator.
+
+## See also
+
+- Using the [File Converter API](../../subsystems/files/converter.md)
diff --git a/versioned_docs/version-5.1/apis/plugintypes/filter/_examples/dynamic.js b/versioned_docs/version-5.1/apis/plugintypes/filter/_examples/dynamic.js
new file mode 100644
index 0000000000..8b93652604
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/filter/_examples/dynamic.js
@@ -0,0 +1,31 @@
+import {eventTypes} from 'core_filters/events';
+
+/** @var {bool} Whether this is the first load of videojs module */
+let firstLoad;
+
+/**
+ * Initialise the dynamic content filter.
+ *
+ * @method
+ * @listens event:filterContentUpdated
+ */
+export const init = () => {
+ if (!firstLoad) {
+ return;
+ }
+ firstLoad = true;
+ // Add the event listener.
+ document.addEventListener(eventTypes.filterContentUpdated, contentUpdatedHandler);
+};
+
+/**
+ * Notify video.js of new nodes.
+ *
+ * @param {Event} event The event.
+ */
+const contentUpdatedHandler = (event) => {
+ const updatedContent = event.detail.nodes;
+ updatedContent.forEach(content => {
+ // Alter any updated content.
+ });
+};
diff --git a/versioned_docs/version-5.1/apis/plugintypes/filter/_examples/filter.php b/versioned_docs/version-5.1/apis/plugintypes/filter/_examples/filter.php
new file mode 100644
index 0000000000..39c3961eb7
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/filter/_examples/filter.php
@@ -0,0 +1,8 @@
+namespace filter_pluginname;
+
+class text_filter extends \core_filters\text_filter {
+ function filter(string $text, array $options = []) {
+ // Return the modified text.
+ return $text;
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/filter/index.md b/versioned_docs/version-5.1/apis/plugintypes/filter/index.md
new file mode 100644
index 0000000000..a57563938b
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/filter/index.md
@@ -0,0 +1,201 @@
+---
+title: Filter plugins
+tags:
+ - Filter
+ - Plugins
+---
+
+
+
+
+
+
+import {
+ DbAccessPHP,
+ Lang,
+ Lib,
+ VersionPHP,
+} from '../../_files';
+
+import {
+ ComponentFileSummary,
+} from '../../../_utils';
+
+Filters are a way to automatically transform content before it is output. Filters may be used to:
+
+- Render embedded equations to images (the TeX filter).
+- Automatically convert links to media files to embedded players.
+- Automatically convert mentions of glossary terms to links.
+
+Filters are one of the easiest types of plugin to create.
+
+Filters are applied to content passed into the `format_string()` and `format_text()` functions, which are part of the [Output API](../subsystems/output).
+
+## File structure
+
+Filter plugins are located in the `/filter` directory.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+
+ View an example directory layout for the `filter_pluginname` plugin.
+
+```console
+ filter/pluginname/
+ |-- lang
+ | `-- en
+ | `-- filter_pluginname.php
+ |-- classes
+ | `-- text_filter.php
+ `-- version.php
+```
+
+
+
+Some of the important files for the filter plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### classes/text_filter.php
+
+import Filter from '!!raw-loader!./_examples/filter.php';
+
+
+
+### version.php
+
+
+
+### lang/en/filter_pluginname.php
+
+
+export const langExample = `
+ $string['filtername'] = 'Activity names auto-linking';
+`;
+
+
+
+## Test a filter
+
+To enable a filter, go to the [filters administration screen](./index.md) and set the filter active to "On".
+
+Filters are applied to all text that is printed with the output functions `format_text()` and `format_string()`. To see a filter in action, add some content to a label resource. When you look at that course in the course listing, you should see that your filter has transformed the text accordingly.
+
+## Filter performance
+
+It is important to note that all active filters will be called to transform every bit of text output using `format_text()` (headers and content), and `format_string()` (headers only). As a result a filter plugin can cause big performance problems. It is extremely important to use cache if your filter must retrieve data from the database, or other similar sources.
+
+If a filter uses a special syntax or it is based on an appearance of a substring in the text, it is recommend to perform a quick and cheap `strpos()` search first prior to executing the full regex-based search and replace.
+
+
+ View example
+
+
+
+## Local configuration
+
+Filters can use different configuration depending on the context in which they are called. For example, the glossary filter can be configured such that when displayed in Forum A it only links words from a particular glossary, while in Forum B it links words from a different glossary..
+
+To support this behaviour, a filter plugin must provide a `filterlocalsettings.php` file which defines a Moodle form which subclasses the `filter_local_settings_form` class. In addition to the standard formslib methods, you must also define a `save_changes` method.
+
+
+ View example
+
+
+
+## Filtering dynamic content
+
+It is possible that page content is loaded by ajax after the page is loaded. In certain filter types (for example MathJax) JavaScript is required to be run on the output of the filter in order to do the final markup. For these types of filters, a JavaScript event is triggered when new content is added to the page (the content will have already been processed by the filter in php). The JavaScript for a filter can listen for these event notifications and reprocess the affected dom nodes.
+
+The content updated event is registered in the `core_filters/events` module and can be imported as:
+
+```js
+import {eventTypes} from 'core_filters/events';
+
+document.addEventListener(eventTypes.filterContentUpdated, eventHandler);
+```
+
+
+ View example
+
+
+import DynamicContent from '!!raw-loader!./_examples/dynamic.js';
+
+{DynamicContent}
+
+
+
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/format.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/format.php
new file mode 100644
index 0000000000..3683a8ca54
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/format.php
@@ -0,0 +1,27 @@
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/filelib.php');
+require_once($CFG->libdir . '/completionlib.php');
+
+// Retrieve course format option fields and add them to the $course object.
+$format = core_courseformat\base::instance($course);
+$course = $format->get_course();
+$context = context_course::instance($course->id);
+
+// Add any extra logic here.
+
+// Make sure section 0 is created.
+course_create_sections_if_missing($course, 0);
+
+$renderer = $format->get_renderer($PAGE);
+
+// Setup the format base instance.
+if (!empty($displaysection)) {
+$format->set_section_number($displaysection);
+}
+// Output course content.
+$outputclass = $format->get_output_classname('content');
+$widget = new $outputclass($format);
+echo $renderer->render($widget);
+
+// Include any format js module here using $PAGE->requires->js.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/format_lang.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/format_lang.php
new file mode 100644
index 0000000000..e5183221b2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/format_lang.php
@@ -0,0 +1,12 @@
+
+$string['addsections'] = 'Add section';
+$string['currentsection'] = 'This section';
+$string['deletesection'] = 'Delete section';
+$string['editsection'] = 'Edit section';
+$string['editsectionname'] = 'Edit section name';
+$string['hidefromothers'] = 'Hide section';
+$string['newsectionname'] = 'New name for section {$a}';
+$string['pluginname'] = 'pluginname format';
+$string['privacy:metadata'] = 'The Sample format plugin does not store any personal data.';
+$string['sectionname'] = 'Section';
+$string['showfromothers'] = 'Show section';
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib.php
new file mode 100644
index 0000000000..3c2e9152ea
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib.php
@@ -0,0 +1,104 @@
+
+class format_pluginname extends core_courseformat\base {
+
+ /**
+ * Returns true if this course format uses sections.
+ *
+ * @return bool
+ */
+ public function uses_sections() {
+ return true;
+ }
+
+ public function uses_indentation(): bool {
+ return false;
+ }
+
+ public function uses_course_index() {
+ return true;
+ }
+
+ /**
+ * Returns the information about the ajax support in the given source format.
+ *
+ * The returned object's property (boolean)capable indicates that
+ * the course format supports Moodle course ajax features.
+ *
+ * @return stdClass
+ */
+ public function supports_ajax() {
+ $ajaxsupport = new stdClass();
+ $ajaxsupport->capable = true;
+ return $ajaxsupport;
+ }
+
+ public function supports_components() {
+ return true;
+ }
+
+ /**
+ * Whether this format allows to delete sections.
+ *
+ * Do not call this function directly, instead use {@link course_can_delete_section()}
+ *
+ * @param int|stdClass|section_info $section
+ * @return bool
+ */
+ public function can_delete_section($section) {
+ return true;
+ }
+
+ /**
+ * Indicates whether the course format supports the creation of a news forum.
+ *
+ * @return bool
+ */
+ public function supports_news() {
+ return true;
+ }
+
+ /**
+ * Returns the display name of the given section that the course prefers.
+ *
+ * This method is required for inplace section name editor.
+ *
+ * @param int|stdClass $section Section object from database or just field section.section
+ * @return string Display name that the course format prefers, e.g. "Topic 2"
+ */
+ public function get_section_name($section) {
+ $section = $this->get_section($section);
+ if ((string)$section->name !== '') {
+ return format_string(
+ $section->name,
+ true,
+ ['context' => context_course::instance($this->courseid)]
+ );
+ } else {
+ return $this->get_default_section_name($section);
+ }
+ }
+}
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place.
+ *
+ * This method is required for inplace section name editor.
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return inplace_editable
+ */
+function format_pluginname_inplace_editable($itemtype, $itemid, $newvalue) {
+ global $DB, $CFG;
+ require_once($CFG->dirroot . '/course/lib.php');
+ if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {
+ $section = $DB->get_record_sql(
+ 'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?',
+ [$itemid, 'pluginname'],
+ MUST_EXIST
+ );
+ $format = core_courseformat\base::instance($section->course);
+ return $format->inplace_editable_update_section_name($section, $itemtype, $newvalue);
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib_components.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib_components.php
new file mode 100644
index 0000000000..32d9a1a0ce
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib_components.php
@@ -0,0 +1,8 @@
+class format_PLUGINNAME extends core_courseformat\base {
+
+ // More methods can be added here.
+
+ public function supports_components() {
+ return true;
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib_course_index.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib_course_index.php
new file mode 100644
index 0000000000..dbb6109248
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/lib_course_index.php
@@ -0,0 +1,8 @@
+class format_PLUGINNAME extends core_courseformat\base {
+
+ // More methods can be added here.
+
+ public function uses_course_index() {
+ return true;
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/cmitem.mustache b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/cmitem.mustache
new file mode 100644
index 0000000000..7b0d80da79
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/cmitem.mustache
@@ -0,0 +1,8 @@
+{{!
+ Include the core content/section/cmitem template.
+}}
+{{< core_courseformat/local/content/section/cmitem }}
+ {{!
+ Add any cm item blocks override here.
+ }}
+{{/ core_courseformat/local/content/section/cmitem }}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/cmitem.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/cmitem.php
new file mode 100644
index 0000000000..670db67e0c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/cmitem.php
@@ -0,0 +1,16 @@
+
+namespace format_pluginname\output\courseformat\content\section;
+
+use core_courseformat\output\local\content\section\cmitem as cmitem_base;
+
+class cmitem extends cmitem_base {
+
+ /**
+ * Returns the output class template path.
+ *
+ * This method redirects the default template when the section activity item is rendered.
+ */
+ public function get_template_name(\renderer_base $renderer): string {
+ return 'format_pluginname/local/content/section/cmitem';
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/content.mustache b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/content.mustache
new file mode 100644
index 0000000000..377189df1c
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/content.mustache
@@ -0,0 +1,14 @@
+{{!
+ Include the core content template.
+ This is the top template for a course.
+}}
+{{< core_courseformat/local/content }}
+ {{!
+ Add any general structure blocks override here.
+ }}
+
+ {{! Mandatory the content/section block. }}
+ {{$ core_courseformat/local/content/section }}
+ {{> format_pluginname/local/content/section }}
+ {{/ core_courseformat/local/content/section }}
+{{/ core_courseformat/local/content }}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/content.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/content.php
new file mode 100644
index 0000000000..6432366ea0
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/content.php
@@ -0,0 +1,16 @@
+
+namespace format_pluginname\output\courseformat;
+
+use core_courseformat\output\local\content as content_base;
+
+class content extends content_base {
+
+ /**
+ * Returns the output class template path.
+ *
+ * This method redirects the default template when the course content is rendered.
+ */
+ public function get_template_name(\renderer_base $renderer): string {
+ return 'format_pluginname/local/content';
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/renderer.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/renderer.php
new file mode 100644
index 0000000000..e9821f2314
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/renderer.php
@@ -0,0 +1,9 @@
+
+namespace format_pluginname\output;
+
+use core_courseformat\output\section_renderer;
+use moodle_page;
+
+class renderer extends section_renderer {
+ // Your renderer methods goes here.
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/section.mustache b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/section.mustache
new file mode 100644
index 0000000000..3225b76066
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/section.mustache
@@ -0,0 +1,12 @@
+{{!
+ Include the core content/section template.
+}}
+{{< core_courseformat/local/content/section }}
+ {{!
+ Add any section blocks override here.
+ }}
+ {{! Mandatory cmitem override }}
+ {{$ core_courseformat/local/content/section/cmitem }}
+ {{> format_pluginname/local/content/section/cmitem }}
+ {{/ core_courseformat/local/content/section/cmitem }}
+{{/ core_courseformat/local/content/section }}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/section.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/section.php
new file mode 100644
index 0000000000..d1d8e4c64f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/output/section.php
@@ -0,0 +1,16 @@
+
+namespace format_pluginname\output\courseformat\content;
+
+use core_courseformat\output\local\content\section as section_base;
+
+class section extends section_base {
+
+ /**
+ * Returns the output class template path.
+ *
+ * This method redirects the default template when the course section is rendered.
+ */
+ public function get_template_name(\renderer_base $renderer): string {
+ return 'format_pluginname/local/content/section';
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/pluginskel_recipe.yaml b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/pluginskel_recipe.yaml
new file mode 100644
index 0000000000..a57d181216
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/pluginskel_recipe.yaml
@@ -0,0 +1,84 @@
+## This is an example recipe file that you can use as a template for your own plugins.
+## See the list of all files it would generate:
+##
+## php generate.php example.yaml --list-files
+##
+## View a particular file contents without actually writing it to the disk:
+##
+## php generate.php example.yaml --file=version.php
+##
+## To see the full list of options, run:
+##
+## php generate.php --help
+##
+---
+## Frankenstyle component name.
+component: format_pluginname
+
+## Human readable name of the plugin.
+name: Example pluginname format
+
+## Human readable release number.
+release: "0.1.0"
+
+## Plugin version number, e.g. 2016062100. Will be set to current date if left empty.
+#version: 2016121200
+
+## Required Moodle version, e.g. 2015051100 or "2.9".
+requires: "4.0"
+
+## Plugin maturity level. Possible options are MATURIY_ALPHA, MATURITY_BETA,
+## MATURITY_RC or MATURIY_STABLE.
+maturity: MATURITY_BETA
+
+## Copyright holder(s) of the generated files and classes.
+copyright: YOURNAME
+
+## Features flags can control generation of optional files/code fragments.
+features:
+ readme: true
+ license: true
+
+ ## Privacy API implementation
+privacy:
+ haspersonaldata: false
+ uselegacypolyfill: false
+
+format_features:
+ # Create the Moodle 4.0+ basic template structure.
+ basic_outputs: true
+
+ # General format features.
+ uses_sections: true
+ uses_course_index: true
+ uses_indentation: false
+ uses_inplace_editor: true
+ uses_reactive_components: true
+ uses_news: true
+
+## Explicitly added strings
+lang_strings:
+ - id: mycustomstring
+ text: You can add 'extra' strings via the recipe file.
+ - id: mycustomstring2
+ text: Another string with {$a->some} placeholder.
+
+ ## Needed for course format plugins.
+ - id: addsections
+ text: Add section
+ - id: currentsection
+ text: This section
+ - id: editsection
+ text: Edit section
+ - id: editsectionname
+ text: Edit section name
+ - id: deletesection
+ text: Delete section
+ - id: newsectionname
+ text: New name for section {$a}
+ - id: sectionname
+ text: Section
+ - id: hidefromothers
+ text: Hide section
+ - id: showfromothers
+ text: Show section
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_examples/renderer.php b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/renderer.php
new file mode 100644
index 0000000000..a29a9cad99
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/_examples/renderer.php
@@ -0,0 +1,41 @@
+namespace format_pluginname\output;
+
+use core_courseformat\base as format_base;
+use core_courseformat\output\section_renderer;
+use moodle_page;
+
+/**
+* Basic renderer for pluginname format.
+*
+* @copyright 2022 Someone
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+ class renderer extends section_renderer {
+ // Override any necessary renderer method here.
+
+ /**
+ * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page.
+ *
+ * This method is required to enable the inplace section title editor.
+ *
+ * @param section_info|stdClass $section The course_section entry from DB
+ * @param stdClass $course The course entry from DB
+ * @return string HTML to output.
+ */
+ public function section_title($section, $course) {
+ return $this->render(format_base::instance($course)->inplace_editable_render_section_name($section));
+ }
+
+ /**
+ * Generate the section title to be displayed on the section page, without a link.
+ *
+ * This method is required to enable the inplace section title editor.
+ *
+ * @param section_info|stdClass $section The course_section entry from DB
+ * @param int|stdClass $course The course entry from DB
+ * @return string HTML to output.
+ */
+ public function section_title_without_link($section, $course) {
+ return $this->render(format_base::instance($course)->inplace_editable_render_section_name($section, false));
+ }
+ }
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_files/course_editor_workflow.png b/versioned_docs/version-5.1/apis/plugintypes/format/_files/course_editor_workflow.png
new file mode 100644
index 0000000000..c4013b05fd
Binary files /dev/null and b/versioned_docs/version-5.1/apis/plugintypes/format/_files/course_editor_workflow.png differ
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/_files/course_format_output.png b/versioned_docs/version-5.1/apis/plugintypes/format/_files/course_format_output.png
new file mode 100644
index 0000000000..794c0dd84c
Binary files /dev/null and b/versioned_docs/version-5.1/apis/plugintypes/format/_files/course_format_output.png differ
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/index.md b/versioned_docs/version-5.1/apis/plugintypes/format/index.md
new file mode 100644
index 0000000000..17bd62b136
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/index.md
@@ -0,0 +1,815 @@
+---
+title: Course format
+tags:
+ - Plugins
+ - Format
+---
+
+
+
+
+
+
+import { getExample } from '@site/src/moodleBridge';
+import { ComponentFileSummary } from '../../../_utils';
+import {
+ Lang,
+ Lib,
+ VersionPHP,
+} from '../../_files';
+
+Course formats are plugins that determine the layout of course resources.
+
+Course formats determine how the course main page looks like (/course/view.php) in both view and editing mode. They are also responsible for building a navigation tree inside the course (displayed to users in the navigation block, course index, and breadcrumb). They can organize the course content in sections. The course creator or teacher can specify the course format for the course in the course edit form.
+
+Course formats also can add their own options fields to the course edit form. They can add course-dependent content to the header/footer of any page inside the course, not only /course/view.php
+
+## File structure
+
+All course format files must be located inside the **course/format/pluginname** folder.
+
+
+ View an example directory layout for the `format_pluginname` plugin.
+
+```console
+ course/format/pluginname/
+ |-- classes
+ | `-- output
+ | `-- courseformat
+ | `-- (Overridden outputs)
+ | `-- renderer.php
+ |-- db
+ | `-- access.php
+ |-- lang
+ | `-- en
+ | `-- format_pluginname.php
+ |-- format.php
+ |-- lib.php
+ `-- version.php
+```
+
+
+
+Some of the important files for the format plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+### format.php
+
+import Format from '!!raw-loader!./_examples/format.php';
+
+
+
+As it can be seen in the example, once the format base instance is created (the return object of `core_courseformat\base::instance` function), the plugin must define any necessary setting (in the example is just set_section_number but any plugin can define its own extra methods). Once this is done, the get_output_classname will return the correct class name for the content output and the format renderer will be responsible for rendering the full course. See [Rendering a course](#rendering-a-course) for more information.
+
+### lib.php
+
+import LibExample from '!!raw-loader!./_examples/lib.php';
+
+
+The main library of the format. It should contain a class `format_pluginname` extending `core_courseformat\base`, this class is known as the format base class. Also, it may contain callbacks for other core and contributed APIs if necessary.
+
+
+The format base class is the most important part of the course format as it defines how the format interacts with both frontend and backend. Depending on the methods a format plugin overrides the course will behave in different ways.
+
+### lang/en/format_pluginname.php
+
+import langExample from '!!raw-loader!./_examples/format_lang.php';
+
+
+
+### classes/output/renderer.php
+
+import Renderer from '!!raw-loader!./_examples/renderer.php';
+
+
+
+This class should:
+
+- use the namespace `format_pluginname\output` (all files in the classes folder should be namespaced)
+- be called renderer (all files in the classes folder match the name of the file)
+- extend `core_courseformat\output\section_renderer`. See Output renderers for more information.
+- Use the namespace `format_pluginname\output` (all files in the classes folder should be namespaced)
+- The class should be called renderer (all files in the classes folder match the name of the file)
+- Should extend `core_courseformat\output\section_renderer`. See Output renderers for more information.
+
+Renderer methods are powerful enough to override all the rendering logic of the course. However, by default, the way to render a course should be based on output classes and templates (see the [output structure section](#format-output-classes-and-templates) for more information).
+
+Since the course format is all about the display it is very important to separate HTML and PHP. All HTML code should be in your `format_pluginname_renderer` class in renderer.php. Ideally, format.php will only call one function from the renderer. Use of renderer is required if you want to output content header and footer.
+
+### version.php
+
+
+
+### classes/output/courseformat/
+
+
+
+The course rendering is based on output classes and templates. Course format plugins can override specific output classes to provide alternative data to the templates. All output classes inside output/courseformat folder will override the default ones automatically. See the output structure section below for more information
+
+### templates/ and templates/local/
+
+
+
+The course rendering is based on output classes and templates. This folder will contain the specific mustache templates of your plugin. See the [override mustache blocks section](#override-mustache-blocks) section for more information.
+
+## Creating a new format
+
+### Using tool_pluginskel
+
+The easiest way to create a new course format using the latest version of tool_pluginskel. You can use the following yaml file to generate a basic course format skeleton. It is important to note that the "requires" attributes should be at least 4.0 in order to generate a Moodle 4.0+ version of the plugin, otherwise the resulting plugin will use a deprecated structure.
+
+
+ View pluginskel recipe
+
+
+import PluginskelRecipe from '!!raw-loader!./_examples/pluginskel_recipe.yaml';
+
+{PluginskelRecipe}
+
+
+
+
+### Manual option: copy the code from an existing code
+
+However, if for some reason you cannot use the latest version of tool_pluginskel, you can copy the code from the topics or weeks formats. Once you do the copy you should:
+
+1. Copy the folder containing the format files.
+2. Rename the folder to the new name. Course format names cannot exceed 21 characters.
+3. Rename language files in course/format/pluginname/lang/
+4. Change `$string['pluginname']` in course/format/pluginname/lang/en/format_pluginname.php to the new name.
+5. Rename class name in lib.php to format_pluginname.
+6. Search and replace other occurrences of the old format name, for example in renderer, capabilities names, settings, JavaScript libraries, etc.
+7. The new format is ready for modification.
+8. After modifying the code, check it with the Code checker.
+
+## Upgrading format to the next Moodle version
+
+Read the files course/format/upgrade.txt, lib/upgrade.txt, and also upgrade.txt of the core APIs that you use in your course format plugin and make changes according to them. When testing don't forget to enable Developer debugging level and error display (Settings->Developer->Debugging).
+
+In case your plugin is a Moodle 3.11 compatible plugin, see the [migration guide](./format/migration) for more information.
+
+## Extending the format base class
+
+All format plugins require a lib.php file containing a format_pluginname class. This class should extend `core_courseformat\base` and define how the plugin will integrate with the core_courseformat subsystem.
+
+Here are the main features in course formats and responsible for them format_base functions.
+
+## Course sections
+
+There is existing functionality in Moodle core to support organizing course modules into sections. Course format plugins do not have to use them but most of them do. Database table `course_sections` stores basic information about sections. Also section info is cached and returned by `format->get_modinfo()` (or the global function `get_fast_modinfo()`) so every action changing the sections must be followed by rebuild_course_cache(). Course module must always belong to the section. Even if your course format does not use sections, the section with the number 0 is always created.
+
+You must define `$string['sectionname']` if your language file even if the format does not use sections because it can be called unconditionally from other parts of the code, even though it won't be displayed.
+
+| `core_courseformat\base` Overridable method | Description |
+|---|---|
+| `uses_sections()` | returns true or false if the format uses sections or not. There is a global function `course_format_uses_sections()` that invokes it. It affects default navigation tree building. Various modules and reports may call this function to know whether to display the section name for the particular module or not. |
+| `get_default_section_name()` | This method gets the default section name if the user has not provided a value for the section name. In format_base, it basically calls `get_section_name()`, which returns the `$string['sectionname']` + the section number of the current section, if available, or blank, otherwise. It can be used in conjunction with your course format's `get_section_name()` implementation. For reference, please refer to the implementations in format_topics and format_weeks classes. |
+| `get_section_name()` | Returns the name for a particular section. This function may be called often so it should use only fields cached in section_info object (field course_sections.name is always cached) In 3.0+, it checks if the `$string['sectionname']` is available in the lang file. If the section name string is not available, it returns an empty string. |
+| `get_view_url()` | Returns the URL for a particular section, it can be either anchored on course view page or separate page. See parent function PHPdocs for more details |
+| `is_section_current()` | Specifies if the section is current, for example, current week or highlighted topic. This function is only used if your renderer uses it for example if your renderer extends format_section_renderer_base. This function is not called from anywhere else in Moodle core. |
+| `get_section_highlighted_name()` | Return the textual label for a current/highlighted section.|
+| `set_section_number()` | Setup the format base instance to display a single section instead of all. This method is used to prepare the format base instance to render the course. |
+| `get_section_number()` | Return zero if the course will deploy all sections or a section number if the current page is only presenting a single section. |
+| `get_course_display()` | Return `COURSE_DISPLAY_SINGLEPAGE` or `COURSE_DISPLAY_MULTIPAGE` depending if the course has multiple section per page or not. |
+| `get_last_section_number()` | Returns the last section |
+| `page_title()` | Formats can override this method to alter the page title. |
+
+### Course features
+
+The format base class has several methods to integrate the course format with the frontend course editor and its webservices.
+
+| `core_courseformat\base` Overridable method | Description |
+|---|---|
+| `ajax_section_move()` | Code executed after sections were rearranged using drag and drop. See the example in format_topics where sections have automatic names depending on their sequence number |
+| `uses_course_index()` | Return true if the course format is compatible with the course index drawer. Note that classic based themes are not compatible with the course index. |
+| `uses_indentation()` | If the format uses the legacy activity indentation. |
+| `supports_components()` | Since Moodle 4.0 the course is rendered using reactive UI components. This kind of component will be the only standard in Moodle 4.3+ but, until then, formats can override this method to specify if they want to use the previous UI elements or the new ones. |
+| `supports_news()` | Determine if the news forum is mandatory or not on the course format. |
+| `get_default_blocks()` | Course format can specify which blocks should be added to the course page when the course is created in this format. If the course format is changed during course edit, blocks are not changed. Whatever course format specifies in the method, site admin can override it with `$CFG->defaultblocks_override` or `$CFG->defaultblocks_pluginname` |
+| `extend_course_navigation()` | This function is called when a navigation tree is built. Node for the course will be created in navigationlib for you and all standard available branches like 'Participants' or 'Reports' will be added to it. After that course format can add nodes for sections and modules. There is a default implementation that adds branches for course sections and modules under them. Or if the course format does not use sections, all modules will just be placed under course mode. The course format is able to override the default navigation tree building. Note that if navigationlib can not find the node for the current course module, the node will be added automatically (after this callback). |
+
+### Course format options
+
+The core table `course_format_options` in Moodle database is designed to store additional options for course formats. Those options may belong for the whole course or just for course section.
+
+Course format options must not have the same names as fields in database table `course`, section options must not have the same names as fields in `course_sections`. Also, make sure names do not duplicate completion and conditional fields in edit forms.
+
+When the teacher changes the course format in the course edit form AND the old and the new course formats share the same option name, the value of this option is copied from one format to another. For example, if the course had format Topics and had 8 sections in it and teacher changes format to Weeks, the course will have 8 weeks in it.
+
+During backup the course format options are stored as if they were additional fields in `course` table. Do not store IDs of elements (courses, sections, etc.) in course format options because they will not be backed up and restored properly. You can use section numbers because they are relative inside the course. If absolute ids are necessary you can create your own backup/restore scripts, see Backup API.
+
+Webservices expect course format options to be passed in additional entities but for backward compatibility `numsections`, `hiddensections` and `coursedisplay` can also be passed as if they were fields in `course` table.
+
+| `core_courseformat\base` Overridable method | Description |
+|---|---|
+| `course_format_options()` | By overriding this method course format specifies which additional options it has for course |
+| `section_format_options()` | By overriding this method course format specifies which additional options it has for course section. Note that since section information is cached you may want to cache some additional options as well. See PHPdocs for more information |
+| `get_format_options()` | (usually no need to override) low level function to retrieve course format options values. It is more convenient to use methods get_course() and get_section() |
+| `create_edit_form_elements()` | This function is called to alter course edit form and standard section edit form. The default implementation creates simple form elements for each option defined in either `course_format_options()` or `section_format_options()`. Overwrite it if you want to have more comprehensive form elements or if you do not want options to appear in edit forms, etc. |
+| `edit_form_validation()` | Overwrite if course format plugin needs additional validation for it's option in course edit form |
+| `update_format_options()` | (usually no need to override) low level function to insert/update records in db table `course_format_options` |
+| `update_course_format_options()` | updates course format options with the data from the edit course form. The plugin can override for example to include calculated options fields, especially when the course format is being changed. For example, format_topics and format_weeks automatically fill field `numsections` when the user switches from other format |
+| `update_section_format_options()` | updates course format options for the section with the data from the edit section form |
+| `editsection_form()` | Return an instance of moodleform to edit a specified section. Default implementation returns instance of `editsection_form` that automatically adds additional fields defined in section_format_options() |
+| `get_default_course_enddate()` | Overwrite if the course format is time-based. The base class calculates the default course end date based on the number of sections. |
+| `delete_format_data()` | This hook method is called when the course is deleted and can be used to remove any course data not stored in the standards `course_format_options` and `course` tables (like user preferences, for example). |
+
+Course format base helpers
+The format base class is used for all the core_courseformat integrations, from settings to rendering. All course output classes will receive the course format instance as a primary param and the class has several helper methods to get information about the format.
+
+| `core_courseformat\base` Overridable method | Description |
+|---|---|
+| `get_course()` | (no need to override) returns object with all fields from db table `course` AND all course format options |
+| `get_section()` | (no need to override) returns instance of section_info. It will contain all fields from table `course_sections` and all course format options for this section |
+| `get_sections()` | Return all course sections (it is just a wrapper fo the modinfo get_section_info_all) |
+| `get_course_display()` | Returns if the course is using a multi page or a single page display (`COURSE_DISPLAY_MULTIPAGE` or `COURSE_DISPLAY_SINGLEPAGE`) |
+| `get_modinfo()` | Returns the current course modinfo (equivalent to get_fast_modinfo but without specifying the course) |
+| `get_renderer()` | Return the course format renderer instance |
+| `get_output_classname()` | This method gets a relative output class path (for example, "content\\section") and returns the correct output class namespace depending on if the format has overridden outputs or not. See [overriding output classes](#override-output-classes) section for more information. |
+| `is_section_current()` | Returns if a specific section is marked as current (highlighted) or not. |
+| `show_editor()` | Do all the user and page validations to know if the current course display has to include editor options or not. This includes both page editing mode and user capabilities. You can pass an array of capabilities which should be checked. If none specified, will default to `moodle/course:manageactivities`. |
+
+## Rendering a course
+
+Each format plugin is responsible for rendering the course in the format.php file, this means each plugin can choose how to render the course content. However, there are some conventions on how a course should be rendered to integrate the plugin with the existing components.
+
+The course rendering is done using four mains elements:
+
+- **File view.php:** responsible for setting up the format base instance and rendering the content.
+- **Course format renderer:** all format plugins must provide its own version of the `core_courseformat\output\section_renderer` (or provide an equivalent class with all the necessary methods).
+- **Output classes:** by default all course elements have a specific output class inside course/format/output/local folder. Each one of them is responsible for generating the necessary data to render the templates. Format plugins can provide alternative versions of those output classes (see [override output classes](#override-output-classes) for more information)
+- **Mustache templates:** each output class has its equivalent mustache template inside course/format/templates. All the course content is rendered using a single "content" template that includes all the rest as sub-templates. For this reason, a plugin that wants to override some templates must provide some extra templates in order to keep the template structure. See [Creating the basic output structure](#creating-the-basic-output-structure) section for more information.
+
+Unless there's a reason for it, the course structure should be rendered overriding the standard outputs and mustache templates.
+
+## Format output classes and templates
+
+The following diagram shows the standard output classes structure:
+
+
+
+There are three renderer methods used for refreshing fragments of the course page:
+
+- **render_content:** used to render the full course. This method is not necessary as the content output itself will be used by default.
+- **course_section_updated:** needed when the frontend needs to render a particular section. It is used mostly when a new section is created. By default it renders the core_courseformat\output\local\content\section output.
+- **Course_section_updated_cm_item:** used every time the frontend needs to update an activity card on the course page. By default it will render the core_courseformat\output\local\content\section\cmitem. The reason why it uses this specific output is that it renders the full course module list item, not just the activity card.
+
+By default, the base renderer methods will use the format output components to render the full course. In case your plugins have special needs, it is possible to override those three methods directly into the format renderer class.
+
+### Override output classes
+
+Instead of having several renderer methods on a single file, the core_courseformat subsystem splits the output logic through several small classes, each one for a specific UI component. Format plugins can easily override specific classes to alter the template data.
+
+The course format base class has a special method called **get_output_classname** that returns the overridden class name if available in the format plugin (or the core one if not). In order to detect the format classes, your plugin must place the overridden one in the equivalent path inside your plugin format_pluginname\output\courseformat\ folder
+
+For example, if a format plugin wants to add new options to the section action menu it should override the core_courseformat\output\local\content\section\controlmenu. To do so the plugin class should be format_pluginname\output\courseformat\content\section\controlmenu. You can find an example of an overridden output in the "course/format/topics/classes/output/courseformat/content/section/controlmenu.php" file.
+
+### Creating the basic output structure
+
+By default, the course renderer will use the core_courseformat output classes and templates. To override some parts of the course elements the plugin must provide a minimum output classes and template structure.
+
+#### Basic output classes
+
+It is recommended your plugin overrides the 3 main course format elements:
+
+- format_pluginname\output\courseformat\content
+- format_pluginname\output\courseformat\content\section
+- format_pluginname\output\courseformat\content\section\cmitem
+
+Those output classes should extend the equivalent core ones but, at least, they should override the **get_template_name** method to redirect the rendering template to the overridden template. It could also override the export_for_template to alter the template data if necessary.
+This is the minimum output classes your plugin must provide:
+
+
+
+
+import OutputContent from '!!raw-loader!./_examples/output/content.php';
+export const OutputContentProps = {
+ examplePurpose: 'Output content',
+ plugintype: 'format',
+ pluginname: 'pluginname',
+ filepath: '/output/courseformat/content.php',
+};
+
+
+
+
+
+
+#### Basic template files
+
+Unlike output classes, mustache files cannot be extended nor overridden. To be able to alter specific mustaches your plugin must provide a minimum template structure. To allow partial overriding, the core_courseformat template uses blocks instead of inclusions to include sub templates.
+
+This is the minimum template structure your plugin must provide:
+
+
+
+
+import TemplateContent from '!!raw-loader!./_examples/output/content.mustache';
+
+{TemplateContent}
+
+
+
+
+import TemplateSection from '!!raw-loader!./_examples/output/section.mustache';
+
+{TemplateSection}
+
+
+
+
+import TemplateCmitem from '!!raw-loader!./_examples/output/cmitem.mustache';
+
+{TemplateCmitem}
+
+
+
+
+### Override mustache blocks
+
+Once your plugin has the basic mustache structure, you can provide extra mustache blocks to override parts of the page. To do so it is important to understand first the special way in which the course format mustaches are included.
+
+Most moodle mustache files include sub-templates by doing `{{> path/to/the/template }}`. However, in the course format subsystems, all sub-templates are loaded using a slightly different pattern.
+
+
+ View example: override course format templates using mustache blocks
+
+
+For example, imagine a mustache file "original/path/parent" including "original/path/to/the/template":
+
+```handlebars
+{{$ original/path/to/the/template }}
+ {{> original/path/to/the/template }}
+{{/ original/path/to/the/template }}
+```
+
+Using this pattern any parent template can replace the sub-template doing:
+
+```handlebars
+{{! Then include the parent template }}
+{{< original/path/parent }}
+ {{! Add custom blocks. }}
+ {{$ original/path/to/the/template }}
+ {{> new/template/path }}
+ {{/ original/path/to/the/template }}
+{{/ original/path/parent }}
+```
+
+Or can wrap the content with extra HTML:
+
+```handlebars
+{{! Then include the parent template }}
+{{< original/path/parent }}
+ {{! Add custom blocks. }}
+ {{$ original/path/to/the/template }}
+
+ {{> new/template/path }}
+
+ {{/ original/path/to/the/template }}
+{{/ original/path/parent }}
+```
+
+Of even replace the fill block by an alternative HTML:
+
+```handlebars
+{{! Then include the parent template }}
+{{< original/path/parent }}
+ {{! Add custom blocks. }}
+ {{$ original/path/to/the/template }}
+
+
+
+Due to the fact that mustache blocks are not scoped, blocks can be overridden by any of the parent templates. This generates some possible scenarios depending on your format needs:
+
+- **[Scenario 1](#scenario-1-adding-blocks-directly-on-the-three-main-mustache-templates):** your format just overrides a few course elements like adding menu options o tweaking the sections or activity HTML.
+- **[Scenario 2](#scenario-2-keep-all-the-intermediate-templates-structure):** your plugin needs a big UI change, keeping the general structure but altering several course elements.
+- **[Scenario 3](#scenario-3-just-keep-a-few-renderer-methods):** Your plugin is a completely different thing that does not follow any standard course rule. Almost everything in your format is done from scratch.
+
+#### Scenario 1: adding blocks directly on the three main mustache templates
+
+If your format only overrides a few inner templates of the course, the overriding blocks can be located in one of the 3 basic templates:
+
+- **local/content:** for general course structure elements
+- **local/content/section:** to alter section elements
+- **local/content/section/cmitem:** to alter activity elements
+
+
+ View example
+
+
+If a format requires to override the activity visibility badges your format_pluginname/local/content/section/cmitem template will look like:
+
+```handlebars
+{{! include the original course format template block }}
+{{< core_courseformat/local/content/section/cmitem }}
+ {{! Add custom blocks here. }}
+ {{$ core_courseformat/local/content/section/badges }}
+ {{> format_pluginname/local/content/cm/badges }}
+ {{/ core_courseformat/local/content/section/badges }}
+{{/ core_courseformat/local/content/section/cmitem }}
+```
+
+
+
+
+Benefits of this approach:
+
+- Easy to maintain in a short term. All templates overrides are located on one of the 3 main templates.
+- Fewer files. The plugin only contains the three mains files and the overridden ones.
+
+Negatives of this approach:
+
+- Harder to maintain in the long run. Is it possible that the format must refactor the template structure if future versions require more main templates to be refreshed via ajax.
+
+#### Scenario 2: keep all the intermediate templates structure
+
+If your plugin needs a big UI change, altering a considerable number of the course elements at different levels (course, section and/or activity), the previous approach is not recommended as it may require more code refactoring in future versions.
+
+To keep your plugin more stable through time the best approach is to override the mustache blocks at the inner parts of the mustache files structure instead of at the main elements. It will require more code but the final result will be more stable.
+
+
+ View example
+
+
+Let's say your format requires overriding the activity visibility badges as in the previous scenario example. Apart from the three main templates, the plugin must create several new template overrides until it reacher the activity badges one:
+
+CM item main file: format_pluginname/local/content/section/cmitem template:
+
+```handlebars
+{{! include the original course format template block }}
+{{< core_courseformat/local/content/section/cmitem }}
+ {{$ core_courseformat/local/content/cm }}
+ {{> format_pluginname/local/content/cm }}
+ {{/ core_courseformat/local/content/cm }}
+{{/ core_courseformat/local/content/section/cmitem }}
+```
+
+The content/cm template:
+
+```handlebars
+{{< core_courseformat/local/content/cm/activity }}
+ {{$ core_courseformat/local/content/cm/badges }}
+ {{> format_pluginname/local/content/cm/badges }}
+ {{/ core_courseformat/local/content/cm/badges }}
+{{/ core_courseformat/local/content/cm/activity }}
+```
+
+The content/cm/badges will contain only the overridden HTML.
+
+Apart from the templates files, the plugin could also provide overridden output classes to ensure that future versions will remain compatible if new ajax partials are required:
+
+In the example the extra output classes can look like:
+
+```php tile="format_pluginname\output\local\content\cm class"
+
+
+
+Benefits of this approach:
+
+- Easy to maintain in the long term. The code will remain the same if future versions require more main templates.
+- Overridden templates can be rendered as a regular course format output. Because each of the overridden mustaches has also its output class, the course format subsystem can render them independently.
+
+Negatives of this approach:
+
+- Requires extra files to keep the structure. Most of the files are just there to ensure the course format subsystem knows how to render them if needed in the future.
+
+#### Scenario 3: just keep a few renderer methods
+
+If your plugin is a completely different thing from a regular course you are most likely on your own. Your format may use a completely different set of renderer methods or output classes and you already have your own template structure.
+
+In that case, you may keep it that way. However, there are a few things you could add to your plugin in order to make it compatible with the new course output. Take in mind that the new course editor expects some conventions about renderer methods that should be easy to incorporate into your plugin.
+
+The first thing you should add is all the section and activities data attributes (see [course elements data attributes](#course-elements-data-attributes). The new course editor did not use CSS classes anymore but data attributes. If your format should be able to interact with the standard editor those attributes are necessary.
+
+Secondly, the new frontend JS modules use renderer methods to refresh a full section or an activity. If your format wants to keep this feature you should implement two methods on your renderer class:
+
+- **course_section_updated:** to render a single section.
+- **course_section_updated_cm_item:** to render a single course module item. Note that this method does not render an activity card only but also the full course item. In a regular course, this also includes the "li" element.
+
+And third, consider using "local/content" as your main course template, "output\local\content" as your main output class or, if you don't use output classes, use the render_content renderer method to print a full course. Those are the expected names to render the full course. For now, they are only used in your plugin "format.php" file but nobody can guarantee this will continue this way in the future.
+
+## The course editor structure
+
+The core_courseformat provides several JavaScript modules that will be enabled when a teacher edits the course. Those libraries use a reactive pattern to keep the course updated when some edit action is executed.
+
+The following diagram represents the data flow of the new architecture:
+
+
+
+### Enabling the course editor
+
+To enable the course editor in your format you should add the following method to your format base class (in your course/format/pluginname/lib.php):
+
+```php
+/**
+ * Enable the component based content.
+ */
+public function supports_components() {
+ return true;
+}
+```
+
+### Course elements data attributes.
+
+The course editor modules use data attributes to find the course elements in the page. If your plugin alters the default templates you should keep those attributes in your HTML structure.
+
+The following table describes the data attributes:
+
+| Concept | Required data attributes |
+|---------|------------------------|
+| **Section** | `data-for="section"` `data-id={SECTION.ID}` `data-number={SECTION.NUM}` |
+| **Section header** | `data-for="section_title"` `data-id={SECTION.ID} data-number={SECTION.NUM}` |
+| **Course module item (activity)** | `data-for="cmitem"` `data-id={CM.ID}` |
+| **Course sections list** | `data-for="course_sectionlist"` |
+| **Section course modules list** | `data-for="cmlist"` |
+| **Course module action link** | `data-action={ACTIONNAME}` `data-id={CM.ID}` |
+| **Section action link** | `data-action={ACTIONNAME}` `data-id={SECTION.ID}` |
+| **Section info** | `data-for="sectioninfo"` |
+
+## Implementing format specific actions
+
+All course editing actions done by the user will trigger what is called "state actions" that will update the backend, but also send back information on how to update the frontend in real time. The core state actions are defined in the `core_courseformat\stateactions` class. However, each format plugin extend this class to add its own actions by placing them in the `course/format/PLUGINNAME/classes/courseformat/stateactions.php` file.
+
+All state action will receive the same parameters:
+
+- `stateupdates $updates` an object to register the state updates. This object will be used to inform the frontend about the changes.
+- `stdClass $course` the course object
+- `int[]` $ids the list of affected ids. They could be cmids or section ids depending on the action.
+- `int $targetsectionid` optional target section id. For example, when moving a course module to a new section.
+- `int $targetcmid` optional target cm id. For example, when moving a course module to a new position.
+
+The best example on how to implement a state action is to look at the `format_topics\courseformat\stateactions` class (located at `course/format/topics/classes/courseformat/stateactions.php`). The topics format incorporate two new state actions: `section_highlight` and `section_unhighlight`.
+
+Once your plugin has the integration class implemented with all the extra actions, the action will be available to be executed by the user.
+
+### Execute format specific action from the course page via Ajax
+
+Once the format provides a state action, the plugin must inform the frontend about the new action.
+
+As explained in previous sections, the course editor uses a reactive pattern to keep the course updated. The course editor will always modify the UI to represent the current state data. When a user executes an action, the course editor will send the action to the backend, wait for the state updates, and apply then to the frontend state data. This is what is known as `mutation`.
+
+The `mutations` is the only way to execute state actions from the frontend, and each plugins that implements new state actions in the backend must provide a mutation to the frontend.
+
+Once again, the best example on how to implement a mutation is to look at the `course/format/topics/amd/src/mutations.js`. The topics format incorporate two new mutations: `sectionHighlight` and `sectionUnhighlight`.
+
+The JavaScript reactive components can execute the mutation using the `this.reactive.dispatch` method.
+
+### Execute state actions via non-AJAX requests
+
+
+
+Thanks to the standard state actions parameters, it is possible to execute state actions via non-AJAX requests by access the `WWWROOT/course/format/update.php` in your instance. The URL should be called with the following parameters:
+
+- `sesskey`: the user session key.
+- `action`: the action name.
+- `courseid`: the course id.
+- `ids` (as array) or `id` as an integer: the list of affected ids (they could be section ids or course module ids depending on the action).
+- `targetsectionid`: the target section id (optional).
+- `targetcmid`: the target cm id (optional).
+- `returnurl`: the URL to redirect the user after the action is executed.
+
+For most state actions, format plugins does not need to implement any extra code to support non-AJAX requests. However, if the action requires some confirmation or extra steps, the plugin can provide an extra output class to render the confirmation forms for the custom state actions.
+
+To provide extra confirmation steps to non-AJAX alternative, the plugin can extend the default confirmation output class following those steps:
+
+1. **Create a New Class**: In your format plugin, create a new `format_PLUGINNAME\output\courseformat\courseupdate` class that extends the `core_courseformat\output\local\courseupdate` class.
+
+2. **Implement a `ACTIONNAME_confirmation_dialog` Method**: The method should return the confirmation HTML. The confirmation dialog should include a form with a submit button to `new url($this->actionurl, ['confirm' => 1])`. By adding the confirm parameter to the URL, the action will be executed.
+
+### Example
+
+```php
+// filepath: /path/to/your/format/plugin/classes/courseupdate.php
+namespace format_yourplugin;
+
+use core_courseformat\output\local\courseupdate;
+
+class courseupdate extends courseupdate {
+
+ public function section_dosomething_confirmation_dialog(
+ renderer_base $output,
+ stdClass $course,
+ array $ids = [],
+ ?int $targetsectionid = null,
+ ?int $targetcmid = null,
+ ): string {
+ return $output->confirm(
+ message: get_string('sectiondosomething', 'format_yourplugin'),
+ cancel: $this->returnurl,
+ continue: new url($this->actionurl, ['confirm' => 1]),
+ displayoptions: [
+ 'confirmtitle' => get_string('sectiondosomething_title', 'format_yourplugin'),
+ 'type' => single_button::BUTTON_DANGER,
+ 'continuestr' => get_string('delete'),
+ ]
+ );
+ }
+}
+```
+
+## Activity badge
+
+
+
+The new activity card design proposed for Moodle 4.3 differentiates badge information from other HTML content (displayed using the pre-existing `afterlink` feature).
+A new `core_courseformat\output\activitybadge` class has been added to let modules extend it to display any content in a badge near the activity name.
+Some considerations about its main features:
+
+- The badge content is always plain text (no HTML).
+- The badge style can be set (by default is initialized with badge-none, but it can be set by any module).
+- An optional URL to redirect the user when the badge is clicked.
+- An optional ID to add the element in case the module wants to add some JS to the badge events.
+- Optionally, any other extra HTML attributes to the badge element (for example, data attributes).
+
+Plugins can implement `mod_PLUGINNAME\output\courseformat\activitybadge` that extends from the original `core_courseformat\output\activitybadge` class. This class will delegate most data attributes to protected methods, so plugins will only need to implement the `update_content()` method, to set these attributes accordingly: `content`, `style`, `url`, `elementid` and `extrattributes`.
+
+```php title="course/format/classes/output/activitybadge.php"
+namespace core_courseformat\output;
+
+abstract class activitybadge implements named_templatable, \renderable {
+
+ /** @var array Badge defined styles. */
+ public const STYLES = [
+ 'none' => 'badge-none',
+ 'dark' => 'badge-dark',
+ 'danger' => 'badge-danger',
+ 'warning' => 'badge-warning',
+ 'info' => 'badge-info',
+ ];
+
+ /** @var cm_info The course module information. */
+ protected $cminfo = null;
+
+ /** @var string The content to be displayed in the activity badge. */
+ protected $content = null;
+
+ /** @var string The style for the activity badge. */
+ protected $style = self::STYLES['none'];
+
+ /** @var \moodle_url An optional URL to redirect the user when the activity badge is clicked. */
+ protected $url = null;
+
+ /** @var string An optional element id in case the module wants to add some code for the activity badge (events, CSS...). */
+ protected $elementid = null;
+
+ /**
+ * @var array An optional array of extra HTML attributes to add to the badge element (for example, data attributes).
+ * The format for this array is [['name' => 'attr1', 'value' => 'attrval1'], ['name' => 'attr2', 'value' => 'attrval2']].
+ */
+ protected $extraattributes = [];
+
+ [...]
+
+ abstract protected function update_content(): void;
+
+}
+```
+
+This feature has been implemented by:
+
+- **Forum**, to display the unread messages.
+
+ ```php title="mod/forum/classes/output/courseformat/activitybadge.php"
+ namespace mod_forum\output\courseformat;
+
+ class activitybadge extends \core_courseformat\output\activitybadge {
+
+ protected function update_content(): void {
+ global $CFG;
+
+ require_once($CFG->dirroot . '/mod/forum/lib.php');
+
+ if (forum_tp_can_track_forums()) {
+ if ($unread = forum_tp_count_forum_unread_posts($this->cminfo, $this->cminfo->get_course())) {
+ if ($unread == 1) {
+ $this->content = get_string('unreadpostsone', 'forum');
+ } else {
+ $this->content = get_string('unreadpostsnumber', 'forum', $unread);
+ }
+ $this->style = self::STYLES['dark'];
+ }
+ }
+ }
+ }
+ ```
+
+- **Resource**, to show the file type (extension). The rest of the resource information (size and creation date) has been kept in the `afterlink` section.
+
+ ```php title="mod/resource/classes/output/courseformat/activitybadge.php"
+ namespace mod_resource\output\courseformat;
+
+ class activitybadge extends \core_courseformat\output\activitybadge {
+
+ protected function update_content(): void {
+ $options = (object) ['displayoptions' => $this->cminfo->customdata['displayoptions']];
+ $this->content = resource_get_optional_filetype($options, $this->cminfo);
+ }
+ }
+ ```
+
+A new `core_courseformat/local/content/cm/activitybadge` template has been also created to display this activity badge data. As usual, it can be overridden by any format plugin.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/format/migration.md b/versioned_docs/version-5.1/apis/plugintypes/format/migration.md
new file mode 100644
index 0000000000..2e017dfd9f
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/format/migration.md
@@ -0,0 +1,284 @@
+---
+title: Migrating 3.11 formats
+tags:
+ - Plugins
+ - Format
+---
+
+
+
+
+
+
+import { getExample } from '@site/src/moodleBridge';
+
+The new course editor introduced n Moodle 4.0 reimplements most of the previous webservices, AMD modules, and internal logic of the course rendering. However, all formats since 3.11 will use the previous libraries by default until its final deprecation in Moodle 4.3. This document collects the main adaptations any 3.11 course format will require to continue working when this happens.
+
+## Changes summary
+
+The main areas affected by the new 4.0 course editor are:
+
+- Course format plugins are now part of the `core_courseformat` subsystem
+- The old format_base class is now `core_courseformat\base` and it is mandatory for all format plugins to extend.
+- Format plugin renderer is now mandatory (and in most cases they will extend `core_courseformat\output\section_renderer`)
+- The old format_section_renderer_base is now `core_courseformat\output\section_renderer`
+- The `core_courseformat\output\section_renderer` class now it extends core_course_renderer directly. This means that `$this->courserenderer` attribute is deprecated.
+- All renderer methods using html_writer are deprecated. All UI elements are now rendered using output classes and mustache files. Formats can provide alternative output classes and templates.
+- The new editor uses data attributes instead of CSS classes to locate section and activities elements in the page HTML. All elements must add those attributes to use the new AMD modules.
+- Most of the logic of the previous core_course/actions AMD module is now replaced with core_courseformat AMD modules, each one corresponding to the specific UI elements.
+- Formats can override the `core_courseformat\base::uses_course_index` in their base class to enable the course index.
+- Formats can override the `core_courseformat\base::supports_components` in their base class to use the new course editor library instead of the legacy one.
+- The course frontend uses a reactive state to maintain the UI elements updated. Format plugins can create new reactive AMD modules to interact with that state or extend the core_courseformat state classes to extend the data stored in that state.
+- Course formats now can implement the `core_courseformat\base::delete_format_data` hook to clean data when the course is deleted.
+- The course `format_base` class now provides a method `show_editor` to know if the user is editing or not the course depending on the page editing and the user capabilities. This method should be used instead of the previous `$PAGE->user_is_editing() && has_capability('moodle/course:manageactivities', $coursecontext)`. If you need to check for different capabilities, you can pass an array of them. If not specified, defaults to only `moodle:course/manageactivities`.
+
+## Moodle 3.11 vs 4.0 course editor architecture
+
+The 4.0 course editor follows a completely new pattern that is not compatible with the previous one. That new pattern is called **reactive components**.
+
+To ensure all 3.11 formats are still usable in 4.0 the new architecture is opt-in, meaning that in order to use the new libraries old formats must indicate to the system that they are compatible (overriding the `core_courseformat\base::supports_components`).
+
+The following table compares some of the main changes:
+
+| Moodle 3.11 | Moodle 4.0 |
+|---- | ---- |
+| AMD module "core_course/actions" is responsible for capturing the course edition actions, sending them to the correct course webservice, and replacing the returned HTML into the proper page elements. | Al the functionalities from the original actions are now divided into several AMD modules:
core_courseformat/local/courseeditor: coordinates all the UI elements via a data structure called "reactive state data" (or simply, "state data").
core_courseformat/local/courseeditor/mutations: centralizes all the backend calls and applies the results to the reactive state data before the UI components update the interface.
core_courseformat/local/content/actions: captures the user clicks on specific action links and displays modals to get more information from the user if necessary (for example the destination position in a move activity action).
core_courseformat/content/\*: ADM modules responsible for keeping some part of the UI aligned with the reactive state data.
core_courseformat/courseeditor/\*: other modules and helpers
NOTE: some user actions like hiding and showing sections/activities are not yet migrated to the new architecture.
NOTE: updating a full section or activity HTML is still handled by the core_course/actions module. However, now both methods are public to the module and they are used by the new course editor.
|
+| course/dndupload.js file is responsible for handling any file dropping on the course page. | Moodle 4.0 still uses the course/dndupload.js to handle files dropped into the course area. |
+| The webservice core_course_edit_section is responsible for updating a course section and returning the section header and the activities list HTML. |
Except for showing and hiding a section (not migrated yet) the rest of the section actions are now handled by the new `core_courseformat_update_course` webservice.
The new webservice has the same parameters for both section and activity actions and always returns a standardized data structure to interact with the frontend reactive state data. This means that it does not return HTML fragments anymore.
|
+| The webservice core_course_edit_module is responsible for updating activity and returning the activity HTML or other page fragments. |
Except for showing and hiding an activity (not migrated yet) the rest of the activity actions are now handled by the new `core_courseformat_update_course` webservice.
The new webservice has the same parameters for both section and activity actions and always returns a standardized data structure to interact with the frontend reactive state data. This means that it does not return HTML fragments anymore.
|
+| All frontend JS logic is responsible for interacting with the backend and updating all the necessary frontend elements depending on the return value. |
The JS logic is now distributed into several modules called "components". Each module can only interact with a specific page element and watch a data structure called reactive state date (or simply "state data").
The main points of the new component structure are:
The initial state data is loaded using the `core_courseformat_get_state` webservice
All components are registered into the core_courseformat/courseeditor page instance.
When the state data changes, the course editor instance triggers the component's watchers methods to update the UI.
Components are not able to alter the state data. If a component captures an action that requires some state change, it asks the course editor to perform a state mutation.
|
+| Formats can alter the page HTML by overriding format_section_renderer_base methods |
Most renderer methods are now deprecated and have been migrated to output classes and mustache templates. There are only a few rendered methods formats that can override.
Now format plugins can override output classes by simply creating the equivalent format_pluginname/output/courseformat/* class. For example, core_courseformat\output\local\content can be overridden by creating a format_pluginname\output\courseformat\content class.
Important note: the new mustache structure uses partials and blocks to include sub-templates. This opens the door to a future frontend course rendering but it also makes the template overriding a bit more complex. See the "overriding templates" section for more information.
|
+|
Format renderer is optional. If none is provided the course format renderer_base is used.
Inside a format rendered the `$this->courserenderer` attribute is used to access the course renderer methods.
| Format renderer is mandatory. The base core_courseformat\output\section_renderer now extends the core_course_renderer class and `$this->courserenderer` is deprecated. |
+| The page elements are located using css class names such as "li.activity", ".actions", or "li.section". | All page elements are now located using data attributes. See [Course elements data attributes](./index.md#course-elements-data-attributes) for more information. |
+| The course is rendered using print_single_section_page or print_multiple_section_page depending on the number of sections to render. |
The course format base instance contains all the necessary data to determine the way a course is rendered. To Specify a single section page formats should use `$format->set_section_number` method before rendering the course.
Once the format instance setup is finished, the course is rendered using the `content` output class. See migrating old renderer methods to outputs section for more information.
|
+| To know if the user is editing the course, the format should check for `$PAGE->user_is_editing()` to know if edit is enabled and also `has_capability('moodle/course:update', $coursecontext)` to know if the user has the required capabilities. | The format base class have a method `$format->show_editor()` that do all the user and page validations to know if the current course display has to include editor options or not. |
+
+The following diagram represents the data flow of the new architecture:
+
+
+
+## First steps to migrate a 3.11 course format to 4.0
+
+From 4.0 the course/format folder has its own subsystem called core_courseformat. This subsystem contains all the course rendering logic (only some minor elements are still in the original location for retro compatibility until Moodle 4.3).
+
+To ensure your format plugin is integrated with the new subsystem:
+
+- The main format_pluginname class should extend `core_courseformat\base` instead of the old format_base one.
+- Your plugin **must provide a renderer class**. In most cases this class **will extend `core_courseformat\output\section_renderer`**.
+- Any output class your plugin needs to override should be located in format_pluginname/classes/output/courseformat
+
+In summary, the first two things you need to adapt to your format are:
+
+### Point 1: create a renderer class
+
+Now renderer classes are mandatory for course format plugins. Here there are two scenarios:
+
+Scenario 1: if you plugin already has one you should replace the old "extends format_section_renderer_base" by "extends core_courseformat\output\section_renderer".
+
+Scenario 2: If your plugin does not have a renderer, create a file **course/format/pluginname/classes/output/renderer.php** with a content like:
+
+
+ View example
+
+
+
+### Point 2: fix your base class
+
+All Moodle 3.11 formats have a base format class extending format_base class from core_course. In Moodle 4.0 this class has been moved to `core_courseformat\base`. This means that you should replace the existing extends to avoid the deprecation message.
+
+For compatibility reasons, the base class does not use a namespace and should be located in your plugin "lib.php" file. This could change after Moodle 4.3 when most old course rendering methods will be removed from core.
+
+## The new output architecture
+
+When the debug messages are enabled, all 3.11 format plugins will show several deprecation messages. Most of them are due to the fact that almost all previous renderer methods are now deprecated. The full list can be found in the **course/upgrade.txt** file.
+
+Until 3.11 all course page elements are rendered using html_writer inside renderer methods. This made the UI hard to maintain because most of the methods are referring to the standard course structure (section_right_content, section_left_content, start_section_list, end_section_list…) instead of generic concepts like sections, activities, or activity menu.
+
+With the new architecture, almost all UI elements are rendered using:
+
+- An **output class** that generates the data to render. For example, course/format/classes/output/local/content/section.php
+- A **mustache template** to render output class data. For example, course/format/templates/local/content/section.mustache
+- An optional **reactive component** to update the frontend when the reactive state data changes: For example, course/format/amd/src/local/content/section.js
+
+The following diagram represents the new output structure compared to the 3.11 one:
+
+
+
+## Migrating old renderer methods to outputs
+
+The process of migrating a renderer method to output can be complex depending on the element your format overrides. For these reasons, Moodle 3.11 formats will remain using the old renderer methods until they explicitly use the new outputs.
+
+### Step 1: start using output components and renderers
+
+As in Moodle 3.11 formats, all the course view is initialized and rendered in the "course/format/pluginname/format.php" file. However, the way the outputs are initialized is quite different from the previous version.
+
+In Moodle 3.11 the format.php process was something like:
+
+1. Do some param validations
+2. Get the course format instance using `course_get_format` or `core_courseformat\base::instance`
+3. Get the format/course renderer using: `$PAGE->get_renderer('format_pluginname')`;
+4. Use the renderer `print_single_section_page` or `print_multiple_section_page` depending on the section param.
+
+The problems with that approach were:
+
+- There are two main renderer methods which are almost the same
+- The auxiliary renderer methods require a big amount of params even if they are not needed for the method because they need to provide those parameters to all the child methods.
+
+To avoid this situation now the format base class instance is used as a single exchange param to all output classes. Once the format instance is initialized every output class can get all necessary information from it.
+
+The new workflow in 4.0 is:
+
+
+ View example
+
+
+
+Some important notes about the code:
+
+- The rendered class now can be obtained using `$format->get_renderer`
+- Format plugins can override any core_courseformat output class (see sections below for more details). To get the correct output you need to use `$format->get_output_classname` method.
+- As you may notice, the output class is rendered directly using the render method, not the `render_from_template` one. This is possible because all output classes implement the new Moodle 4.0 `named_templatable` interface.
+
+### Step 2: override any output class to alter the template data to your plugin needs
+
+Instead of having several renderer methods on a single file, the core_courseformat subsystem splits the output logic through several small classes, each one for a specific UI component. Format plugins can easily override specific classes to alter the template data.
+
+The course format base class has a special method called get_output_classname that returns the overridden class name if available in the format plugin, or the core one if not. In order to detect the format classes, your plugin must place the overridden one in your format_pluginname\output\courseformat\...
+
+For example, if a format plugin wants to add new options to the section action menu it should override the core_courseformat\output\local\content\section\controlmenu. To do so the plugin class should be format_topics\output\courseformat\content\section\controlmenu. You can find an example of an overridden output in the "course/format/topics/classes/output/courseformat/content/section/controlmenu.php" file.
+
+### Step 3: create the basic mustache structure
+
+Unlike output classes, mustache files cannot be extended nor overridden. To be able to alter specific mustaches your plugin must provide a minimum template structure. Furthermore, your plugin must provide some overridden output classes providing the alternative mustache templates location.
+
+In moodle 3.11 the course render uses html_writer to generate the course view, where each renderer method has both data collection and HTML generating inside. This is not the case in Moodle 4.0. From now on, the full output data is collected via output classes before rendering all the nested mustache templates.
+
+The course format subsystem requires a minimum of 3 mustaches to render a course:
+
+- **local/content**: the full course template
+- **local/content/section**: a section template. Used to refresh a section via ajax
+- **local/content/section/cmitem**: an activity item template: Used to refresh an activity via ajax
+
+The first thing your plugin needs is to create that structure and link it to the output components. Follow the guide on the [create format plugin page](./index.md#creating-the-basic-output-structure) to know how to create the basic structure.
+
+### Step 4: create your own custom mustache blocks
+
+Once your plugin has the basic mustache structure, you can provide extra mustache blocks to override parts of the page. Follow the [Override mustache blocks](./index.md#override-mustache-blocks) on the Creating a course format page to know how to do it.
+
+## Enabling course index in your format
+
+If your course format plugin uses a sections-activity structure it is possible to enable the course index. Add the course index in your format is as easy as overriding a method on your format base class:
+
+
+ View example
+
+
+import BaseCourseIndex from '!!raw-loader!./_examples/lib_course_index.php';
+export const BaseCourseIndexProps = {
+ examplePurpose: 'Format base class with course index enabled',
+ plugintype: 'format',
+ pluginname: 'pluginname',
+ filepath: '/lib.php',
+};
+
+
+
+
+It is important to note that the course index drawer is only available in Boost based themes, Classic based themes won't display it.
+
+See the course index section in the create format plugin page for more information.
+
+## Enabling reactive components
+
+Moodle 4.0 introduced a new reactive course editor for the frontend. However, the new modules are not compatible with the previous YUI ones. To prevent errors in the 3.11 formats the new libraries are opt-in, meaning plugins must adapt their code before using it.
+
+Step 1: add data attributes to the HTML elements
+The previous YUI editor uses CSS classes to identify sections, activities, and section headers. Nowadays, the use of CSS classes beyond styling is discouraged so the new library uses data attributes to identify the main course page elements.
+
+To adapt your plugin to the new editor you must add the proper data attributes. The following table explains the new selectors:
+
+| Concept | 3.11 equivalent | 4.0 data attributes |
+| ---- | ---- | ---- |
+| Section | `li.section#section-{SECTION.NUM}` | `data-for="section"` `data-id={SECTION.ID}` `data-number={SECTION.NUM}` |
+| Section header | `#sectionid-{SECTION.ID}-title` | `data-for="section_title"` `data-id={SECTION.ID}` `data-number={SECTION.NUM}` |
+| Course module item (activity) | `li.activity#module-{CM.ID}` | `data-for="cmitem"` `data-id={CM.ID}` |
+| Course sections list | `.course-content>ul` | `data-for="course_sectionlist"` `Section course modules list Li.section .content .section` `data-for="cmlist"`|
+| Course module action link | `a.cm-edit-action` | `data-action={ACTIONNAME}` `data-id={CM.ID}` |
+| Section action link | `.section_action_menu` | `data-action={ACTIONNAME}` `data-id={SECTION.ID}` |
+| Section info | `.section_availability` | `data-for="sectioninfo"` |
+
+### Step 2: enable supports components feature
+
+Once your plugin has all the necessary data attributes you can disable the old YUI editor and enable the new reactive one by adding this method to your plugin base class:
+
+
+ View example
+
+
+import BaseComponents from '!!raw-loader!./_examples/lib_components.php';
+export const BaseComponentsProps = {
+ examplePurpose: 'Format base class with components enabled',
+ plugintype: 'format',
+ pluginname: 'pluginname',
+ filepath: '/lib.php',
+};
+
+
{getExample(BaseComponentsProps, BaseComponents)}
+
+
+
+
+### Step 3: check the reactive components are enabled.
+
+In principle, if you create the basic mustache structure as described in the previous chapter the course editor should work as expected. However, to check if they are working properly:
+
+1. Enable developer debug level on your site and access a course as an administrator
+2. Wait for the page to load and find in the debug footer the "Reactive instances" section.
+3. Click on the button "CourseEditorXXXX" (where XXXX is the current course ID)
+4. Once the panel opens, click on the "Highlight OFF" button (it will change to "Highlight ON")
+5. Go to the top of the page and check the course content has several thick blue borders.
+
+If the border appears around all the course content elements (sections, section headers and activities) means that the course editor has registered all the course content as a reactive component.
+
+If you don't have a "CourseEditorXXXX" button or the content elements don't get highlighted means that, most probably, you override the main content mustache in a peculiar way and removed the JS initialization.
+
+To re-introduce the JS initialization you should edit the plugin's "content.mustache" file with the following:
+
+- Add `id="{{uniqid}}-course-format"` to the course content div element.
+- At the end of the template add:
+
+```handlebars
+{{#js}}
+require(['core_courseformat/local/content'], function(component) {
+ component.init('{{uniqid}}-course-format', {}, {{sectionreturn}});
+});
+{{/js}}
+```
+
+By doing that the content main reactive component should be initialized and if you enable the highlighting the content should have a blue border. If some if the inner elements does not have a blue border when highlight is ON means that some data attribute is missing. Check the step 1 of this chapter for more information.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/index.md b/versioned_docs/version-5.1/apis/plugintypes/index.md
new file mode 100644
index 0000000000..12aaf9f599
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/index.md
@@ -0,0 +1,301 @@
+---
+title: Plugin types
+tags:
+ - Plugins
+ - core
+ - API
+---
+
+Moodle is a powerful, and very extensible, Learning Management System. One of its core tenets is its extensibility, and this is primarily achieved through the development of plugins.
+
+A wider range of plugin types are available and these should be selected depending on your needs.
+
+## Things you can find in all plugins
+
+Although there are many different types of plugin, there are some things that work the same way in all plugin types. Please see the [Plugin files](./commonfiles) documentation that describes common files which are found in many plugin types.
+
+## Naming conventions
+
+Plugins typically have at least two names:
+
+- The friendly name, shown to users, and
+- A machine name used internally.
+
+The machine name must meet the following rules:
+
+- It must start with a lowercase latin letter
+- It may contain only lowercase latin letters, numbers, and underscores
+- It must end with a lowercase latin letter, or a number
+- The hyphen, and minus character `-` are not allowed
+
+If a plugin does not meet these requirements then it will be silently ignored.
+
+:::tip
+
+Plugin name validation takes place in `core_component::is_valid_plugin_name()` and the following regular expression is used:
+
+```
+/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/
+```
+
+:::
+
+:::danger Activity module exception
+
+The underscore character is not supported in activity modules for legacy reasons.
+
+:::
+
+
+
+| Plugin type | Component name ([Frankenstyle](/general/development/policies/codingstyle/frankenstyle)) | Moodle path | Description | Moodle versions |
+| --- | --- | --- | --- | --- |
+| [Activity modules](./mod/index.mdx) | mod | /mod | Activity modules are essential types of plugins in Moodle as they provide activities in courses. For example: Forum, Quiz and Assignment. | 1.0+ |
+| [Antivirus plugins](./antivirus/index.mdx) | antivirus | /lib/antivirus | Antivirus scanner plugins provide functionality for virus scanning user uploaded files using third-party virus scanning tools in Moodle. For example: ClamAV. | 3.1+ |
+| [Assignment submission plugins](./assign/submission.md) | assignsubmission | /mod/assign/submission | Different forms of assignment submissions | 2.3+ |
+| [Assignment feedback plugins](./assign/feedback.md) | assignfeedback | /mod/assign/feedback | Different forms of assignment feedbacks | 2.3+ |
+| [Book tools](./mod_book/index.md) | booktool | /mod/book/tool | Small information-displays or tools that can be moved around pages | 2.1+ |
+| [Custom fields](./customfield/index.md) | customfield | /customfield/field | Custom field types, used in Custom course fields | 3.7+ |
+| [Database fields](./mod_data/fields.md) | datafield | /mod/data/field | Different types of data that may be added to the Database activity module | 1.6+ |
+| [Database presets](./mod_data/presets.md) | datapreset | /mod/data/preset | Pre-defined templates for the Database activity module | 1.6+ |
+| [LTI sources](https://docs.moodle.org/dev/External_tool_source) | ltisource | /mod/lti/source | LTI providers can be added to external tools easily through the external tools interface see [Documentation on External Tools](https://docs.moodle.org/en/External_tool). This type of plugin is specific to LTI providers that need a plugin that can register custom handlers to process LTI messages | 2.7+ |
+| [File Converters](./fileconverter/index.md) | fileconverter | /files/converter | Allow conversion between different types of user-submitted file. For example from .doc to PDF. | 3.2+ |
+| [LTI services](https://docs.moodle.org/dev/LTI_services) | ltiservice | /mod/lti/service | Allows the implementation of LTI services as described by the IMS LTI specification | 2.8+ |
+| [Machine learning backends](./mlbackend/index.md) | mlbackend | /lib/mlbackend | Prediction processors for analytics API | 3.4+ |
+| [Forum reports](./mod_forum/index.md) | forumreport | /mod/forum/report | Display various reports in the forum activity | 3.8+ |
+| [Quiz reports](https://docs.moodle.org/dev/Quiz_reports) | quiz | /mod/quiz/report | Display and analyse the results of quizzes, or just plug miscellaneous behaviour into the quiz module | 1.1+ |
+| [Quiz access rules](https://docs.moodle.org/dev/Quiz_access_rules) | quizaccess | /mod/quiz/accessrule | Add conditions to when or where quizzes can be attempted, for example only from some IP addresses, or student must enter a password first | 2.2+ |
+| [SCORM reports](https://docs.moodle.org/dev/SCORM_reports) | scormreport | /mod/scorm/report | Analysis of SCORM attempts | 2.2+ |
+| [Workshop grading strategies](https://docs.moodle.org/dev/Workshop_grading_strategies) | workshopform | /mod/workshop/form | Define the type of the grading form and implement the calculation of the grade for submission in the [Workshop](https://docs.moodle.org/dev/Workshop) module | 2.0+ |
+| [Workshop allocation methods](https://docs.moodle.org/dev/Workshop_allocation_methods) | workshopallocation | /mod/workshop/allocation | Define ways how submissions are assigned for assessment in the [Workshop](https://docs.moodle.org/dev/Workshop) module | 2.0+ |
+| [Workshop evaluation methods](https://docs.moodle.org/dev/Workshop_evaluation_methods) | workshopeval | /mod/workshop/eval | Implement the calculation of the grade for assessment (grading grade) in the [Workshop](https://docs.moodle.org/dev/Workshop) module | 2.0+ |
+| [Blocks](./blocks/index.md) | block | /blocks | Small information-displays or tools that can be moved around pages | 2.0+ |
+| [Question types](https://docs.moodle.org/dev/Question_types) | qtype | /question/type | Different types of question (for example multiple-choice, drag-and-drop) that can be used in quizzes and other activities | 1.6+ |
+| [Question behaviours](https://docs.moodle.org/dev/Question_behaviours) | qbehaviour | /question/behaviour | Control how student interact with questions during an attempt | 2.1+ |
+| [Question import/export formats](https://docs.moodle.org/dev/Question_formats) | qformat | /question/format | Import and export question definitions to/from the question bank | 1.6+ |
+| [Text filters](./filter/index.md) | filter | /filter | Automatically convert, highlight, and transmogrify text posted into Moodle. | 1.4+ |
+| [Editors](./../subsystems/editor/index.md) | editor | /lib/editor | Alternative text editors for editing content | 2.0+ |
+| [Atto editor plugins](./atto/index.md) | atto | /lib/editor/atto/plugins | Extra functionality for the Atto text editor | 2.7+ |
+| [Enrolment plugins](./enrol/index.md) | enrol | /enrol | Ways to control who is enrolled in courses | 2.0+ |
+| [Authentication plugins](https://docs.moodle.org/dev/Authentication_plugins) | auth | /auth | Allows connection to external sources of authentication | 2.0+ |
+| [Admin tools](/general/projects/api/admin-tools) | tool | /admin/tool | Provides utility scripts useful for various site administration and maintenance tasks | 2.2+ |
+| [Log stores](./logstore/index.md) | logstore | /admin/tool/log/store | Event logs storage back-ends | 2.7+ |
+| [Availability conditions](./availability/index.md) | availability | /availability/condition | Conditions to restrict user access to activities and sections. | 2.7+ |
+| [Calendar types](https://docs.moodle.org/dev/Calendar_types) | calendartype | /calendar/type | Defines how dates are displayed throughout Moodle | 2.6+ |
+| [Messaging consumers](https://docs.moodle.org/dev/Messaging_consumers) | message | /message/output | Represent various targets where messages and notifications can be sent to (email, sms, jabber, ...) | 2.0+ |
+| [Course formats](./format/index.md) | format | /course/format | Different ways of laying out the activities and blocks in a course | 1.3+ |
+| [Data formats](https://docs.moodle.org/dev/Data_formats) | dataformat | /dataformat | Formats for data exporting and downloading | 3.1+ |
+| [User profile fields](https://docs.moodle.org/dev/User_profile_fields) | profilefield | /user/profile/field | Add new types of data to user profiles | 1.9+ |
+| [Reports](https://docs.moodle.org/dev/Reports) | report | /report | Provides useful views of data in a Moodle site for admins and teachers | 2.2+ |
+| [Course reports](https://docs.moodle.org/dev/Course_reports) | coursereport | /course/report | Reports of activity within the course | Up to 2.1 (for 2.2+ see [Reports](https://docs.moodle.org/dev/Reports)) |
+| [Gradebook export](https://docs.moodle.org/dev/Gradebook_export) | gradeexport | /grade/export | Export grades in various formats | 1.9+ |
+| [Gradebook import](https://docs.moodle.org/dev/Gradebook_import) | gradeimport | /grade/import | Import grades in various formats | 1.9+ |
+| [Gradebook reports](https://docs.moodle.org/dev/Gradebook_reports) | gradereport | /grade/report | Display/edit grades in various layouts and reports | 1.9+ |
+| [Advanced grading methods](https://docs.moodle.org/dev/Grading_methods) | gradingform | /grade/grading/form | Interfaces for actually performing grading in activity modules (for example Rubrics) | 2.2+ |
+| [MNet services](https://docs.moodle.org/dev/MNet_services) | mnetservice | /mnet/service | Allows to implement remote services for the [MNet](https://docs.moodle.org/dev/MNet) environment (deprecated, use web services instead) | 2.0+ |
+| [Webservice protocols](https://docs.moodle.org/dev/Webservice_protocols) | webservice | /webservice | Define new protocols for web service communication (such as SOAP, XML-RPC, JSON, REST ...) | 2.0+ |
+| [Repository plugins](./repository/index.md) | repository | /repository | Connect to external sources of files to use in Moodle | 2.0+ |
+| [Portfolio plugins](https://docs.moodle.org/dev/Portfolio_plugins) | portfolio | /portfolio | Connect external portfolio services as destinations for users to store Moodle content | 1.9+ |
+| [Search engines](https://docs.moodle.org/dev/Search_engines) | search | /search/engine | Search engine backends to index Moodle's contents. | 3.1+ |
+| [Media players](https://docs.moodle.org/dev/Media_players) | media | /media/player | Pluggable media players | 3.2+ |
+| [Plagiarism plugins](https://docs.moodle.org/dev/Plagiarism_plugins) | plagiarism | /plagiarism | Define external services to process submitted files and content | 2.0+ |
+| [Cache store](https://docs.moodle.org/dev/Cache_store) | cachestore | /cache/stores | Cache storage back-ends. | 2.4+ |
+| [Cache locks](https://docs.moodle.org/dev/Cache_locks) | cachelock | /cache/locks | Cache lock implementations. | 2.4+ |
+| [Themes](https://docs.moodle.org/dev/Themes) | theme | /theme | Change the look of Moodle by changing the the HTML and the CSS. | 2.0+ |
+| [Local plugins](./local/index.mdx) | local | /local | Generic plugins for local customisations | 2.0+ |
+| [Content bank content types](https://docs.moodle.org/dev/Content_bank_content_types) | contenttype | /contentbank/contenttype | Content types to upload, create or edit in the content bank and use all over the Moodle site | 3.9+ |
+| [H5P libraries](https://docs.moodle.org/dev/H5P_libraries) | h5plib | /h5p/h5plib | Plugin type for the particular versions of the H5P integration library. | 3.9+ |
+| [Question bank plugins](./qbank/index.md) | qbank | /question/bank | Plugin type for extending question bank functionality. | 4.0+ |
+
+
+ Obtaining the list of plugin types known to your Moodle
+
+You can get an exact list of valid plugin types for your Moodle version using the following example:
+
+```php title="/plugintypes.php"
+get_plugin_types() as $type => $dir) {
+ $dir = substr($dir, strlen($CFG->dirroot));
+ printf(
+ "%-20s %-50s %s" . PHP_EOL,
+ $type,
+ $pluginman->plugintype_name_plural($type),
+ $dir)
+ ;
+}
+```
+
+
+
+## Plugin type deprecation
+
+
+
+When a plugin or subplugin type is no longer needed or is replaced by another plugin type, it should be deprecated.
+Using `components.json` or `subplugins.json` plugin types and subplugin types, respectively, can be marked as deprecated.
+
+The process for plugin and subplugin type deprecation differs slightly to the normal [Deprecation](/general/development/policies/deprecation) process.
+Unlike with code deprecation, where the deprecated class or method is usually expected to remain functional during the deprecation window, deprecated plugin/subplugin types are treated as end-of-life as soon as they are deprecated.
+
+Once deprecated, core will exclude plugins of the respective plugin type when performing common core-plugin communication, such as with hooks, callbacks, events, and-so-on.
+In the case of subplugins, the subplugin owner (the component which the subplugin belongs to), **must** have been updated to remove or replace all references to the subplugins before the time of deprecation.
+
+Class autoloading and string resolution is still supported during the deprecation window, to assist with any plugin migration scripts that may be required.
+
+:::info limitations
+
+Whilst both plugin and subplugin types can be deprecated, only those plugin types which do _not_ support subplugins can be deprecated.
+
+:::
+
+### Deprecation process
+
+Deprecation follows a 3 stage process:
+
+1. The plugin/subplugin type is marked as deprecated (a core version bump is also required).
+2. The plugin/subplugin type is marked as deleted (a core version bump is also required).
+3. Final removal of the plugin/subplugin type from the respective config file.
+
+#### First stage deprecation
+
+During first stage deprecation, plugins of the respective type may remain installed, but are deemed end-of-life.
+
+This stage gives administrators time to remove the affected plugins from the site, or migrate them to their replacement plugins.
+
+#### Second stage deprecation
+
+The second stage deprecation is the deletion phase.
+
+If any affected plugins are still present (that is any which have not been uninstalled or migrated yet), the site upgrade will be blocked.
+
+These plugins **must** be removed before continuing with site upgrade.
+
+#### Final deprecation
+
+In the final deprecation stage the relevant configuration changes supporting first and second stage deprecation can be removed from the respective config files. This removes the last reference to these plugin/subplugin types.
+
+### Deprecating a plugin type
+
+The first phase of plugin type deprecation involves describing the plugin in the `deprecatedplugintypes` configuration in `lib/components.json`. The plugin type must also be removed from the `plugintypes` object.
+
+The second phase of plugin type deprecation involves moving the entry from the `deprecatedplugintypes` object to the `deletedplugintypes` object.
+
+:::info Remember
+
+Don't forget to increment the core version number when marking a plugin/subplugin type for either deprecation or deletion. A version bump isn't needed for final removal.
+
+:::
+
+:::tip Example of plugin type deprecation config values
+
+To mark a plugin type as deprecated in `components.json`, the plugin type should be removed from the `plugintypes` object, and added to a new `deprecatedplugintypes` object.
+
+```json title="lib/components.json demonstrating first stage deprecation of a plugin type"
+{
+ "plugintypes": {
+ ...
+ },
+ "subsystems": {
+ ...
+ },
+ "deprecatedplugintypes": {
+ "aiplacement": "ai/placement"
+ }
+}
+```
+
+To mark a plugin type as deleted in `components.json`, the plugin type should be removed from the `deprecatedplugintypes` object, and added to a new `deletedplugintypes` object. If the `deprecatedplugintypes` object is now empty, it may be removed entirely from config.
+
+```json title="lib/components.json demonstrating second stage deprecation (deletion) of a plugin type"
+{
+ "plugintypes": {
+ ...
+ },
+ "subsystems": {
+ ...
+ },
+ "deletedplugintypes": {
+ "aiplacement": "ai/placement"
+ }
+}
+```
+
+Third stage deprecation just removes the plugin type from the `deletedplugintypes` object. If the `deletedplugintypes` object is now empty, it may be removed entirely from config.
+
+```json title="lib/components.json demonstrating final stage deprecation of a plugin type. The process is the same for subplugin types."
+{
+ "plugintypes": {
+ ...
+ },
+ "subsystems": {
+ ...
+ },
+}
+```
+
+:::
+
+### Deprecating a subplugin type
+
+To mark a subplugin type as deprecated, edit the component's `subplugins.json` file, remove the subplugin type from the `subplugintypes` object and add it to the `deprecatedsubplugintypes` object. The mark a subplugin type for stage 2 deprecation (deletion), edit the same file and move the subplugin type from the `deprecatedsubplugintypes` object to the `deletedsubplugintypes` object.
+
+Following deletion, the plugin/subplugin type can be removed from the respective JSON entirely.
+
+:::info Remember
+
+Don't forget to increment the core version number when marking a plugin/subplugin type for either deprecation or deletion. A version bump isn't needed for final removal.
+
+:::
+
+:::tip Example of subplugin type deprecation config values
+
+To mark a subplugin type as deprecated in a component's `subplugins.json`, the subplugin type should be removed from the `subplugintypes` object, and added to a new `deprecatedsubplugintypes` object.
+
+```json title="mod/lti/db/subplugins.json demonstrating first stage deprecation of a subplugin type"
+{
+ "subplugintypes": {
+ "ltiservice": "service"
+ },
+ "deprecatedsubplugintypes": {
+ "ltisource": "source"
+ }
+}
+```
+
+To mark a subplugin type as deleted in a component's `subplugins.json`, the subplugin type should be removed from the `deprecatedsubplugintypes` object, and added to a new `deletedsubplugintypes` object. If the `deprecatedsubplugintypes` object is now empty, it may be removed entirely from config.
+
+```json title="mod/lti/db/subplugins.json demonstrating second stage deprecation (deletion) of a subplugin type"
+{
+ "subplugintypes": {
+ "ltiservice": "service"
+ },
+ "deletedsubplugintypes": {
+ "ltisource": "source"
+ }
+}
+```
+
+Third stage deprecation just removes the subplugin type from the `deletedsubplugintypes` object. If this object is then empty, it may be removed entirely from config.
+
+```json title="mod/lti/db/subplugins.json demonstrating final stage deprecation of a subplugin type."
+{
+ "subplugintypes": {
+ "ltiservice": "service"
+ }
+}
+```
+
+:::
+
+## See also
+
+- [Guidelines for contributing code](https://docs.moodle.org/dev/Guidelines_for_contributed_code)
+- [Core APIs](../../apis.md)
+- [Frankenstyle](/general/development/policies/codingstyle/frankenstyle)
+- [Moodle Plugins directory](http://moodle.org/plugins)
+- [Tutorial](https://docs.moodle.org/dev/Tutorial) to help you learn how to write plugins for Moodle from start to finish, while showing you how to navigate the most important developer documentation along the way.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/local/index.mdx b/versioned_docs/version-5.1/apis/plugintypes/local/index.mdx
new file mode 100644
index 0000000000..403785d5f8
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/local/index.mdx
@@ -0,0 +1,152 @@
+---
+title: Local plugins
+tags:
+ - Plugins
+---
+
+import {
+ getExample,
+ getFileNameWithComponentPath,
+ CodeBlock,
+} from '@site/src/moodleBridge';
+
+import {
+ Lib,
+ SettingsPHP,
+} from '../../_files';
+
+The recommended way to add new functionality to Moodle is to create a new standard plugin (for example, activity, block, authentication, enrol). The `local` plugin-type is mostly suitable for things that do not fit into these standard plugin types.
+
+Local plugins are used in cases when no standard plugin is suitable. Examples of these situations include:
+
+- event consumers communicating with external systems
+- custom definitions of web services and external functions
+- applications that extend moodle at the system level (for example hub server, amos server)
+- custom admin settings
+- extending the navigation block with custom menus
+- new database tables used in core hacks (**strongly discouraged**)
+- new capability definitions used in core hacks (**strongly discouraged**)
+
+## List of differences from normal plugins:
+
+Local plugins have several important differences from the standard plugin types, including:
+
+- they are always executed last during install, and upgrade. This is guaranteed by their order in `get_plugin_types()`.
+- they are _expected_ to use event handlers. Event subscriptions are intended for communication from core to plugins only, making local plugins the ideal candidate for them.
+- they can add admin settings to any settings page. They are loaded last when constructing admin tree to enable this.
+- they _do not need_ to have any UI. Other plugin types are usually visible somewhere within the interface.
+
+## File structure
+
+Local plugins support the [standard plugin files](../commonfiles) supported by other plugin types.
+
+## Examples
+
+The following examples show some ways in which you can use a local plugin.
+
+### Adding an element to the settings menu
+
+A local plugin can extend or modify the settings navigation by defining a function named `local_[pluginname]_extend_settings_navigation` in its `lib.php`. This is called when Moodle builds the settings block. For example:
+
+export const settingsNavigationExample = `
+function local_[pluginname]_extend_settings_navigation($settingsnav, $context) {
+ global $CFG, $PAGE;\n
+\
+ // Only add this settings item on non-site course pages.
+ if (!$PAGE->course or $PAGE->course->id == 1) {
+ return;
+ }\n
+\
+ // Only let users with the appropriate capability see this settings item.
+ if (!has_capability('moodle/backup:backupcourse', context_course::instance($PAGE->course->id))) {
+ return;
+ }\n
+\
+ if ($settingnode = $settingsnav->find('courseadmin', navigation_node::TYPE_COURSE)) {
+ $strfoo = get_string('foo', 'local_[pluginname]');
+ $url = new moodle_url('/local/[pluginname]/foo.php', array('id' => $PAGE->course->id));
+ $foonode = navigation_node::create(
+ $strfoo,
+ $url,
+ navigation_node::NODETYPE_LEAF,
+ '[pluginname]',
+ '[pluginname]',
+ new pix_icon('t/addcontact', $strfoo)
+ );
+ if ($PAGE->url->compare($url, URL_MATCH_BASE)) {
+ $foonode->make_active();
+ }
+ $settingnode->add_node($foonode);
+ }
+}
+`;
+
+
+
+### Removing the "Site Home" link from the navigation menu
+
+A plugin can modify existing navigation, and settings navigation, components from within the `local_[pluginname]_extend_navigation()` function, for example:
+
+export const modifyNavigationExample = `
+function local_[pluginname]_extend_navigation(global_navigation $navigation) {
+ if ($home = $navigation->find('home', global_navigation::TYPE_SETTING)) {
+ $home->remove();
+ }
+}
+`;
+
+
+
+### Adding Site Wide Settings For Your Local Plugin
+
+export const siteWideSettingsExample = `
+// Ensure the configurations for this site are set
+if ($hassiteconfig) {\n
+\
+ // Create the new settings page
+ // - in a local plugin this is not defined as standard, so normal $settings->methods will throw an error as
+ // $settings will be null
+ $settings = new admin_settingpage('local_[pluginname]', 'Your Settings Page Title');\n
+\
+ // Create
+ $ADMIN->add('localplugins', $settings);\n
+\
+ // Add a setting field to the settings for this page
+ $settings->add(new admin_setting_configtext(
+ // This is the reference you will use to your configuration
+ 'local_[pluginname]/apikey',\n
+\
+ // This is the friendly title for the config, which will be displayed
+ 'External API: Key',\n
+\
+ // This is helper text for this config field
+ 'This is the key used to access the External API',\n
+\
+ // This is the default value
+ 'No Key Defined',\n
+\
+ // This is the type of Parameter this config is
+ PARAM_TEXT
+ ));
+}
+`;
+
+
diff --git a/versioned_docs/version-5.1/apis/plugintypes/logstore/index.md b/versioned_docs/version-5.1/apis/plugintypes/logstore/index.md
new file mode 100644
index 0000000000..3449ea77ac
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/logstore/index.md
@@ -0,0 +1,14 @@
+---
+title: Logstore plugins
+tags:
+ - Logging
+ - Events
+ - logstore
+ - Plugin
+ - Plugintype
+documentationDraft: true
+---
+
+Moodle supports the ability to define a custom log storage system using the `logstore` plugin type. This hasn't been documented yet - perhaps you are able to help us.
+
+
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mlbackend/index.md b/versioned_docs/version-5.1/apis/plugintypes/mlbackend/index.md
new file mode 100644
index 0000000000..27de3c670a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mlbackend/index.md
@@ -0,0 +1,212 @@
+---
+title: Machine learning backends
+tags:
+ - Analytics
+ - API
+ - plugintype
+---
+
+
+
+
+Machine learning backends process the datasets generated from the indicators and targets calculated by the [Analytics API](../../subsystems/analytics/index.md). They are used for machine learning training, prediction and models evaluation.
+
+:::tip
+
+We strongly recommend that you read the [Analytics API](../../subsystems/analytics/index.md) documentation to help understand the core concepts, how they are implemented in Moodle, and how machine learning backend plugins fit into the analytics API.
+
+:::
+
+The communication between machine learning backends and Moodle is through files because the code that will process the dataset can be written in PHP, in Python, in other languages or even use cloud services. This needs to be scalable so they are expected to be able to manage big files and train algorithms reading input files in batches if necessary.
+
+## Backends included in Moodle core
+
+### PHP
+
+The **PHP backend** is the default predictions processor as it is written in PHP and does not have any external dependencies. It is using logistic regression.
+
+### Python
+
+The **Python backend** requires *python* binary (either python 2 or python 3) and [moodlemlbackend python package](https://pypi.python.org/pypi?name=moodlemlbackend&version=0.0.5&:action=display) which is maintained by Moodle HQ.
+
+The Python version and libraries versions used are **very important**. We recommend using Python 3.7 for mlbackend 3.x versions.
+
+The Python backend is based on [Google's tensorflow library](https://www.tensorflow.org/) and uses a feed-forward neural network with 1 single hidden layer.
+
+The *moodlemlbackend* package does store model performance information that can be visualised using [tensorboard](https://www.tensorflow.org/get_started/summaries_and_tensorboard). Information generated during models evaluation is available through the models management page, under each model *Actions > Log* menu.
+
+:::tip
+
+We recommend use of the **Python** backend as it is able to predict more accurately than the PHP backend and it is faster.
+
+:::
+
+:::info
+
+You can [view the source](https://github.com/moodlehq/moodle-mlbackend-python) of the _moodlemlbackend_ library that Moodle uses.
+
+:::
+
+## File structure
+
+Machine learning backends are located in the `lib/mlbackend` directory.
+
+Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+
+ View an example directory layout for the `mlbackend_python` plugin.
+
+```console
+lib/mlbackend/python
+├── classes
+│ ├── privacy
+│ │ └── provider.php
+│ └── processor.php
+├── lang
+│ └── en
+│ └── mlbackend_python.php
+├── phpunit.xml
+├── settings.php
+├── tests
+│ └── processor_test.php
+├── upgrade.txt
+└── version.php
+```
+
+
+
+Some of the important files for the mlbackend plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+## Interfaces
+
+A summary of these interfaces purpose:
+
+- Evaluate a provided prediction model
+- Train machine learning algorithms with the existing site data
+- Predict targets based on previously trained algorithms
+
+### Predictor
+
+This is the basic interface to be implemented by machine learning backends. Two main types are, *classifiers* and *regressors*. We provide the *Regressor* interface but it is not currently implemented by core Machine learning backends. Both of these are supervised algorithms. Each type includes methods to train, predict and evaluate datasets.
+
+You can use **is_ready** to check that the backend is available.
+
+```php
+/**
+ * Is it ready to predict?
+ *
+ * @return bool
+ */
+public function is_ready();
+```
+
+**clear_model** and **delete_output_dir** purpose is to clean up stuff created by the machine learning backend.
+
+```php
+/**
+ * Delete all stored information of the current model id.
+ *
+ * This method is called when there are important changes to a model,
+ * all previous training algorithms using that version of the model
+ * should be deleted.
+ *
+ * @param string $uniqueid The site model unique id string
+ * @param string $modelversionoutputdir The output dir of this model version
+ * @return null
+ */
+public function clear_model($uniqueid, $modelversionoutputdir);
+
+/**
+ * Delete the output directory.
+ *
+ * This method is called when a model is completely deleted.
+ *
+ * @param string $modeloutputdir The model directory id (parent of all model versions subdirectories).
+ * @param string $uniqueid The site model unique id string
+ * @return null
+ */
+public function delete_output_dir($modeloutputdir, $uniqueid);
+```
+
+### Classifier
+
+A [classifier](https://en.wikipedia.org/wiki/Statistical_classification) sorts input into two or more categories, based on analysis of the indicators. This is frequently used in binary predictions, e.g. course completion vs. dropout. This machine learning algorithm is "supervised": It requires a training data set of elements whose classification is known (e.g. courses in the past with a clear definition of whether the student has dropped out or not). This is an interface to be implemented by machine learning backends that support classification. It extends the *Predictor* interface.
+
+Both these methods and *Predictor* methods should be implemented.
+
+```php
+/**
+ * Train this processor classification model using the provided supervised learning dataset.
+ *
+ * @param string $uniqueid
+ * @param \stored_file $dataset
+ * @param string $outputdir
+ * @return \stdClass
+ */
+public function train_classification($uniqueid, \stored_file $dataset, $outputdir);
+
+/**
+ * Classifies the provided dataset samples.
+ *
+ * @param string $uniqueid
+ * @param \stored_file $dataset
+ * @param string $outputdir
+ * @return \stdClass
+ */
+public function classify($uniqueid, \stored_file $dataset, $outputdir);
+
+/**
+ * Evaluates this processor classification model using the provided supervised learning dataset.
+ *
+ * @param string $uniqueid
+ * @param float $maxdeviation
+ * @param int $niterations
+ * @param \stored_file $dataset
+ * @param string $outputdir
+ * @param string $trainedmodeldir
+ * @return \stdClass
+ */
+public function evaluate_classification($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir);
+```
+
+### Regressor
+
+A [regressor](https://en.wikipedia.org/wiki/Regression_analysis) predicts the value of an outcome (or dependent) variable based on analysis of the indicators. This value is linear, such as a final grade in a course or the likelihood a student is to pass a course. This machine learning algorithm is "supervised": It requires a training data set of elements whose classification is known (e.g. courses in the past with a clear definition of whether the student has dropped out or not). This is an interface to be implemented by machine learning backends that support regression. It extends *Predictor* interface.
+
+Both these methods and *Predictor* methods should be implemented.
+
+```php
+/**
+ * Train this processor regression model using the provided supervised learning dataset.
+ *
+ * @param string $uniqueid
+ * @param \stored_file $dataset
+ * @param string $outputdir
+ * @return \stdClass
+ */
+public function train_regression($uniqueid, \stored_file $dataset, $outputdir);
+
+/**
+ * Estimates linear values for the provided dataset samples.
+ *
+ * @param string $uniqueid
+ * @param \stored_file $dataset
+ * @param mixed $outputdir
+ * @return void
+ */
+public function estimate($uniqueid, \stored_file $dataset, $outputdir);
+
+
+/**
+ * Evaluates this processor regression model using the provided supervised learning dataset.
+ *
+ * @param string $uniqueid
+ * @param float $maxdeviation
+ * @param int $niterations
+ * @param \stored_file $dataset
+ * @param string $outputdir
+ * @param string $trainedmodeldir
+ * @return \stdClass
+ */
+public function evaluate_regression($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir);
+```
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png b/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png
new file mode 100644
index 0000000000..2d2b39bc1a
Binary files /dev/null and b/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png differ
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png b/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png
new file mode 100644
index 0000000000..0b6147facc
Binary files /dev/null and b/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png differ
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png b/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png
new file mode 100644
index 0000000000..b3aeb20f1d
Binary files /dev/null and b/versioned_docs/version-5.1/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png differ
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/access_description.md b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/access_description.md
new file mode 100644
index 0000000000..7fc9b95250
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/access_description.md
@@ -0,0 +1,20 @@
+
+For activities the following capabilities are _required_:
+
+- `mod/[modname]:addinstance`: Controls whether a user may create a new instance of the activity
+- `mod/[modname]:view`: Controls whether a user may view an instance of the activity
+
+The example below shows the recommended configuration for the `addinstance` and `view` capabilities.
+
+This configuration will allow:
+
+- editing teachers and managers to create new instances, but not non-editing teachers.
+- all roles to view the activity.
+
+:::important
+
+Granting the view capability to archetypes like `guest` does not allow any user to view all activities. Users are still subject to standard access controls like course enrolment.
+
+:::
+
+For further information on what each attribute in that capabilities array means visit [NEWMODULE Adding capabilities](https://docs.moodle.org/dev/NEWMODULE_Adding_capabilities).
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/index-php.mdx b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/index-php.mdx
new file mode 100644
index 0000000000..b65d23bcb1
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/index-php.mdx
@@ -0,0 +1,4 @@
+
+The `index.php` should be used to list all instances of an activity that the current user has access to in the specified course.
+
+The [`activityoverviewbase`](../courseoverview.md) class provides a static method to redirect the `mod/PLUGINNAME/index.php` page to the **Activities** page. This method should be called in the `index.php` file of the activity plugin.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/index-php.tsx b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/index-php.tsx
new file mode 100644
index 0000000000..16c5a87463
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/index-php.tsx
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { Props } from '../../../../_utils';
+import DefaultDescription from './index-php.mdx';
+
+const defaultExample = `require_once('../../config.php');
+
+$courseid = required_param('id', PARAM_INT);
+
+\core_courseformat\activityoverviewbase::redirect_to_overview_page($courseid, '[modname]');
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/install_description.md b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/install_description.md
new file mode 100644
index 0000000000..877ccac079
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/install_description.md
@@ -0,0 +1,12 @@
+
+Moodle requires that you create a table for your plugin whose name exactly matches the plugin name. For example, the `certificate` activity module _must_ have a database table named `certificate`. Certain fields within this table are
+also _required_:
+
+| Field name | Properties | Keys / Indexes | Notes |
+| --- | --- | --- | --- |
+| `id` | `INT(10), auto sequence` | primary key for the table | |
+| `course` | `INT(10)` | foreign key to the `course` table | |
+| `name` | `CHAR(255)` | | Holds the user-specified name of the activity instance |
+| `timemodified` | `INT(10)` | | The timestamp of when the activity was last modified |
+| `intro` | `TEXT` | | A standard field to hold the user-defined activity description (see `FEATURE_MOD_INTRO`) |
+| `introformat` | `INT(4)` | | A standard field to hold the format of the field |
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/lib_description.md b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/lib_description.md
new file mode 100644
index 0000000000..daa8c49786
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/lib_description.md
@@ -0,0 +1,108 @@
+
+For an Activity, you _must_ define the following three functions, which are described below:
+
+```php title="mod/[modname]/lib.php"
+function [modname]_add_instance($instancedata, $mform = null): int;
+function [modname]_update_instance($instancedata, $mform): bool;
+function [modname]_delete_instance($id): bool;
+```
+
+- The `[modname]_add_instance()` function is called when the activity creation form is submitted. This function is only called when adding an activity and should contain any logic required to add the activity.
+- The `[modname]_update_instance()` function is called when the activity editing form is submitted.
+- The `[modname]_delete_instance()` function is called when the activity deletion is confirmed. It is responsible for removing all data associated with the instance.
+
+:::note
+
+The `lib.php` file is one of the older parts of Moodle and functionality is gradually being migrated to class-based function calls.
+
+:::
+
+##### Activity module support functions
+
+Activity modules can implement a global function to provide additional information about the module
+features. These functions are optional and can be used to provide additional features or to modify the behaviour of the activity module.
+
+```php title="mod/[modname]/lib.php"
+function [modname]_supports(string $feature): bool|string|null;
+```
+
+The function `[modname]_supports` is used to check if the activity module supports a particular feature. The function should return `true` if the feature is supported, `false` if it is not supported, `null` if the feature is unknown, or string for the module purpose for some features.
+
+Each feature is identified by a constant, which is defined in the `lib
+/moodlelib.php` file. Some of the available features are:
+
+- `FEATURE_GROUPS` and `FEATURE_GROUPINGS`: The activity module supports groups and groupings.
+- `FEATURE_SHOW_DESCRIPTION`: The activity module supports showing the description on the course page.
+- `FEATURE_QUICKCREATE`: The activity `[modname]_add_instance()` function is able to create an instance without showing a form using the default settings. It is used by the `core_courseformat_new_module` webservice to know which activities are compatible. If this feature is supported, the activity module should provide a `quickcreatename` string in the language file that will be used as the name of the instance created.
+- `FEATURE_COMPLETION`: The activity module supports activity completion. For now this feature only affects the bulk completion settings. However, in the future ([MDL-83027](https://moodle.atlassian.net/browse/MDL-83027)) activities can set to false to disable all completion settings.
+
+
+ View example
+
+
+
+:::tip
+
+To have your Activity plugin classified in the right Activity category, you must define the function `[modname]_supports` and add the `FEATURE_MOD_PURPOSE` constant:
+
+
+ View example
+
+
+
+The available activity purposes for this feature are:
+
+- **Administration** (`MOD_PURPOSE_ADMINISTRATION`)
+Tools for course administration, such as attendance tracking or appointment scheduling.
+- **Assessment** (`MOD_PURPOSE_ASSESSMENT`)
+Activities that allow the evaluation and measurement of student understanding and performance.
+ - Core activities in this category: Assignment, Quiz, Workshop.
+- **Collaboration** (`MOD_PURPOSE_COLLABORATION`)
+Tools for collaborative learning that encourage knowledge sharing, discussions, and teamwork.
+ - Core activities in this category: Database, Forum, Glossary, Wiki.
+- **Communication** (`MOD_PURPOSE_COMMUNICATION`)
+Activities that facilitate real-time communication, asynchronous interaction, and feedback collection.
+ - Core activities in this category: BigBlueButton, Chat, Choice, Feedback, Survey.
+- **Interactive content** (`MOD_PURPOSE_INTERACTIVECONTENT`)
+Engaging interactive activities that encourage active learner participation.
+ - Core activities in this category: H5P, IMS package, Lesson, SCORM package.
+- **Resources** (`MOD_PURPOSE_CONTENT`)
+Activities and tools to organise and display course materials like documents, web links, and multimedia.
+ - Core activities in this category: Book, File, Folder, Page, URL, Text and media area.
+- **Other** (`MOD_PURPOSE_OTHER`)
+Other types of activities.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.mdx b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.mdx
new file mode 100644
index 0000000000..95055149e0
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.mdx
@@ -0,0 +1,16 @@
+
+This file is used when adding/editing a module to a course. It contains the elements that will be displayed on the form responsible for creating/installing an instance of your module. The class in the file should be called `mod_[modname]_mod_form`.
+
+:::warning
+
+The `mod_[modname]_mod_form` is a current exception to the class autoloading rules.
+
+This will be addressed in [MDL-74472](https://moodle.atlassian.net/browse/MDL-74472).
+
+:::
+
+:::info
+
+The following example gives a sample implementation of the creation form. It does **not** contain the full file.
+
+:::
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.tsx b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.tsx
new file mode 100644
index 0000000000..71c4745642
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.tsx
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { Props } from '../../../../_utils';
+import DefaultDescription from './mod_form-php.mdx';
+
+const defaultExample = `require_once($CFG->dirroot.'/course/moodleform_mod.php');
+require_once($CFG->dirroot.'/mod/certificate/lib.php');
+
+class mod_certificate_mod_form extends moodleform_mod {
+
+ function definition() {
+ global $CFG, $DB, $OUTPUT;
+
+ $mform =& $this->_form;
+
+ // Section header title according to language file.
+ $mform->addElement('header', 'general', get_string('general', 'certificate'));
+
+ // Add a text input for the name of the certificate.
+ $mform->addElement('text', 'name', get_string('certificatename', 'certificate'), ['size' => '64']);
+ $mform->setType('name', PARAM_TEXT);
+ $mform->addRule('name', null, 'required', null, 'client');
+
+ // Add a select menu for the 'use code' setting.
+ $ynoptions = [
+ 0 => get_string('no'),
+ 1 => get_string('yes'),
+ ];
+ $mform->addElement('select', 'usecode', get_string('usecode', 'certificate'), $ynoptions);
+ $mform->setDefault('usecode', 0);
+ $mform->addHelpButton('usecode', 'usecode', 'certificate');
+
+ // Standard Moodle course module elements (course, category, etc.).
+ $this->standard_coursemodule_elements();
+
+ // Standard Moodle form buttons.
+ $this->add_action_buttons();
+ }
+
+ function validation($data, $files) {
+ $errors = array();
+
+ // Validate the 'name' field.
+ if (empty($data['name'])) {
+ $errors['name'] = get_string('errornoname', 'certificate');
+ }
+
+ return $errors;
+ }
+
+ function data_preprocessing(&$default_values) {
+ // Set default values for the form fields.
+ $default_values['name'] = 'Default Certificate Name';
+ $default_values['usecode'] = 1;
+
+ }
+
+ function definition_after_data() {
+ $mform = $this->_form;
+ $data = $this->get_data();
+
+ // Disable the 'name' field if 'usecode' is set to 1.
+ if ($data && !empty($data->usecode)) {
+ $mform->disabledIf('name', 'usecode', 'eq', 1);
+ }
+
+ }
+
+ function preprocess_data($data) {
+ // Modify the 'name' data before saving.
+ $data->name = strtoupper($data->name);
+
+ return $data;
+ }
+}
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.txt b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.txt
new file mode 100644
index 0000000000..046d87ffdb
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/mod_form-php.txt
@@ -0,0 +1,74 @@
+if (!defined('MOODLE_INTERNAL')) {
+ die('Direct access to this script is forbidden.'); // It must be included from a Moodle page
+}
+
+require_once($CFG->dirroot.'/course/moodleform_mod.php');
+require_once($CFG->dirroot.'/mod/certificate/lib.php');
+
+class mod_certificate_mod_form extends moodleform_mod {
+
+ function definition() {
+ global $CFG, $DB, $OUTPUT;
+
+ $mform =& $this->_form;
+
+ // Section header title according to language file.
+ $mform->addElement('header', 'general', get_string('general', 'certificate'));
+
+ // Add a text input for the name of the certificate.
+ $mform->addElement('text', 'name', get_string('certificatename', 'certificate'), ['size' => '64']);
+ $mform->setType('name', PARAM_TEXT);
+ $mform->addRule('name', null, 'required', null, 'client');
+
+ // Add a select menu for the 'use code' setting.
+ $ynoptions = [
+ 0 => get_string('no'),
+ 1 => get_string('yes'),
+ ];
+ $mform->addElement('select', 'usecode', get_string('usecode', 'certificate'), $ynoptions);
+ $mform->setDefault('usecode', 0);
+ $mform->addHelpButton('usecode', 'usecode', 'certificate');
+
+ // Standard Moodle course module elements (course, category, etc.).
+ $this->standard_coursemodule_elements();
+
+ // Standard Moodle form buttons.
+ $this->add_action_buttons();
+ }
+
+ function validation($data, $files) {
+ $errors = array();
+
+ // Validate the 'name' field.
+ if (empty($data['name'])) {
+ $errors['name'] = get_string('errornoname', 'certificate');
+ }
+
+ return $errors;
+ }
+
+ function data_preprocessing(&$default_values) {
+ // Set default values for the form fields.
+ $default_values['name'] = 'Default Certificate Name';
+ $default_values['usecode'] = 1;
+
+ }
+
+ function definition_after_data() {
+ $mform = $this->_form;
+ $data = $this->get_data();
+
+ // Disable the 'name' field if 'usecode' is set to 1.
+ if ($data && !empty($data->usecode)) {
+ $mform->disabledIf('name', 'usecode', 'eq', 1);
+ }
+
+ }
+
+ function preprocess_data($data) {
+ // Modify the 'name' data before saving.
+ $data->name = strtoupper($data->name);
+
+ return $data;
+ }
+}
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/view-php.mdx b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/view-php.mdx
new file mode 100644
index 0000000000..96567c5f4a
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/view-php.mdx
@@ -0,0 +1,2 @@
+
+Moodle will automatically generate links to view the activity using the `/view.php` page and passing in an `id` value. The `id` passed is the course module ID, which can be used to fetch all remaining data for the activity instance.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/_files/view-php.tsx b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/view-php.tsx
new file mode 100644
index 0000000000..28a608e8c3
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/_files/view-php.tsx
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Moodle Pty Ltd.
+ *
+ * Moodle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Moodle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Moodle. If not, see .
+ */
+import React from 'react';
+import { ComponentFileSummary } from '../../../../_utils';
+import type { Props } from '../../../../_utils';
+import DefaultDescription from './view-php.mdx';
+
+const defaultExample = `
+require('../../config.php');
+
+$id = required_param('id', PARAM_INT);
+[$course, $cm] = get_course_and_cm_from_cmid($id, '[modname]');
+$instance = $DB->get_record('[modname]', ['id'=> $cm->instance], '*', MUST_EXIST);
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/activitymodule.md b/versioned_docs/version-5.1/apis/plugintypes/mod/activitymodule.md
new file mode 100644
index 0000000000..b57ebcc4e2
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/activitymodule.md
@@ -0,0 +1,77 @@
+---
+title: Activity chooser
+tags:
+- MUA Project
+documentationDraft: true
+---
+
+:::caution
+
+This documentation is from the project kick-off and has not been updated since the project completed.
+
+:::
+
+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.
+
+[MDL-57828](https://moodle.atlassian.net/browse/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](https://moodle.atlassian.net/browse/MDL-61511)) to tackle the same issue.
+
+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.
+
+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.
+
+## Features
+
+The following are changes that we are planning on making in this project. We have a demo that can be viewed and interacted with.
+[Invisio mockup of the activity chooser](https://projects.invisionapp.com/share/SVSREPYNBYG#/screens/388682478).
+
+Our current work can be viewed at [activity chooser prototype](https://activitychooser.prototype.moodledemo.net/). Please take a look.
+
+### Larger display area
+
+The activity chooser will be wider and have the activities in a grid format. This allows for more activities to be seen at once.
+
+### Activities and resources are now merged
+
+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.
+
+### Starred / Favourites tab
+
+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.
+
+
+
+### Recommended tab
+
+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.
+
+
+
+### Smart search bar
+
+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.
+
+### Other activity types
+
+Other activity types such as LTI will be able to be added to the activity chooser for the user to select.
+
+### Activity information hidden
+
+The information about an activity will be accessible through the 'i' 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.
+
+
+
+## Third party plugin developers
+
+Course module plugins can add items to the activity chooser by implementing the `{plugin}_get_course_content_items()` callback in their plugin lib (lib.php). This callback replaces the now deprecated `{plugin}_get_shortcuts()` method.
+
+In order for activity starring and recommendations to work, each content_item has an ID which is subject to some additional rules. Each ID:
+
+- Must be unique to your component.
+- Must not change.
+- Must be of type integer.
+
+See `lti_get_course_content_items()` for an example implementation in core.
+
+Additionally, for recommendations to be made, plugins must implement the `{plugin}_get_all_content_items()` callback in their lib.php. This method must return a list of all content items that can be added across all courses.
+
+Developers who are currently using the deprecated `{plugin}_get_shortcuts()` 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.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/courseoverview.md b/versioned_docs/version-5.1/apis/plugintypes/mod/courseoverview.md
new file mode 100644
index 0000000000..8ae2e1f920
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/courseoverview.md
@@ -0,0 +1,311 @@
+---
+title: Course overview integration
+tags:
+- MUA Project
+- API
+- Module
+- Activity
+- mod
+- Course
+---
+
+## Summary
+
+
+
+The course overview table displays a summary of all activities in a course, along with relevant information such as due dates, submission state, completion status, etc. Both students and teachers can access the course overview by clicking the "Activities" tab in the course navigation. The new page replaces the old `mod/PLUGINNAME/index.php` page provided by the activity plugins.
+
+To customize the information displayed in the course overview table, activity plugins must implement their own integration class.
+
+## Overview Class Structure
+
+The integration class must be located in `mod/PLUGINNAME/classes/courseformat/overview.php` using the namespace `mod_PLUGINNAME\courseformat\overview`. The `overview` class should extend the `core_courseformat\activityoverviewbase` class and implement the necessary methods to provide the required overview data.
+
+### Class Definition
+
+This is an example of the class definition that do not add any extra overview items. Simply by extending the `activityoverviewbase` class, the course overview will display the activity name, the section and, if the user has completion, the completion status of the activity.
+
+```php
+namespace mod_PLUGINNAME\courseformat;
+
+use core_courseformat\activityoverviewbase;
+
+class overview extends activityoverviewbase {
+ // Implement methods here.
+}
+```
+
+### The `overviewitem` class
+
+The `core_courseformat\local\overview\overviewitem` class represents a specific activity overview item. Currently, items are used only as cells in the course overview table, but they may be used in other places like the course page or the activity page in the future. Also, they could be exported to the mobile APP or other external systems.
+
+The class has the following mandatory properties:
+
+- **`name`** (`string`): The name of the item, used as the table header in the course overview.
+- **`value`** (`int|string|bool|null`): The value of the item, used for filtering purposes. It won't be displayed in the overview table but will be included as a data attribute.
+- **`content`** (`string|renderable|null`): The content of the item. It can be a string for simple elements, but it is highly recommended to use renderable objects to provide useful extra information depending on the context. If the content is null, the item will be displayed as '-' in the overview table.
+
+Also, the class has the following optional properties:
+
+- **`textalign`** (`core\output\local\properties\text_align`): Indicates the preferred text alignment for the parent container. Notice the type hint is not string but `text_align` enum, this limit only to valid text alignment values.
+- **`alertcount`** (`integer`) and **`alertlabel`** (`string`): Some items may have an alert count to inform the user of pending actions. This information is not displayed in the table (must be included also in the content) but is added as a data attribute and may be used in other places in the future, such as filtering or mobile app notifications.
+- **`extradata`** (`stdClass`): Use this property to include any additional data your plugin needs to provide. While it is not used in the overview page, it will be included in the web service data exported for the mobile app, or for future uses.
+- **`key`** (`string`): A unique identifier for the overview item within the displayed context. Typically, the key is assigned automatically based on the context, so you do not need to specify it when instantiating an overview item. For example, the overview table will automatically set the key when retrieving activity overview items.
+
+### Extra Overview Items
+
+To provide extra overview items, the plugin can override the `get_extra_overview_items` method. This method should return an array of `\core_courseformat\local\overview\overviewitem` objects indexed by item shortname.
+
+This is an example of a plugin adding an extra overview item to show if the activity has been submitted:
+
+```php
+namespace mod_PLUGINNAME\courseformat;
+
+use core_courseformat\activityoverviewbase;
+use core_courseformat\local\overview\overviewitem;
+use core\output\pix_icon;
+
+class overview extends activityoverviewbase {
+ #[\Override]
+ public function get_extra_overview_items(): array {
+ return [
+ 'submitted' => $this->get_extra_submitted_overview(),
+ ];
+ }
+
+ /**
+ * Get the extra overview item to show if the activity has been submitted.
+ *
+ * @return overviewitem|null
+ */
+ private function get_extra_submitted_overview(): ?overviewitem {
+ // Validate if the user needs this overview information. Return null otherwise.
+ if (!has_capability('mod/PLUGINNAME:complete', $this->context)) {
+ return null;
+ }
+
+ // All items should have a value (for filtering purposes)-
+ $value = false;
+
+ // The overview content can be a string.
+ $content = '-';
+
+ // Logic to determine if the activity is already submitted
+ if (PLUGIN_LOGIC_TO_DETECT_IF_THE_USER_HAS_SUBMITTED_ANYTHING($this->cm)) {
+ $value = true;
+ // The overview content can also be a renderable object.
+ $content = new \pix_icon(
+ 'i/checkedcircle',
+ alt: get_string('already_submitted', 'mod_PLUGINNAME'),
+ attributes: ['class' => 'text-success'],
+ );
+ }
+
+ // Return the overview item. It is mandatory to provide also a name to display.
+ // The name will be used as the table header in the course overview, but it could
+ // also be used in other placements in the future.
+ return new overviewitem(
+ name: get_string('responded', 'mod_PLUGINNAME'),
+ value: $value,
+ content: $content,
+ );
+ }
+}
+```
+
+## Fixed Overview Items
+
+Some activity information is especial and they will be added automatically to the course overview table without declaring them in the `get_extra_overview_items` method.
+
+The following are the fixed overview items:
+
+- **Activity Name**: The name of the activity as a link, the section name and the visibility badge.
+- **Completion Status**: The completion status of the activity, if enabled and apply to the current user.
+
+However, there are other fixed overview items that are considered empty unless the plugin overrides the corresponding method:
+
+- **Due Date**: The due date of the activity. Provided by the `get_due_date_overview` method.
+- **Main action**: The main action of the activity. Provided by the `get_actions_overview` method.
+
+### Due Date Overview
+
+To provide a due date overview, override the `get_due_date_overview` method. This method must return an `overviewitem` object or null if there is no due date.
+
+```php
+namespace mod_PLUGINNAME\courseformat;
+
+use core_courseformat\activityoverviewbase;
+use core_courseformat\local\overview\overviewitem;
+
+class overview extends activityoverviewbase {
+ #[\Override]
+ public function get_due_date_overview(): ?overviewitem {
+ // Implement here how to get the due date of the activity.
+ $duedate = DO_SOMETHING_TO_GET_YOUR_PLUGIN_DUE_DATE();
+
+ if (empty($duedate)) {
+ // If all overview items return null, the due date column will not be displayed.
+ return new overviewitem(
+ name: get_string('duedate'),
+ value: null,
+ content: '-',
+ );
+ }
+ return new overviewitem(
+ name: get_string('duedate'),
+ value: $this->cm->name,
+ content: userdate($duedate),
+ );
+ }
+}
+```
+
+### Main Action Overview
+
+The main action is always displayed as the last column in the course overview table. However, by default the column is empty and it is not rendered in the table. However, the plugin can define their own action cell to provide useful links to the user. For example, the main action could be the submissions count with a link to the submissions page.
+
+For now, the main action overview is only displayed in the course overview table. However, in the future, it could be used in other places like the course page or the activity page.
+
+To provide a main action overview, override the `get_actions_overview` method.
+
+```php
+namespace mod_PLUGINNAME\courseformat;
+
+use core_courseformat\activityoverviewbase;
+use core_courseformat\local\overview\overviewitem;
+use core\output\action_link;
+use core\output\local\properties\button;
+use core\output\local\properties\text_align;
+
+class overview extends activityoverviewbase {
+ #[\Override]
+ public function get_actions_overview(): ?overviewitem {
+ // Validate if the user can do the action. Return null otherwise.
+ if (!has_capability('mod/PLUGINNAME:viewreports', $this->context)) {
+ return null;
+ }
+
+ $submissions = CALCULATE_YOUR_PLUGIN_SUBMISSIONS_COUNT();
+ $total = CALCULATE_YOUR_PLUGIN_TOTAL_SUBMISSIONS();
+
+ if (!$total) {
+ return new overviewitem(
+ name: get_string('responses', 'mod_feedback'),
+ value: 0,
+ content: '-',
+ textalign: text_align::CENTER,
+ );
+ }
+
+ // For the action content, it is recommended to style it using the
+ // button enum css helper, it will make the final result consistent
+ // with the rest of plugins.
+ $content = new action_link(
+ url: new url('/mod/feedback/show_entries.php', ['id' => $this->cm->id]),
+ text: get_string(
+ 'count_of_total',
+ 'core',
+ ['count' => $submissions, 'total' => $total]
+ ),
+ attributes: ['class' => button::SECONDARY_OUTLINE->classes()],
+ );
+
+ return new overviewitem(
+ name: get_string('responses', 'mod_feedback'),
+ value: $submissions,
+ content: $content,
+ );
+ }
+}
+```
+
+### Custom grade overview items
+
+If the activity has a grade item, the course overview will display the grade of the activity to the student. If your plugin has one single grade item, you don't need to do anything.
+
+For activities with more than one grade item (for example, mod_workshop), no grade will be shown by default because the system doesn't know the meaning of each grade item. For those cases, the plugin must implement `get_grade_item_names` to provide the generic name for each grade item. The overview report will show only the grade items with a generic name.
+
+This is an example of a plugin with two grade items:
+
+```php
+#[\Override]
+protected function get_grade_item_names(array $items): array {
+ // Add some fallback in case some grade item is missing.
+ if (count($items) != 2) {
+ return parent::get_grade_item_names($items);
+ }
+ $names = [];
+ foreach ($items as $item) {
+ // Use the itemnunmber to know which grade item is.
+ $stridentifier = ($item->itemnumber == 0) ? 'submission_gradenoun' : 'assessment_gradenoun';
+ // Names must be indexed by the grade item id.
+ $names[$item->id] = get_string($stridentifier, 'mod_YOURPLUGIN');
+ }
+ return $names;
+}
+```
+
+:::note
+
+It is not recommended to override the `get_grades_overviews` method. The method is used to provide the grade information to the course overview, but it is not intended to be used to provide extra information. If you don't want to show the grade information in your plugin, you can override the `get_grade_item_names` method and return an empty array.
+
+:::
+
+## Dependency Injection
+
+The `overview` class will be loaded using [dependency injection](../../core/di/index.md). The constructor must accept a `cm_info` object to initialize the parent class, however, the constructor can also accept other dependencies that the plugin needs.
+
+This is an example of a overview integration that needs access to the database and a clock interface for timestamps.
+
+```php
+namespace mod_PLUGINNAME\courseformat;
+
+use core_course\activityoverviewbase;
+
+class overview extends activityoverviewbase {
+ public function __construct(
+ /** @var cm_info $cm the activity course module. */
+ cm_info $cm,
+ /** @var \moodle_database $db the database acces. */
+ protected readonly \moodle_database $db,
+ /** @var \core\clock $clock the clock interface */
+ protected readonly \core\clock $clock
+ ) {
+ parent::__construct($cm);
+ }
+
+ // The rest of your code goes here. All methods must access the
+ // $this->db property to interact with the database instead of
+ // using the global $DB.
+
+ // Also, to handle timestamps, use $this->clock->time() instead.
+}
+```
+
+## Redirect the index.php page to the course overview
+
+The `activityoverviewbase` class provides a static method to redirect the old `mod/PLUGINNAME/index.php` page to the new course overview. This method should be called in the `index.php` file of the activity plugin.
+
+This is an example of the `index.php` file of a plugin redirecting to the course overview:
+
+```php
+require_once("../../config.php");
+
+$courseid = required_param('id', PARAM_INT);
+
+\core_courseformat\activityoverviewbase::redirect_to_overview_page($courseid, 'YOURPLUGINNAME');
+```
+
+Once done, the plugin can deprecate any method, output or renderer related to the previous index page.
+
+## Webservice and Moodle APP support
+
+
+
+The Moodle Mobile app does not currently support the course overview table, but this feature is planned for future releases. All data from the overview table is already available via the `core_courseformat_get_overview_information` web service.
+
+This web service returns overview items for all activities in a course, including any extra overview items provided by plugins. To ensure your plugin is compatible with the Moodle Mobile app, follow these guidelines:
+
+- If your overview item content is a plain string, use only basic HTML tags such as ``, ``, ``, and ``. Avoid using special CSS classes, including Bootstrap or other frameworks, as these are not supported by the app.
+- If your overview item content is a renderable object, ensure that it also implements the `exportable` interface. This interface provides a stable data structure for use in web services and is required for mobile compatibility.
+- If your plugin requires custom visual elements that need more information than the standard output class provides, use the `extradata` property to supply any additional data for the APP. This property is included in the web service response and will be accessible by your custom templates in the Moodle app when the course overview table is implemented.
diff --git a/versioned_docs/version-5.1/apis/plugintypes/mod/index.mdx b/versioned_docs/version-5.1/apis/plugintypes/mod/index.mdx
new file mode 100644
index 0000000000..b400fda0c9
--- /dev/null
+++ b/versioned_docs/version-5.1/apis/plugintypes/mod/index.mdx
@@ -0,0 +1,239 @@
+---
+title: Activity modules
+toc_max_heading_level: 4
+tags:
+ - API
+ - Activity
+ - Module
+ - mod
+---
+
+import {
+ BackupDir,
+ DbAccessPHP,
+ DbEventsPHP,
+ DbInstallXML,
+ DbMobilePHP,
+ DbUpgradePHP,
+ Lang,
+ Lib,
+ VersionPHP,
+} from '../../_files';
+
+import ModFormPHP from './_files/mod_form-php';
+import IndexPHP from './_files/index-php';
+import ViewPHP from './_files/view-php';
+
+import InstallDescription from './_files/install_description.md';
+import AccessDescription from './_files/access_description.md';
+import LibDescription from './_files/lib_description.md';
+
+Activity modules are a fundamental course feature and are usually the primary delivery method for learning content in Moodle.
+
+The plugintype of an Activity module is `mod`, and the frankenstyle name of a plugin is therefore `mod_[modname]`.
+
+All activity module plugins are located in the `/mod/` folder of Moodle.
+
+:::note
+
+The term `[modname]` is used as a placeholder in this documentation and should be replaced with the name of your activity module.
+
+:::
+
+## Folder layout
+
+Activity modules reside in the `/mod` directory.
+
+Each module is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use.
+
+Below is an example of the file structure for the `folder` plugin.
+
+
+ View an example directory layout for the `folder` plugin.
+