diff --git a/docs/devupdate.md b/docs/devupdate.md
index 5d868f8028..fba63792a4 100644
--- a/docs/devupdate.md
+++ b/docs/devupdate.md
@@ -1,271 +1,10 @@
---
-title: Moodle 4.5 developer update
+title: Moodle 5.0 developer update
tags:
- Core development
-- Moodle 4.5
+- Moodle 5.0
---
-This page highlights the important changes that are coming in Moodle 4.5 for developers.
-
-## Badges
-
-### Deprecated `badges/newbadge.php`
-
-
-
-The `badges/newbadge.php` and `badges/edit.php` pages have been combined to make things easier to maintain since both were pretty similar (`newbadge.php` for creating badges and `edit.php` for editing them).
-
-As a result, `badges/newbadge.php` is now deprecated and will be removed in Moodle 6.0. Please update your code to use `badges/edit.php` instead.
-
-:::info
-
-Visiting
-
-https://yourmoodlesite/badges/newbadge.php?id=x
-
-will now automatically redirect to
-
-https://yourmoodlesite/badges/edit.php?courseid=x&mode=new
-
-:::
-
-### Deprecated `badges/view.php`
-
-
-
-The `badges/index.php` and `badges/view.php` pages have been combined to make things easier to maintain since both were pretty similar and the workflow for accessing badges from the course page was confusing in some cases.
-
-As a result, `badges/view.php` is now deprecated and will be removed in Moodle 6.0. Please update your code to use `badges/index.php` instead.
-
-Apart from that, the `course_badges` system report has been merged with `badges` system report. It has been deprecated too and will be removed in Moodle 6.0. Please update your code to use `badges` system report instead.
-
-:::info
-
-Visiting
-
-https://yourmoodlesite/badges/view.php?type=x&id=x
-
-will now automatically redirect to
-
-https://yourmoodlesite/badges/index.php?type=x&id=x
-
-:::
-
-## Core changes
-
-### Autoloader
-
-#### `ABORT_AFTER_CONFIG`
-
-
-
-Prior to Moodle 4.5 only a small number of classes were compatible with scripts using the `ABORT_AFTER_CONFIG` constant.
-
-MDL-80275 modifies the location of the class Autoloader in the Moodle bootstrap to make it available to scripts using the `ABORT_AFTER_CONFIG` constant.
-
-:::note
-
-Please note that the same limitations regarding access to the Database, Session, and similar resources still exist.
-
-:::
-
-#### Autoloading legacy classes
-
-
-
-The Moodle class autoloader is now able to load legacy classes defined in the relevant `db/legacyclasses.php`. Files should only contain a single class.
-
-```php title="Example entry in lib/db/legacyclasses.php"
-$legacyclasses = [
- // The legacy \moodle_exception class can be loaded from lib/classes/exception/moodle_exception.php.
- \moodle_exception::class => 'exception/moodle_exception.php',
-
- // The legacy \cache class can be loaded from cache/classes/cache.php.
- \cache::class => [
- 'core_cache',
- 'cache.php',
- ],
-];
-```
-
-See MDL-81919 for further information on the rationale behind this change.
-
-### SMS API
-
-A new SMS API was introduced. See the [SMS API documentation](./apis/subsystems/sms/index.md) for more information.
-
-## Course
-
-### Reset course page
-
-The reset course page has been improved. The words "Delete", and "Remove" have been removed from all options to make it easier to focus on the type of data to be removed and avoid inconsistencies and duplicated information.
-Third party plugins implementing reset methods might need to:
-
-- Add static element in the `_reset_course_form_definition` method before all the options with the `Delete` string:
-
- ```php
- $mform->addElement('static', 'assigndelete', get_string('delete'));
- ```
-
-- Review all the strings used in the reset page to remove the `Delete` or `Remove` words from them.
-
-:::caution
-
-Starting from Moodle 4.5, the Reset course page form defined in the `_reset_course_form_definition` method should be reviewed because their options should not contain the `Delete` or `Remove` words.
-Check changes in any of the core plugins that implement the reset course method.
-
-:::
-
-## Deprecations
-
-### Icon deprecation
-
-
-
-A new mechanism for deprecating icons has been introduced. More information can be found in the [icon deprecation documentation](/general/development/policies/deprecation/icon-deprecation).
-
-## Filter Plugins
-
-
-
-Filter plugins and the Filter API have been updated to use the standard Moodle Class Autoloading infrastructure.
-
-To ensure that your plugin continues to work in Moodle 4.5, you should move the `filter_[pluginname]` class located in `filter/[pluginname]/filter.php` to `filter/[pluginname]/classes/text_filter.php`, setting the namespace to `filter_[pluginname]` and renaming the class to `text_filter`.
-
-:::tip Codebases supporting multiple versions of Moodle
-
-If your codebase also supports Moodle 4.4 and earlier then you will also need to create a file in the 'old' location (`filter/[pluginname]/filter.php`) with the following content:
-
-```php title="filter/[pluginname]/filter.php"
-class_alias(\filter_[pluginname]\text_filter::class, \filter_[pluginname]::class);
-```
-
-This will ensure that the plugin class is available at both the old and new locations.
-
-:::
-
-## TinyMCE plugins
-
-The `helplinktext` language string is no longer required by editor plugins, instead the `pluginname` will be used in the help dialogue
-
-## Theme
-
-### Context header
-
-
-
-The method `core_renderer::render_context_header($contextheader)` has been deprecated, `core_renderer::render($contextheader)` should be used instead.
-
-Plugins can still modify the context header by:
-
-- Overriding `core_renderer::context_header()` method in their class extending `core_renderer`
-- Adding `core_renderer::render_context_header()` method to their class extending `core_renderer`
-- Overriding the `core/context_header.mustache` template
-
-
-
-
-
-```php title="theme/example/classes/output/core_renderer.php"
-class core_renderer extends \core_renderer {
- [...]
- public function context_header($headerinfo = null, $headinglevel = 1): string {
- $output = parent::context_header($headerinfo, $headinglevel);
- return $output . '
-```
-
-
-
-
-
-### Refactoring BS4 features dropped in BS5 using a "bridge"
-
-
-
-Some of the Bootstrap 4 classes will be deprecated or dropped in its version 5. To prepare for this, some of the current Bootstrap 4 classes usages have been replaced with version 5 compatible classes using a "bridge". This will help us to upgrade to Bootstrap 5 in the future.
-
-See more information in [Bootstrap 5 migration](./guides/bs5migration/index.md).
-
-### Support FontAwesome families
-
-
-
-Upon upgrading Font Awesome (FA) from version 4 to 6, the solid family was selected by default. However, FA6 includes additional families such as regular and brands. Support for these families has now been integrated, allowing icons defined with `icon_system::FONTAWESOME` to use them.
-Icons can add the FontAwesome family (`fa-regular`, `fa-brands`, `fa-solid`) near the icon name to display it using this styling:
-
-```php title="Example of FA families from lib/classes/output/icon_system_fontawesome.php"
- 'core:i/rss' => 'fa-rss',
- 'core:i/rsssitelogo' => 'fa-graduation-cap',
- 'core:i/scales' => 'fa-scale-balanced',
- 'core:i/scheduled' => 'fa-regular fa-calendar-check',
- 'core:i/search' => 'fa-magnifying-glass',
- 'core:i/section' => 'fa-regular fa-rectangle-list',
- 'core:i/sendmessage' => 'fa-regular fa-paper-plane',
- 'core:i/settings' => 'fa-gear',
- 'core:i/share' => 'fa-regular fa-share-from-square',
- 'core:i/show' => 'fa-regular fa-eye-slash',
- 'core:i/siteevent' => 'fa-solid fa-globe',
-
-```
-
-### FontAwesome icons updated
-
-
-
-The icons in Moodle core have been updated according to the UX team's specifications in MDL-77754. The team reviewed and proposed updates to leverage the new icons in Font Awesome 6, ensuring greater consistency.
-
-In addition to updating the icons, the following changes have been made:
-
-- The SVG files have been updated to SVG FA6 for better alignment and improved appearance.
-- PNG, JPG, and GIF files have been removed from the repository where possible, leaving only SVG files to simplify maintenance.
-
-For third-party plugins utilizing their own icons via the callback `get_fontawesome_icon_map()`, it is advisable to review and align these icons with the core icons for consistency. Here are some guidelines followed by the UX team that may be useful:
-
-- The pencil icon has been replaced by a pen.
-- The cog icon is used exclusively for settings; otherwise, use the pen icon.
-- Import/Upload actions should use the `fa-upload` icon, while Export/Download actions should use the `fa-download` icon.
-- The eye icon is used for both visibility and preview actions.
-
-:::tip Icons in Component library
-
-On the Icons page of the [Component library](/general/development/tools/component-library), you can find a comprehensive list of all the icons available in Moodle core.
-
-:::
+This page highlights the important changes that are coming in Moodle 5.0 for developers.
diff --git a/nextVersion.js b/nextVersion.js
index 4522c516c6..3cf1b88c12 100644
--- a/nextVersion.js
+++ b/nextVersion.js
@@ -15,7 +15,7 @@
* along with Moodle. If not, see .
*/
-const nextVersion = '4.5';
+const nextVersion = '5.0';
const nextLTSVersion = '5.3';
const nextVersionRoot = `/docs/${nextVersion}`;
diff --git a/versioned_docs/version-4.5/_devupdate/activity-icons.png b/versioned_docs/version-4.5/_devupdate/activity-icons.png
new file mode 100644
index 0000000000..389dd3b7d5
Binary files /dev/null and b/versioned_docs/version-4.5/_devupdate/activity-icons.png differ
diff --git a/versioned_docs/version-4.5/_utils.tsx b/versioned_docs/version-4.5/_utils.tsx
new file mode 100644
index 0000000000..437aa81219
--- /dev/null
+++ b/versioned_docs/version-4.5/_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-4.5/apis.md b/versioned_docs/version-4.5/apis.md
new file mode 100644
index 0000000000..fdf7d23b79
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/amd-dir.mdx b/versioned_docs/version-4.5/apis/_files/amd-dir.mdx
new file mode 100644
index 0000000000..b22f14b4cd
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/amd-dir.tsx b/versioned_docs/version-4.5/apis/_files/amd-dir.tsx
new file mode 100644
index 0000000000..3587716005
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/backup-dir.mdx b/versioned_docs/version-4.5/apis/_files/backup-dir.mdx
new file mode 100644
index 0000000000..7b77168eb2
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/backup-dir.tsx b/versioned_docs/version-4.5/apis/_files/backup-dir.tsx
new file mode 100644
index 0000000000..00e6225317
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/changes.mdx b/versioned_docs/version-4.5/apis/_files/changes.mdx
new file mode 100644
index 0000000000..afcb40332e
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/changes.tsx b/versioned_docs/version-4.5/apis/_files/changes.tsx
new file mode 100644
index 0000000000..7ba853911c
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/classes-dir.mdx b/versioned_docs/version-4.5/apis/_files/classes-dir.mdx
new file mode 100644
index 0000000000..d5a522cef6
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/classes-dir.tsx b/versioned_docs/version-4.5/apis/_files/classes-dir.tsx
new file mode 100644
index 0000000000..ef801dddbd
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/cli-dir.mdx b/versioned_docs/version-4.5/apis/_files/cli-dir.mdx
new file mode 100644
index 0000000000..8757cadb97
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/cli-dir.tsx b/versioned_docs/version-4.5/apis/_files/cli-dir.tsx
new file mode 100644
index 0000000000..94f11fcb00
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-access-php.mdx b/versioned_docs/version-4.5/apis/_files/db-access-php.mdx
new file mode 100644
index 0000000000..f4bd89b3a4
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-access-php.tsx b/versioned_docs/version-4.5/apis/_files/db-access-php.tsx
new file mode 100644
index 0000000000..877cd86aae
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-events-php.mdx b/versioned_docs/version-4.5/apis/_files/db-events-php.mdx
new file mode 100644
index 0000000000..11667d3c5e
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-events-php.tsx b/versioned_docs/version-4.5/apis/_files/db-events-php.tsx
new file mode 100644
index 0000000000..71e928813f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-install-php.mdx b/versioned_docs/version-4.5/apis/_files/db-install-php.mdx
new file mode 100644
index 0000000000..e91709f741
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-install-php.tsx b/versioned_docs/version-4.5/apis/_files/db-install-php.tsx
new file mode 100644
index 0000000000..9a18223bf7
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-install-xml.mdx b/versioned_docs/version-4.5/apis/_files/db-install-xml.mdx
new file mode 100644
index 0000000000..4af56d4989
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-legacyclasses-php.mdx b/versioned_docs/version-4.5/apis/_files/db-legacyclasses-php.mdx
new file mode 100644
index 0000000000..12eb9a5d34
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-legacyclasses-php.tsx b/versioned_docs/version-4.5/apis/_files/db-legacyclasses-php.tsx
new file mode 100644
index 0000000000..e83e62e417
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-messages-php.mdx b/versioned_docs/version-4.5/apis/_files/db-messages-php.mdx
new file mode 100644
index 0000000000..2b52b7cf0a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-messages-php.tsx b/versioned_docs/version-4.5/apis/_files/db-messages-php.tsx
new file mode 100644
index 0000000000..f8c576ec84
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-mobile-php.mdx b/versioned_docs/version-4.5/apis/_files/db-mobile-php.mdx
new file mode 100644
index 0000000000..b16a65f3c1
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-mobile-php.tsx b/versioned_docs/version-4.5/apis/_files/db-mobile-php.tsx
new file mode 100644
index 0000000000..322b29f8f9
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-renamedclasses-php.mdx b/versioned_docs/version-4.5/apis/_files/db-renamedclasses-php.mdx
new file mode 100644
index 0000000000..91e69e0053
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-renamedclasses-php.tsx b/versioned_docs/version-4.5/apis/_files/db-renamedclasses-php.tsx
new file mode 100644
index 0000000000..ef6d5b81d6
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-services-php.mdx b/versioned_docs/version-4.5/apis/_files/db-services-php.mdx
new file mode 100644
index 0000000000..8e34f888db
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-services-php.tsx b/versioned_docs/version-4.5/apis/_files/db-services-php.tsx
new file mode 100644
index 0000000000..04a8536db3
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-tasks-example.php b/versioned_docs/version-4.5/apis/_files/db-tasks-example.php
new file mode 100644
index 0000000000..1c8ac31514
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-tasks-php.mdx b/versioned_docs/version-4.5/apis/_files/db-tasks-php.mdx
new file mode 100644
index 0000000000..ea33364e06
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-tasks-php.tsx b/versioned_docs/version-4.5/apis/_files/db-tasks-php.tsx
new file mode 100644
index 0000000000..68c9ac809b
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-uninstall-php.mdx b/versioned_docs/version-4.5/apis/_files/db-uninstall-php.mdx
new file mode 100644
index 0000000000..5ed3089e4b
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-uninstall-php.tsx b/versioned_docs/version-4.5/apis/_files/db-uninstall-php.tsx
new file mode 100644
index 0000000000..8d4f8c2081
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-upgrade-php.mdx b/versioned_docs/version-4.5/apis/_files/db-upgrade-php.mdx
new file mode 100644
index 0000000000..3dde622bc7
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/db-upgrade-php.tsx b/versioned_docs/version-4.5/apis/_files/db-upgrade-php.tsx
new file mode 100644
index 0000000000..8e96d96b65
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/environment-xml.mdx b/versioned_docs/version-4.5/apis/_files/environment-xml.mdx
new file mode 100644
index 0000000000..62af6b1660
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/environment-xml.tsx b/versioned_docs/version-4.5/apis/_files/environment-xml.tsx
new file mode 100644
index 0000000000..ce2afb8324
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/index.tsx b/versioned_docs/version-4.5/apis/_files/index.tsx
new file mode 100644
index 0000000000..76ac8c209f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/install-xml.tsx b/versioned_docs/version-4.5/apis/_files/install-xml.tsx
new file mode 100644
index 0000000000..98b03c8821
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/lang-extra.md b/versioned_docs/version-4.5/apis/_files/lang-extra.md
new file mode 100644
index 0000000000..f6f10fb772
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/lang.md b/versioned_docs/version-4.5/apis/_files/lang.md
new file mode 100644
index 0000000000..c73920bb90
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/lang.tsx b/versioned_docs/version-4.5/apis/_files/lang.tsx
new file mode 100644
index 0000000000..8356a3d5af
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/lib.mdx b/versioned_docs/version-4.5/apis/_files/lib.mdx
new file mode 100644
index 0000000000..e9f02313d0
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/lib.tsx b/versioned_docs/version-4.5/apis/_files/lib.tsx
new file mode 100644
index 0000000000..bc3ddec6b9
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/locallib.mdx b/versioned_docs/version-4.5/apis/_files/locallib.mdx
new file mode 100644
index 0000000000..fb3cab2811
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/locallib.tsx b/versioned_docs/version-4.5/apis/_files/locallib.tsx
new file mode 100644
index 0000000000..0093700154
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/pix-dir.mdx b/versioned_docs/version-4.5/apis/_files/pix-dir.mdx
new file mode 100644
index 0000000000..f58f9c6fee
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/pix-dir.tsx b/versioned_docs/version-4.5/apis/_files/pix-dir.tsx
new file mode 100644
index 0000000000..cdb803f34a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/readme.mdx b/versioned_docs/version-4.5/apis/_files/readme.mdx
new file mode 100644
index 0000000000..42bb31fa21
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/readme.tsx b/versioned_docs/version-4.5/apis/_files/readme.tsx
new file mode 100644
index 0000000000..8999ddaccc
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/readme_moodle-txt.mdx b/versioned_docs/version-4.5/apis/_files/readme_moodle-txt.mdx
new file mode 100644
index 0000000000..ed589cbfd4
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/readme_moodle-txt.tsx b/versioned_docs/version-4.5/apis/_files/readme_moodle-txt.tsx
new file mode 100644
index 0000000000..55827c08aa
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/settings-php.mdx b/versioned_docs/version-4.5/apis/_files/settings-php.mdx
new file mode 100644
index 0000000000..04b942c4c9
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/settings-php.tsx b/versioned_docs/version-4.5/apis/_files/settings-php.tsx
new file mode 100644
index 0000000000..9dd13af88e
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/styles-css.mdx b/versioned_docs/version-4.5/apis/_files/styles-css.mdx
new file mode 100644
index 0000000000..5b5495f69b
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/styles-css.tsx b/versioned_docs/version-4.5/apis/_files/styles-css.tsx
new file mode 100644
index 0000000000..21ea5adcc5
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/thirdpartylibs-xml.mdx b/versioned_docs/version-4.5/apis/_files/thirdpartylibs-xml.mdx
new file mode 100644
index 0000000000..301dde8126
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/thirdpartylibs-xml.tsx b/versioned_docs/version-4.5/apis/_files/thirdpartylibs-xml.tsx
new file mode 100644
index 0000000000..052e1a2ed0
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/upgrade-php.mdx b/versioned_docs/version-4.5/apis/_files/upgrade-php.mdx
new file mode 100644
index 0000000000..41f6748ed8
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/upgrade-txt.mdx b/versioned_docs/version-4.5/apis/_files/upgrade-txt.mdx
new file mode 100644
index 0000000000..a29d8cd37f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/upgrade-txt.tsx b/versioned_docs/version-4.5/apis/_files/upgrade-txt.tsx
new file mode 100644
index 0000000000..db928bfec3
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/version-php.mdx b/versioned_docs/version-4.5/apis/_files/version-php.mdx
new file mode 100644
index 0000000000..23c9c7847a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/version-php.tsx b/versioned_docs/version-4.5/apis/_files/version-php.tsx
new file mode 100644
index 0000000000..f855221dcb
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/yui-dir.mdx b/versioned_docs/version-4.5/apis/_files/yui-dir.mdx
new file mode 100644
index 0000000000..4ec0275759
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/_files/yui-dir.tsx b/versioned_docs/version-4.5/apis/_files/yui-dir.tsx
new file mode 100644
index 0000000000..fec0d588eb
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/commonfiles/db-tasks.php/index.md b/versioned_docs/version-4.5/apis/commonfiles/db-tasks.php/index.md
new file mode 100644
index 0000000000..024c64999f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/commonfiles/index.mdx b/versioned_docs/version-4.5/apis/commonfiles/index.mdx
new file mode 100644
index 0000000000..f5749e95c9
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/commonfiles/tag.php/index.md b/versioned_docs/version-4.5/apis/commonfiles/tag.php/index.md
new file mode 100644
index 0000000000..fcfd89e679
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/commonfiles/version.php/index.md b/versioned_docs/version-4.5/apis/commonfiles/version.php/index.md
new file mode 100644
index 0000000000..1695182990
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/activitycompletion/index.md b/versioned_docs/version-4.5/apis/core/activitycompletion/index.md
new file mode 100644
index 0000000000..e959da9505
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/calendar/_index/my_overview_sam_student.png b/versioned_docs/version-4.5/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-4.5/apis/core/calendar/_index/my_overview_sam_student.png differ
diff --git a/versioned_docs/version-4.5/apis/core/calendar/index.md b/versioned_docs/version-4.5/apis/core/calendar/index.md
new file mode 100644
index 0000000000..6818ffb379
--- /dev/null
+++ b/versioned_docs/version-4.5/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;
+```
+
+![my overview sam student.png](./_index/my_overview_sam_student.png)
+
+### 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://tracker.moodle.org/browse/[MDL-60340](https://tracker.moodle.org/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-4.5/apis/core/clock/index.md b/versioned_docs/version-4.5/apis/core/clock/index.md
new file mode 100644
index 0000000000..db805fda40
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/conditionalactivities/index.md b/versioned_docs/version-4.5/apis/core/conditionalactivities/index.md
new file mode 100644
index 0000000000..eac7de5ed4
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/customfields/index.md b/versioned_docs/version-4.5/apis/core/customfields/index.md
new file mode 100644
index 0000000000..faaac256b6
--- /dev/null
+++ b/versioned_docs/version-4.5/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://tracker.moodle.org/browse/MDL-64626) - Custom fields API (Moodle 3.7+) implementations and improvements
+- [MDL-57898](https://tracker.moodle.org/browse/MDL-57898) - Add custom field types plugin and course custom fields functionality
diff --git a/versioned_docs/version-4.5/apis/core/deprecation/index.md b/versioned_docs/version-4.5/apis/core/deprecation/index.md
new file mode 100644
index 0000000000..314604cf16
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/di/index.md b/versioned_docs/version-4.5/apis/core/di/index.md
new file mode 100644
index 0000000000..52ea3402bd
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png b/versioned_docs/version-4.5/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png
new file mode 100644
index 0000000000..4b1c683898
Binary files /dev/null and b/versioned_docs/version-4.5/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png differ
diff --git a/versioned_docs/version-4.5/apis/core/dml/_exceptions/Dml_exceptions.png b/versioned_docs/version-4.5/apis/core/dml/_exceptions/Dml_exceptions.png
new file mode 100644
index 0000000000..304093c414
Binary files /dev/null and b/versioned_docs/version-4.5/apis/core/dml/_exceptions/Dml_exceptions.png differ
diff --git a/versioned_docs/version-4.5/apis/core/dml/database-schema.md b/versioned_docs/version-4.5/apis/core/dml/database-schema.md
new file mode 100644
index 0000000000..834c03f675
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/dml/ddl.md b/versioned_docs/version-4.5/apis/core/dml/ddl.md
new file mode 100644
index 0000000000..6e1b866b6f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/dml/delegated-transactions.md b/versioned_docs/version-4.5/apis/core/dml/delegated-transactions.md
new file mode 100644
index 0000000000..2dc3d7a03c
--- /dev/null
+++ b/versioned_docs/version-4.5/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
+
+![The flow of transactions in diagram format](./_delegated-transactions/TransactionsAndExceptionsFlow.png)
+
+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-4.5/apis/core/dml/drivers.md b/versioned_docs/version-4.5/apis/core/dml/drivers.md
new file mode 100644
index 0000000000..efc34f1f6a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/dml/exceptions.md b/versioned_docs/version-4.5/apis/core/dml/exceptions.md
new file mode 100644
index 0000000000..f0b6a9b3c8
--- /dev/null
+++ b/versioned_docs/version-4.5/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 Exceptions](_exceptions/Dml_exceptions.png)
+
+### 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-4.5/apis/core/dml/index.md b/versioned_docs/version-4.5/apis/core/dml/index.md
new file mode 100644
index 0000000000..3a571dee54
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/core/dml/index.md
@@ -0,0 +1,1115 @@
+---
+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_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://tracker.moodle.org/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-4.5/apis/core/grading/index.md b/versioned_docs/version-4.5/apis/core/grading/index.md
new file mode 100644
index 0000000000..1a3112c82f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/hooks/index.md b/versioned_docs/version-4.5/apis/core/hooks/index.md
new file mode 100644
index 0000000000..c69a637555
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/core/hooks/index.md
@@ -0,0 +1,536 @@
+---
+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
+
+Callbacks _are executed during system installation and all upgrades_, the callback
+methods must verify the plugin is in correct state. Often the easiest way is to
+use function during_initial_install() or version string from the plugin configuration.
+
+:::
+
+### 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-4.5/apis/core/htmlwriter/index.md b/versioned_docs/version-4.5/apis/core/htmlwriter/index.md
new file mode 100644
index 0000000000..4740676734
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/index.md b/versioned_docs/version-4.5/apis/core/index.md
new file mode 100644
index 0000000000..68027faa93
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/lock/index.md b/versioned_docs/version-4.5/apis/core/lock/index.md
new file mode 100644
index 0000000000..7b80a348f2
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/message/index.md b/versioned_docs/version-4.5/apis/core/message/index.md
new file mode 100644
index 0000000000..7d2f24ed70
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/navigation/_index/Moodle-IA.png b/versioned_docs/version-4.5/apis/core/navigation/_index/Moodle-IA.png
new file mode 100644
index 0000000000..b081cfa530
Binary files /dev/null and b/versioned_docs/version-4.5/apis/core/navigation/_index/Moodle-IA.png differ
diff --git a/versioned_docs/version-4.5/apis/core/navigation/_index/assignmentmenu.png b/versioned_docs/version-4.5/apis/core/navigation/_index/assignmentmenu.png
new file mode 100644
index 0000000000..94abeec2bd
Binary files /dev/null and b/versioned_docs/version-4.5/apis/core/navigation/_index/assignmentmenu.png differ
diff --git a/versioned_docs/version-4.5/apis/core/navigation/index.md b/versioned_docs/version-4.5/apis/core/navigation/index.md
new file mode 100644
index 0000000000..e78efb91a0
--- /dev/null
+++ b/versioned_docs/version-4.5/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:
+
+![Information Architecture](./_index/Moodle-IA.png)
+
+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.
+
+![The structure of the Assignment plugin in the Settings navigation](./_index/assignmentmenu.png)
+
+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-4.5/apis/core/preference/index.md b/versioned_docs/version-4.5/apis/core/preference/index.md
new file mode 100644
index 0000000000..b71b7a83fd
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/core/reportbuilder/_index/Actions.jpg b/versioned_docs/version-4.5/apis/core/reportbuilder/_index/Actions.jpg
new file mode 100644
index 0000000000..c8deb6d3b4
Binary files /dev/null and b/versioned_docs/version-4.5/apis/core/reportbuilder/_index/Actions.jpg differ
diff --git a/versioned_docs/version-4.5/apis/core/reportbuilder/index.md b/versioned_docs/version-4.5/apis/core/reportbuilder/index.md
new file mode 100644
index 0000000000..a9117b1795
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/core/reportbuilder/index.md
@@ -0,0 +1,289 @@
+---
+title: Report builder API
+tags:
+ - Report builder
+ - reports
+---
+
+## System Reports
+
+### Introduction
+
+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.
+
+### 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
+
+```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_table_aliases()
+get_default_entity_title()
+initialise()
+```
+
+##### get_default_table_aliases()
+
+Defines the SQL alias for the database tables the entity uses.
+
+##### get_default_entity_title()
+
+Defines the default title for this entity.
+
+##### initialise()
+
+This is where we **add** the entity columns and filters.
+
+#### 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
+
+![Example of actions on the tasks logs system report](./_index/Actions.jpg)
+
+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)
diff --git a/versioned_docs/version-4.5/apis/core/user/index.md b/versioned_docs/version-4.5/apis/core/user/index.md
new file mode 100644
index 0000000000..06b30c1b59
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/ai/index.md b/versioned_docs/version-4.5/apis/plugintypes/ai/index.md
new file mode 100644
index 0000000000..1f1002c147
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/ai/index.md
@@ -0,0 +1,39 @@
+---
+title: AI Plugins
+tags:
+ - AI
+ - LLM
+ - Provider
+ - Placement
+---
+
+The AI subsystem in the LMS is designed to be extensible, allowing for the integration of external AI services.
+This is achieved through the use of AI plugins, which are divided into two types: Providers and Placements.
+
+### Placements
+
+The aim of Placements is to provide a consistent UX and UI for users when they use AI backed functionality.
+
+Placement plugins leverage the functionality of the other components of the AI subsystem.
+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 LMS plugins in their own right and are not "other" types of LMS plugins,
+it gives great flexibility in how AI functionality is presented to users.
+
+See the [Placements](/apis/plugintypes/ai/placement.md) documentation for more information
+on developing Placement plugins.
+
+### Providers
+
+Provider plugins are the interface between the LMS AI subsystem and external AI systems.
+Their focus is on 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.
+
+Because of this design the Providers that provide the AI Actions can be swapped out, mix and matched
+or upgraded; both without the need to update the Placement code and without the need to change the
+way users interact with the functionality.
+
+See the [Providers](/apis/plugintypes/ai/provider.md) documentation for more information
+on developing Provider plugins.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md b/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md
new file mode 100644
index 0000000000..a157bb3841
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md
@@ -0,0 +1,129 @@
+---
+title: Placements
+tags:
+ - AI
+ - LLM
+ - Placement
+---
+
+The aim of Placements is to provide a consistent UX and UI for users when they use AI backed functionality.
+
+Placement plugins leverage the functionality of the other components of the AI subsystem.
+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 LMS plugins in their own right and are not "other" types of LMS plugins,
+it gives great flexibility in how AI functionality is presented to users.
+
+:::warning The Golden Rule:
+
+Placements DO NOT know about Providers, and Providers DO NOT know about Placements.
+Everything should go via the Manager.
+
+:::
+
+Placements are defined as classes in their own namespace according to their plugin name.
+The naming convention for Action classes is `aiplacement_`,
+for example: `aiplacement_editor`. With corresponding namespaces.
+
+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 are responsible for determining who and where a Placement (and by extension an Action can be used).
+It is not the job of Actions or Providers to determine access.
+
+## Action Processing
+
+The following is the basic workflow in order for a placement to have an action processed for a user request:
+
+- The Placement instantiates a new action object of type they wish to use.
+- The action must be instantiated and passing it 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,
+);
+```
+
+- The Placement then instantiates the Manager class and calls `process_action()`
+- passing in the configured action object:
+
+```php
+// Send the action to the AI manager.
+$manager = \core\di::get(\core_ai\manager::class);
+$response = $manager->process_action($action);
+````
+
+- The process_action() method will then return a response object (instance of `responses\response_base`).
+- It is up to the Placement to check for success (or not) of the response and pass the result back to the
+ user or for further processing.
+
+## Plugin Structure
+
+Placement plugins reside in the `ai/placement` directory.
+
+Each Placement is in a separate subdirectory and consists of a number of mandatory files and any other
+files the developer is going to use.
+
+The following is the typical structure of a Placement plugin, using the Editor Placement as an example:
+
+```bash
+.
+├── classes
+│ ├── external
+│ │ ├── generate_image.php
+│ │ └── generate_text.php
+│ ├── placement.php
+│ └── privacy
+│ └── provider.php
+├── db
+│ ├── access.php
+│ └── services.php
+├── lang
+│ └── en
+│ └── aiplacement_editor.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 as for Provider plugins, for example:
+
+```php
+use core_ai\admin\admin_settingspage_provider;
+
+if ($hassiteconfig) {
+ // Placement specific settings heading.
+ $settings = new admin_settingspage_provider(
+ 'aiprovider_openai',
+ new lang_string('pluginname', 'aiprovider_openai'),
+ 'moodle/site:config',
+ true,
+ );
+
+...
+```
diff --git a/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md b/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md
new file mode 100644
index 0000000000..1af3f249c2
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md
@@ -0,0 +1,228 @@
+---
+title: Providers
+tags:
+ - AI
+ - LLM
+ - Provider
+---
+Providers are the interface between the LMS AI subsystem and external AI systems.
+Their focus should be on 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.
+
+:::warning The Golden Rule:
+
+Placements DO NOT know about Providers, and Providers DO NOT know about Placements.
+Everything should go via the Manager.
+
+:::
+
+Providers are defined as classes in their own namespace according to their plugin name.
+The naming convention for Action classes is `aiprovider_`,
+for example: `aiprovider_openai`, `aiprovider_azureai`. With corresponding namespaces.
+
+Each Provider MUST inherit from the `\core_ai\provider` abstract class.
+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 function get_action_list(): array {
+ return [
+ \core_ai\aiactions\generate_text::class,
+ \core_ai\aiactions\generate_image::class,
+ \core_ai\aiactions\summarise_text::class,
+ ];
+}
+```
+
+## 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 following is the typical structure of a Provider plugin, using the OpenAI Provider as an example:
+
+```bash
+.
+├── classes
+│ ├── abstract_processor.php
+│ ├── privacy
+│ │ └── provider.php
+│ ├── process_generate_image.php
+│ ├── process_generate_text.php
+│ ├── process_summarise_text.php
+│ └── provider.php
+├── lang
+│ └── en
+│ └── aiprovider_openai.php
+├── settings.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
+
+```
+
+## Settings
+
+Settings for the Provider should be defined in the `settings.php` file.
+Each Provider plugin should create a new admin settings page using `core_ai\admin\admin_settingspage_provider` class.
+
+For example, the `aiprovider_openai` plugin defines this:
+
+```php
+use core_ai\admin\admin_settingspage_provider;
+
+if ($hassiteconfig) {
+ // Provider specific settings heading.
+ $settings = new admin_settingspage_provider(
+ 'aiprovider_openai',
+ new lang_string('pluginname', 'aiprovider_openai'),
+ 'moodle/site:config',
+ true,
+ );
+
+...
+```
+
+## Rate Limiting
+
+It is recommended that Providers implement rate limiting to prevent abuse of the external AI services.
+
+To assist with this, the AI subsystem provides a `core_ai\rate_limiter` class that can be used to implement rate limiting.
+This class supports both user and system level rate limiting.
+
+This should be implemented in a `is_request_allowed()` method in the Provider class. For example, from the
+`aiprovider_openai` plugin:
+
+```php
+/**
+ * Check if the request is allowed by the rate limiter.
+ *
+ * @param aiactions\base $action The action to check.
+ * @return array|bool True on success, array of error details on failure.
+ */
+ public function is_request_allowed(aiactions\base $action): array|bool {
+ $ratelimiter = \core\di::get(rate_limiter::class);
+ $component = \core\component::get_component_from_classname(get_class($this));
+
+ // Check the user rate limit.
+ if ($this->enableuserratelimit) {
+ if (!$ratelimiter->check_user_rate_limit(
+ component: $component,
+ ratelimit: $this->userratelimit,
+ userid: $action->get_configuration('userid')
+ )) {
+ return [
+ 'success' => false,
+ 'errorcode' => 429,
+ 'errormessage' => 'User rate limit exceeded',
+ ];
+ }
+ }
+
+ // Check the global rate limit.
+ if ($this->enableglobalratelimit) {
+ if (!$ratelimiter->check_global_rate_limit(
+ component: $component,
+ ratelimit: $this->globalratelimit
+ )) {
+ return [
+ 'success' => false,
+ 'errorcode' => 429,
+ 'errormessage' => 'Global rate limit exceeded',
+ ];
+ }
+ }
+
+ return true;
+ }
+```
+
+If implementing rate limiting, settings for the rate limits should be provided in the plugin settings.
+
+For example, the `aiprovider_openai` plugin provides settings for the user and global rate limits:
+
+```php
+ // Setting to enable/disable global rate limiting.
+ $settings->add(new admin_setting_configcheckbox(
+ 'aiprovider_openai/enableglobalratelimit',
+ new lang_string('enableglobalratelimit', 'aiprovider_openai'),
+ new lang_string('enableglobalratelimit_desc', 'aiprovider_openai'),
+ 0,
+ ));
+
+ // Setting to set how many requests per hour are allowed for the global rate limit.
+ // Should only be enabled when global rate limiting is enabled.
+ $settings->add(new admin_setting_configtext(
+ 'aiprovider_openai/globalratelimit',
+ new lang_string('globalratelimit', 'aiprovider_openai'),
+ new lang_string('globalratelimit_desc', 'aiprovider_openai'),
+ 100,
+ PARAM_INT,
+ ));
+ $settings->hide_if('aiprovider_openai/globalratelimit', 'aiprovider_openai/enableglobalratelimit', 'eq', 0);
+
+ // Setting to enable/disable user rate limiting.
+ $settings->add(new admin_setting_configcheckbox(
+ 'aiprovider_openai/enableuserratelimit',
+ new lang_string('enableuserratelimit', 'aiprovider_openai'),
+ new lang_string('enableuserratelimit_desc', 'aiprovider_openai'),
+ 0,
+ ));
+
+ // Setting to set how many requests per hour are allowed for the user rate limit.
+ // Should only be enabled when user rate limiting is enabled.
+ $settings->add(new admin_setting_configtext(
+ 'aiprovider_openai/userratelimit',
+ new lang_string('userratelimit', 'aiprovider_openai'),
+ new lang_string('userratelimit_desc', 'aiprovider_openai'),
+ 10,
+ PARAM_INT,
+ ));
+ $settings->hide_if('aiprovider_openai/userratelimit', 'aiprovider_openai/enableuserratelimit', 'eq', 0);
+```
diff --git a/versioned_docs/version-4.5/apis/plugintypes/antivirus/_files/scanner-php.mdx b/versioned_docs/version-4.5/apis/plugintypes/antivirus/_files/scanner-php.mdx
new file mode 100644
index 0000000000..0b614a3177
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/antivirus/_files/scanner-php.tsx b/versioned_docs/version-4.5/apis/plugintypes/antivirus/_files/scanner-php.tsx
new file mode 100644
index 0000000000..167d78e6ff
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/antivirus/index.mdx b/versioned_docs/version-4.5/apis/plugintypes/antivirus/index.mdx
new file mode 100644
index 0000000000..2b457d8e21
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/assign/_files/submission_settings.php b/versioned_docs/version-4.5/apis/plugintypes/assign/_files/submission_settings.php
new file mode 100644
index 0000000000..60e7a7248f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/assign/feedback.md b/versioned_docs/version-4.5/apis/plugintypes/assign/feedback.md
new file mode 100644
index 0000000000..8b050a157a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/assign/index.md b/versioned_docs/version-4.5/apis/plugintypes/assign/index.md
new file mode 100644
index 0000000000..120ab233f0
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/assign/submission.md b/versioned_docs/version-4.5/apis/plugintypes/assign/submission.md
new file mode 100644
index 0000000000..d33be4991b
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/assign/submission.md
@@ -0,0 +1,657 @@
+---
+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.
+
+#### 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-4.5/apis/plugintypes/atto/_examples/button.md b/versioned_docs/version-4.5/apis/plugintypes/atto/_examples/button.md
new file mode 100644
index 0000000000..e2b7e9c1b6
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/atto/_examples/button.tsx b/versioned_docs/version-4.5/apis/plugintypes/atto/_examples/button.tsx
new file mode 100644
index 0000000000..011667eb0c
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/atto/_examples/lib.md b/versioned_docs/version-4.5/apis/plugintypes/atto/_examples/lib.md
new file mode 100644
index 0000000000..b303cf986e
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/atto/_examples/lib.php b/versioned_docs/version-4.5/apis/plugintypes/atto/_examples/lib.php
new file mode 100644
index 0000000000..ef29ea076f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/atto/index.md b/versioned_docs/version-4.5/apis/plugintypes/atto/index.md
new file mode 100644
index 0000000000..62f9bff96c
--- /dev/null
+++ b/versioned_docs/version-4.5/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](https://docs.moodle.org/dev/Editors). 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.
+
+It is likely that Atto will be removed in Moodle 4.6.
+
+:::
+
+## 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-4.5/apis/plugintypes/availability/_examples/lang.md b/versioned_docs/version-4.5/apis/plugintypes/availability/_examples/lang.md
new file mode 100644
index 0000000000..4e73de046c
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/availability/_examples/lang.php b/versioned_docs/version-4.5/apis/plugintypes/availability/_examples/lang.php
new file mode 100644
index 0000000000..307d2e9ca6
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/availability/index.md b/versioned_docs/version-4.5/apis/plugintypes/availability/index.md
new file mode 100644
index 0000000000..1c5518eba1
--- /dev/null
+++ b/versioned_docs/version-4.5/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://tracker.moodle.org/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-4.5/apis/plugintypes/blocks/_examples/access.php b/versioned_docs/version-4.5/apis/plugintypes/blocks/_examples/access.php
new file mode 100644
index 0000000000..0b702aa8af
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/blocks/_examples/block_pluginname.php b/versioned_docs/version-4.5/apis/plugintypes/blocks/_examples/block_pluginname.php
new file mode 100644
index 0000000000..7482aa9f2c
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/blocks/_examples/edit_form.php b/versioned_docs/version-4.5/apis/plugintypes/blocks/_examples/edit_form.php
new file mode 100644
index 0000000000..0951869db3
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml b/versioned_docs/version-4.5/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml
new file mode 100644
index 0000000000..43bfafbab6
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/blocks/index.md b/versioned_docs/version-4.5/apis/plugintypes/blocks/index.md
new file mode 100644
index 0000000000..e625885a21
--- /dev/null
+++ b/versioned_docs/version-4.5/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 hte 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-4.5/apis/plugintypes/communication/index.md b/versioned_docs/version-4.5/apis/plugintypes/communication/index.md
new file mode 100644
index 0000000000..80b89180dd
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/customfield/_files/data_controller.php b/versioned_docs/version-4.5/apis/plugintypes/customfield/_files/data_controller.php
new file mode 100644
index 0000000000..8e2d30416a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/customfield/_files/data_controller.tsx b/versioned_docs/version-4.5/apis/plugintypes/customfield/_files/data_controller.tsx
new file mode 100644
index 0000000000..abc3371585
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/customfield/_files/field_controller.php b/versioned_docs/version-4.5/apis/plugintypes/customfield/_files/field_controller.php
new file mode 100644
index 0000000000..f2572e41d6
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/customfield/_files/field_controller.tsx b/versioned_docs/version-4.5/apis/plugintypes/customfield/_files/field_controller.tsx
new file mode 100644
index 0000000000..e4ace539f7
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/customfield/index.md b/versioned_docs/version-4.5/apis/plugintypes/customfield/index.md
new file mode 100644
index 0000000000..da5467c7d9
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/enrol/_examples/access.php b/versioned_docs/version-4.5/apis/plugintypes/enrol/_examples/access.php
new file mode 100644
index 0000000000..c7e1d3581e
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/enrol/_examples/enrol_lang.php b/versioned_docs/version-4.5/apis/plugintypes/enrol/_examples/enrol_lang.php
new file mode 100644
index 0000000000..b9e70e318a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/enrol/_examples/lib.php b/versioned_docs/version-4.5/apis/plugintypes/enrol/_examples/lib.php
new file mode 100644
index 0000000000..bb1b237d6b
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/enrol/_examples/message_lib.php b/versioned_docs/version-4.5/apis/plugintypes/enrol/_examples/message_lib.php
new file mode 100644
index 0000000000..8855bcafde
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/enrol/_examples/ui_lib.php b/versioned_docs/version-4.5/apis/plugintypes/enrol/_examples/ui_lib.php
new file mode 100644
index 0000000000..d56a1a65f3
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/enrol/index.md b/versioned_docs/version-4.5/apis/plugintypes/enrol/index.md
new file mode 100644
index 0000000000..d2e96fcfca
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/fileconverter/_files/converter.md b/versioned_docs/version-4.5/apis/plugintypes/fileconverter/_files/converter.md
new file mode 100644
index 0000000000..da4cf2ea22
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/fileconverter/_files/converter.tsx b/versioned_docs/version-4.5/apis/plugintypes/fileconverter/_files/converter.tsx
new file mode 100644
index 0000000000..a1a3702eac
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/fileconverter/index.md b/versioned_docs/version-4.5/apis/plugintypes/fileconverter/index.md
new file mode 100644
index 0000000000..c6fb303638
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/filter/_examples/dynamic.js b/versioned_docs/version-4.5/apis/plugintypes/filter/_examples/dynamic.js
new file mode 100644
index 0000000000..8b93652604
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/filter/_examples/filter.php b/versioned_docs/version-4.5/apis/plugintypes/filter/_examples/filter.php
new file mode 100644
index 0000000000..39c3961eb7
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/filter/index.md b/versioned_docs/version-4.5/apis/plugintypes/filter/index.md
new file mode 100644
index 0000000000..0d57a06bcc
--- /dev/null
+++ b/versioned_docs/version-4.5/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.
+
+### 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-4.5/apis/plugintypes/format/_examples/format.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/format.php
new file mode 100644
index 0000000000..3683a8ca54
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/format_lang.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/format_lang.php
new file mode 100644
index 0000000000..e5183221b2
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/lib.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/lib.php
new file mode 100644
index 0000000000..3c2e9152ea
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/lib_components.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/lib_components.php
new file mode 100644
index 0000000000..32d9a1a0ce
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/lib_course_index.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/lib_course_index.php
new file mode 100644
index 0000000000..dbb6109248
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/cmitem.mustache b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/cmitem.mustache
new file mode 100644
index 0000000000..7b0d80da79
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/cmitem.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/cmitem.php
new file mode 100644
index 0000000000..670db67e0c
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/content.mustache b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/content.mustache
new file mode 100644
index 0000000000..377189df1c
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/content.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/content.php
new file mode 100644
index 0000000000..6432366ea0
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/renderer.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/renderer.php
new file mode 100644
index 0000000000..e9821f2314
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/section.mustache b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/section.mustache
new file mode 100644
index 0000000000..3225b76066
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/output/section.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/output/section.php
new file mode 100644
index 0000000000..d1d8e4c64f
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/pluginskel_recipe.yaml b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/pluginskel_recipe.yaml
new file mode 100644
index 0000000000..a57d181216
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_examples/renderer.php b/versioned_docs/version-4.5/apis/plugintypes/format/_examples/renderer.php
new file mode 100644
index 0000000000..a29a9cad99
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/format/_files/course_editor_workflow.png b/versioned_docs/version-4.5/apis/plugintypes/format/_files/course_editor_workflow.png
new file mode 100644
index 0000000000..c4013b05fd
Binary files /dev/null and b/versioned_docs/version-4.5/apis/plugintypes/format/_files/course_editor_workflow.png differ
diff --git a/versioned_docs/version-4.5/apis/plugintypes/format/_files/course_format_output.png b/versioned_docs/version-4.5/apis/plugintypes/format/_files/course_format_output.png
new file mode 100644
index 0000000000..794c0dd84c
Binary files /dev/null and b/versioned_docs/version-4.5/apis/plugintypes/format/_files/course_format_output.png differ
diff --git a/versioned_docs/version-4.5/apis/plugintypes/format/index.md b/versioned_docs/version-4.5/apis/plugintypes/format/index.md
new file mode 100644
index 0000000000..7fb422efd9
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/format/index.md
@@ -0,0 +1,732 @@
+---
+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 |
+| `get_max_sections()` | Returns the maximum number of sections this format can contain |
+| `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:
+
+![Output classes structure](./_files/course_format_output.png)
+
+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:
+
+![Course editor workflow](./_files/course_editor_workflow.png)
+
+### 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"` |
+
+## 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-4.5/apis/plugintypes/format/migration.md b/versioned_docs/version-4.5/apis/plugintypes/format/migration.md
new file mode 100644
index 0000000000..2e017dfd9f
--- /dev/null
+++ b/versioned_docs/version-4.5/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:
+
+![Course editor workflow](./_files/course_editor_workflow.png)
+
+## 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:
+
+![Output classes structure](./_files/course_format_output.png)
+
+## 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-4.5/apis/plugintypes/index.md b/versioned_docs/version-4.5/apis/plugintypes/index.md
new file mode 100644
index 0000000000..49d939ee49
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/index.md
@@ -0,0 +1,141 @@
+---
+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](https://docs.moodle.org/dev/Editors) | 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)
+ ;
+}
+```
+
+
+
+## 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-4.5/apis/plugintypes/local/index.mdx b/versioned_docs/version-4.5/apis/plugintypes/local/index.mdx
new file mode 100644
index 0000000000..403785d5f8
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/logstore/index.md b/versioned_docs/version-4.5/apis/plugintypes/logstore/index.md
new file mode 100644
index 0000000000..3449ea77ac
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mlbackend/index.md b/versioned_docs/version-4.5/apis/plugintypes/mlbackend/index.md
new file mode 100644
index 0000000000..27de3c670a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png b/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png
new file mode 100644
index 0000000000..2d2b39bc1a
Binary files /dev/null and b/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png differ
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png b/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png
new file mode 100644
index 0000000000..0b6147facc
Binary files /dev/null and b/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png differ
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png b/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png
new file mode 100644
index 0000000000..b3aeb20f1d
Binary files /dev/null and b/versioned_docs/version-4.5/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png differ
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod/_files/access_description.md b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/access_description.md
new file mode 100644
index 0000000000..7fc9b95250
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/_files/index-php.mdx b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/index-php.mdx
new file mode 100644
index 0000000000..1ab22546c9
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/index-php.mdx
@@ -0,0 +1,2 @@
+
+The `index.php` should be used to list all instances of an activity that the current user has access to in the specified course.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod/_files/index-php.tsx b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/index-php.tsx
new file mode 100644
index 0000000000..caebf44fb2
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/index-php.tsx
@@ -0,0 +1,52 @@
+/**
+ * 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');
+
+// The \`id\` parameter is the course id.
+$id = required_param('id', PARAM_INT);
+
+// Fetch the requested course.
+$course = $DB->get_record('course', ['id'=> $id], '*', MUST_EXIST);
+
+// Require that the user is logged into the course.
+require_course_login($course);
+
+$modinfo = get_fast_modinfo($course);
+
+foreach ($modinfo->get_instances_of('[modinfo]') as $instanceid => $cm) {
+ // Display information about your activity.
+}
+`;
+
+export default (initialProps: Props): ComponentFileSummary => (
+
+);
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod/_files/install_description.md b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/install_description.md
new file mode 100644
index 0000000000..877ccac079
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/_files/lib_description.md b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/lib_description.md
new file mode 100644
index 0000000000..4daebbad07
--- /dev/null
+++ b/versioned_docs/version-4.5/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_create_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://tracker.moodle.org/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-4.5/apis/plugintypes/mod/_files/mod_form-php.mdx b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/mod_form-php.mdx
new file mode 100644
index 0000000000..c2bdec2fd8
--- /dev/null
+++ b/versioned_docs/version-4.5/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://tracker.moodle.org/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-4.5/apis/plugintypes/mod/_files/mod_form-php.tsx b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/mod_form-php.tsx
new file mode 100644
index 0000000000..71c4745642
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/_files/mod_form-php.txt b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/mod_form-php.txt
new file mode 100644
index 0000000000..046d87ffdb
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/_files/view-php.mdx b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/view-php.mdx
new file mode 100644
index 0000000000..96567c5f4a
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/_files/view-php.tsx b/versioned_docs/version-4.5/apis/plugintypes/mod/_files/view-php.tsx
new file mode 100644
index 0000000000..28a608e8c3
--- /dev/null
+++ b/versioned_docs/version-4.5/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-4.5/apis/plugintypes/mod/activitymodule.md b/versioned_docs/version-4.5/apis/plugintypes/mod/activitymodule.md
new file mode 100644
index 0000000000..75bc000555
--- /dev/null
+++ b/versioned_docs/version-4.5/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://tracker.moodle.org/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://tracker.moodle.org/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.
+
+![The starred tab](./_activitymodule/activity-chooser-starred.png)
+
+### 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.
+
+![The recommended tab](./_activitymodule/activity-chooser-recommend.png)
+
+### 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.
+
+![Additional information about an activity](./_activitymodule/activity-chooser-info.png)
+
+## 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-4.5/apis/plugintypes/mod/index.mdx b/versioned_docs/version-4.5/apis/plugintypes/mod/index.mdx
new file mode 100644
index 0000000000..b400fda0c9
--- /dev/null
+++ b/versioned_docs/version-4.5/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.
+
+
+
+## Standard Files and their Functions
+
+There are several files that are crucial to Moodle. These files are used to install your module and then integrate it into Moodle. Each file has a particular function, some of the files are optional, and are only created if you want to use the functionality it offers. Below are the list of most commonly used files.
+
+### Backup Folder
+
+
+
+### `access.php` - Capability defaults
+
+export const accessExample = `
+$capabilities = [
+ 'mod/[modname]:addinstance' => [
+ 'riskbitmask' => RISK_XSS,
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW,
+ ],
+ 'clonepermissionsfrom' => 'moodle/course:manageactivities',
+ ],
+ 'mod/[modname]:view' => [
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => [
+ 'guest' => CAP_ALLOW,
+ 'student' => CAP_ALLOW,
+ 'teacher' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW,
+ ],
+ ],
+];
+`;
+
+}
+/>
+
+### `events.php` - Event observers
+
+
+
+### `install.xml` - Database installation
+
+}
+/>
+
+### `upgrade.php` - Upgrade steps
+
+
+
+### `mobile.php` - Moodle Mobile Remote Add-ons
+
+
+
+### `/lang/en/[modname].php` - Language string definitions
+
+
+
+### `lib.php` - Library functions
+
+}
+/>
+
+### `mod_form.php` - Instance create/edit form
+
+
+
+### `index.php` - Instance list
+
+
+
+### `view.php` - View an activity
+
+
+
+### `version.php`
+
+
+
+## See also
+
+- [Tutorial](https://docs.moodle.org/dev/Tutorial)
+- [Module visibility and display](./visibility.md)
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod/visibility.md b/versioned_docs/version-4.5/apis/plugintypes/mod/visibility.md
new file mode 100644
index 0000000000..69ecf32237
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod/visibility.md
@@ -0,0 +1,127 @@
+---
+title: Module visibility and display
+tags:
+- API
+- Module
+- Activity
+- mod
+---
+
+## Summary
+
+A new API allows you to customise how your module displays on the main course page:
+
+- You can display custom HTML below the link to your module.
+- If your module does not have a link (like Label, where it is only for display on the main page) then you can remove the link from the main page and from all navigation etc.
+- You can display HTML next to the link to your module that indicates dynamic information (like Forum, where it displays information about unread messages).
+- You can display additional icons next to the other module editing icons when the user is editing the page.
+
+In addition, existing things you could already do (like change the icon on the main page) are still available when using the new API.
+
+The `get_fast_modinfo` function now returns specific classes which are documented and which you can use to obtain new information about modules.
+
+## Backward compatibility
+
+All modules and code written for Moodle 2.0 should continue to behave in exactly the same manner. There is no need to change existing modules for this API unless you want to use the new features.
+
+## Removing your link
+
+If your module should not appear in navigation and in other lists of modules to visit or get information for, like Label, the easiest way to remove that link is to return true for `FEATURE_NO_VIEW_LINK` in your module's `_supports` function.
+
+## Customising module display, in cache
+
+The first place you can customise your module display is in the existing `_get_coursemodule_info` API function. This function obtains information about the module which will be stored in the course cache (the `modinfo` field of the course table).
+
+The course cache is only updated when somebody edits a module, so it can't be used for dynamic information - but it's okay if it takes a few database queries to calculate the data because it will be cached for future use.
+
+The function should return a value of class `cached_cm_info`. For example:
+
+```php
+function mod_frog_get_coursemodule_info($cm) {
+ $info = new cached_cm_info();
+ $info->content = '
This will display below the module.
';
+ return $info;
+}
+```
+
+You can change several properties which are documented in that class definition. If you don't change a property, its value remains default.
+
+- `name` - name of activity (text of the link on course page).
+- `icon`, `iconcomponent` - name and component name of icon to display by the link.
+- `content` - extra HTML content to display below the module link on course page (not shown in navigation etc).
+- `customdata` - arbitrary extra PHP data to store in modinfo cache; useful if, for performance reasons, your module needs to store data that should be accessible very quickly from other parts of the course. Warning: Do not store complex objects here because when they are serialized (together with all other data) they may contain \0 byte and it causes fatal error on Postgres.
+- `extraclasses` - extra CSS class or classes that will be added to the activity on the main page, so that you can alter the styling.
+- `onclick` - already-escaped HTML that will be inserted as the value of the onclick attribute.
+
+If you don't need the information to be cached (it can be retrieved very quickly without making any database queries) then you might consider using one of the functions below instead, in order to avoid unnecessarily increasing the size of the course cache. Although the headings mention the current user, you can of course use those functions in a way that doesn't depend on the current user.
+
+Don't use renderers in this function (see MDL-41074). If you have data you would like to render onto the course page, put it into the custom data property and render it in the MODNAME_cm_info_view() function (see below). For an example, see mod_folder.
+
+## Customising module display, for current user
+
+You can customise module display dynamically (when the page loads). For example you might want to alter it based on the permissions of the current user.
+
+```php
+function mod_frog_cm_info_dynamic(cm_info $cm) {
+ $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+ if (!has_capability('some/capability', $context)) {
+ $cm->set_user_visible(false);
+ }
+}
+```
+
+This code can affect the navigation, and whether users are permitted to access the module (as above). It runs on all pages within the course, so it's very important that you do not put slow code in this function: it should not make any database queries.
+
+In addition to the `set_user_visible` function shown, you can also set many other things such as additional editing icons which will appear if editing mode is enabled. See the cm_info class documentation for more information.
+
+Most things are set using functions (as above; another example would be `set_content` which sets the same content data as mentioned in the previous section) while other things can be set directly using public variables.
+
+## Customising module display, for current user, on course page only
+
+Sometimes you need to display custom information for the current user that appears only on the course view page. For example, the forum module displays unread information on the view page. This information doesn't show on other pages (it isn't included in the navigation, for instance).
+
+```php
+function mod_frog_cm_info_view(cm_info $cm) {
+ $cm->set_after_link('Last tadpole: 22:17');
+}
+```
+
+Because this function only runs when looking at the course page:
+
+- It is OK to do tasks which may require some database queries (such as checking for unread forum messages), although obviously this should be kept to a minimum. In particular, care should be taken so that if there are 20 instances of the activity on the course page, it doesn't make 20 separate queries to obtain the information.
+
+- Inside this function you cannot set options which affect the appearance or access to the activity on other pages; for example, you cannot turn off the `uservisible` flag as shown in the previous example. This is because these options are required on other pages (e.g. to display navigation) so it does not make sense to set them only for the course page. If you try, you'll get a `coding_exception`.
+
+## get_fast_modinfo data
+
+The function `get_fast_modinfo` now returns an object of class course_modinfo, which itself contains cm_info objects about each activity. (These are entirely backward-compatible with the previous return value.)
+
+In addition to the old methods for obtaining data from $modinfo, there are some new functions. For example, here is how to get a single cm_info from $modinfo:
+
+```php
+$modinfo = get_fast_modinfo($course);
+$cm = $modinfo->get_cm($cmid);
+```
+
+The cm_info objects contain additional information that is not present in the course_modules database row, such as the module's name, and the icon and associated content mentioned above. In order to distinguish these from the plain database objects, you can specify the cm_info class in a function definition:
+
+```php
+function my_clever_function(cm_info $cm) {
+ if (!$cm->uservisible) {
+ // The module is not visible or available to current user,
+ // so do something clever instead.
+ }
+}
+```
+
+By specifying cm_info in the parameter list, you'll cause PHP to give an error if anyone tries to call that function with a $cm object that just came from the database row, instead of from `get_fast_modinfo`. (It is good practice to always get $cm from get_fast_modinfo, but there might be exceptions.)
+
+Of course, this is only necessary if your function relies on a feature that is specific to cm_info, such as the `uservisible` field above. If your function only uses fields which are present in the database row, then there's no need to require cm_info.
+
+## More documentation
+
+All three classes for this API are in the core file `lib/modinfolib.php` and contain complete PHPdoc information for all fields and functions.
+
+- [`course_modinfo` PHPdocs](http://phpdocs.moodle.org/HEAD/core/lib/course_modinfo.html)
+- [`cminfo` PHPdocs](http://phpdocs.moodle.org/HEAD/core/lib/cm_info.html)
+- [`cached_cm_info` PHPdocs](http://phpdocs.moodle.org/HEAD/core/lib/cached_cm_info.html)
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod_book/index.md b/versioned_docs/version-4.5/apis/plugintypes/mod_book/index.md
new file mode 100644
index 0000000000..a28f112e96
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod_book/index.md
@@ -0,0 +1,10 @@
+---
+title: Book activity sub-plugins
+tags:
+ - Book
+ - Subplugin
+ - Plugintype
+documentationDraft: true
+---
+
+The `mod_book` activity can be extended using the tool sub-plugin type. This hasn't been documented yet - perhaps you are able to help us.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod_data/fields.md b/versioned_docs/version-4.5/apis/plugintypes/mod_data/fields.md
new file mode 100644
index 0000000000..3d1bf3e009
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod_data/fields.md
@@ -0,0 +1,81 @@
+---
+title: Database fields
+tags:
+ - mod_data
+ - datafield
+ - plugintype
+ - subplugin
+documentationDraft: true
+---
+
+The [Database activity](https://docs.moodle.org/en/Database_module) included with Moodle includes support for several predefined [field types](./fields.md), including text, date, and URL. It is also possible to create new field types. For example, you might like to create:
+
+- Discipline-specific field types - For example "Protein PDB code": users can enter the PDB code for a protein, and then the display 3D viewer for the protein structure, or link out to molecular databases.
+- Institution-specific field types - For example "library reference number": Allow users to enter a reference number which can be automatically turned into a direct link for local library services.
+- Module-specific field types - For example "wiki page": users see a drop-down list containing the names of pages in your wiki, and can choose which page this particular entry refers to.
+
+import { ComponentFileSummary } from '../../../_utils';
+
+## File structure
+
+Database field sub-plugins are located in the `/mod/data/field` 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 `datafield_number` subplugin.
+
+```console
+mod/data/field/number
+├── classes
+│ └── privacy
+│ └── provider.php
+├── field.class.php
+├── lang
+│ └── en
+│ └── datafield_number.php
+├── mod.html
+└── version.php
+```
+
+
+
+Some of the important files for the database field plugintype are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin.
+
+### Field class
+
+
+
+The field, its behaviours, and its properties, are defined in a class named `data_field_[pluginname]` located in `field.class.php`. This class must extend the `data_field_base` base class.
+
+:::danger Class locations
+
+The field definition is currently located in the `field.class.php` file and is not yet autoloaded by Moodle.
+
+:::
+
+The base class defines some simple behaviours which you can override in your plugin. The following functions are of particular interest:
+
+- `display_add_field($recordid = 0)` - Return some HTML for use when a user is adding/editing a record
+- `display_browse_field($recordid, $template)` - Return some HTML for displaying a record
+- `update_content($recordid, $value, $name = '')` - Store the data entered by a user for a record
+- `get_sort_sql($fieldname)` - Specify SQL for how this field should be sorted
+- `get_content_value($value)` - Useful if the info stored in the database if different from the info that ends up being presented to the user
+
+### Field configuration form
+
+
+
+:::danger
+
+The field definition is one of the older parts of Moodle and does not use best practice.
+
+:::
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod_data/index.md b/versioned_docs/version-4.5/apis/plugintypes/mod_data/index.md
new file mode 100644
index 0000000000..bca2e13820
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod_data/index.md
@@ -0,0 +1,12 @@
+---
+title: Database activity sub-plugins
+tags:
+ - Database
+ - Subplugin
+ - Plugintype
+---
+
+The `mod_data` activity can be extended using two sub-plugin types, namely:
+
+- [Database field types](./fields.md), used to create custom field data types; and
+- [Database presets](./presets.md), a legacy plugintype used to share configurations.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod_data/presets.md b/versioned_docs/version-4.5/apis/plugintypes/mod_data/presets.md
new file mode 100644
index 0000000000..38034887db
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod_data/presets.md
@@ -0,0 +1,15 @@
+---
+title: Database presets
+tags:
+ - mod_data
+ - datapreset
+ - subplugin
+ - deprecated
+---
+
+
+The database preset subplugin type is no longer recommended.
+
+## See also
+
+[en](https://docs.moodle.org/en/Database_presets)
diff --git a/versioned_docs/version-4.5/apis/plugintypes/mod_forum/index.md b/versioned_docs/version-4.5/apis/plugintypes/mod_forum/index.md
new file mode 100644
index 0000000000..6dd3127421
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/mod_forum/index.md
@@ -0,0 +1,10 @@
+---
+title: Forum activity sub-plugins
+tags:
+ - Forum
+ - Subplugin
+ - Plugintype
+documentationDraft: true
+---
+
+The `mod_fporum` activity can be extended using the `forumreport` sub-plugin type. This hasn't been documented yet - perhaps you are able to help us.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/qbank/index.md b/versioned_docs/version-4.5/apis/plugintypes/qbank/index.md
new file mode 100644
index 0000000000..655b570ee6
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/qbank/index.md
@@ -0,0 +1,26 @@
+---
+title: Question bank plugins
+tags:
+ - Plugins
+ - Question
+ - qbank
+description: Question bank plugins allow you to extend the functionality of the Moodle Question bank.
+documentationDraft: true
+---
+
+
+
+Question bank plugins allow you to extend the functionality of the Moodle Question bank. They just one of the plugin types used by core_question. To see how they fit in, please read [this overview of the question subsystems](../subsystems/question/).
+
+Question bank plugins can extend the question bank in many ways, including:
+
+- Table columns
+- Action menu items
+- Bulk actions
+- Navigation node (tabs)
+- Question preview additions (via callback)
+
+The place to start implementing most of these is with a class `classes/plugin_features.php` in your plugin, that declares which features you want to add to the question bank. Until more documentation is written, looking at the examples of the plugins in Moodle core should give you a good idea what you need to do.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/qtype/index.md b/versioned_docs/version-4.5/apis/plugintypes/qtype/index.md
new file mode 100644
index 0000000000..94bbc39029
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/qtype/index.md
@@ -0,0 +1,27 @@
+---
+title: Question type plugins
+tags:
+ - Plugins
+ - Question
+ - qtype
+description: Question type plugins implement the different types of question that the core Question subsystem can handle.
+---
+
+Question types are one of the plugins used by the question subsystem. To see how they fit in, please read [this overview of the question subsystems](../subsystems/question/).
+
+Question types have to do many things:
+
+1. `edit_..._form.php` - Provide an editing form so that teachers can create and edit questions of this type.
+2. `questiontypes.php` - Define a class to handle loading and saving data from this form.
+3. ... and related methods providing metadata about this question types.
+4. ... and import and export in any Question formats that the type wants to support.
+5. `question.php` - this class represents one instance of this question type, while it is being attempted by a user. It must do many things
+6. ... Start a new attempt (e.g. in a multiple choice question, this is where we randomly shuffle the choices).
+7. ... or if we are continuing an existing attempt, re-initialise the question to the same state, using the data from the DB.
+8. ... Tell the question engine what data this question type is expecting to be submitted.
+9. ... Analyse those submitted responses: e.g. has it changed? is it complete.
+10. ... Automatically grade the response to give a 'fraction' (mark between 0 and 1) and a state (correct / partially correct / incorrect).
+11. ... check access to files for the file API.
+12. `renderer.php` - to display the key bits of this question types for the `core_question_renderer` to combine into the overall question display.
+13. Implements Backup and restore, and all the other standard parts of a Moodle plugin like DB tables.
+14. Track [users preferences for the settings used for newly created questions](./qtype/newquestiondefaults).
diff --git a/versioned_docs/version-4.5/apis/plugintypes/qtype/newquestiondefaults.md b/versioned_docs/version-4.5/apis/plugintypes/qtype/newquestiondefaults.md
new file mode 100644
index 0000000000..58cdb0b881
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/qtype/newquestiondefaults.md
@@ -0,0 +1,67 @@
+---
+title: Defaults for new questions
+tags:
+ - Plugins
+ - Question
+ - qtype
+description: A way for question types to remember a user's preferred settings for creating questions of a given type.
+documentationDraft: true
+---
+
+## Introduction
+
+Many question types are quite flexible, and so have a lot of options on their editing form. Quite often, when a teacher is creating a number of questions, it is likely they will want to keep using the same values for some options. Therefore, the question system has a way for question types to save some settings as user-preferences, and then use them as the default when creating a new question.
+
+Note, this is only done when a teacher creates and saves a new question. We don't save the preferences when a teacher edits an existing question (which might have been created by someone else with different preferences).
+
+## How to implement this feature
+
+### Decide which settings should be saved
+
+It is not appropriate to save all the settings. For example, name and question text are what uniquely define a particular question. It would be unhelpful to remember and re-use these since they need to be different each time.
+
+The kind of settings we want to save are the ones like do you want the choices in your multiple-choice question numbered 'a, b, c, ...' or '1, 2, 3, ...' or not numbered at all? As you think about this, looking through what other question types do is probably a good way to get a feel for what sorts of things it makes sense to remember. That will also promote consistency. Search for implementations of `save_defaults_for_new_questions`.
+
+### In the form class - use any previously saved defaults
+
+Before implementing this feature, your form class is likely to have code like
+
+```php
+$mform->setDefault('shuffleanswers', 1);
+```
+
+For all the settings where you want to implement this feature, need to change the hard-coded default (`1` here) to instead fetch the default from the user's preferences using the `get_default_value` method:
+
+```php
+$mform->setDefault('shuffleanswers', $this->get_default_value('shuffleanswers', 1));
+```
+
+### In the question-type class
+
+Here we need to override the method `save_defaults_for_new_questions` to save the values these settings. For example:
+
+```php
+ public function save_defaults_for_new_questions(stdClass $fromform): void {
+ parent::save_defaults_for_new_questions($fromform);
+ $this->set_default_value('shuffleanswers', $fromform->shuffleanswers);
+ }
+```
+
+All the settings save here should match the ones fetched by `get_default_value` in the form. You need to call `parent` because Moodle core saves some settings that apply to all question types.
+
+### Privacy provider
+
+Because this feature works using user preferences, you need to declare that in your privacy provider.
+
+This is boring but necessary. Easiest way to see what to do is to [copy another question type](https://github.com/moodle/moodle/blob/main/question/type/match/classes/privacy/provider.php).
+
+Note, it is necessary for your provider to declare the ones saved by core. (I suppose, ideally, someone would make a helpful base class, or trait, to make it easier to implement this.)
+
+### Automated tests
+
+Always a good idea. You are likely to need:
+
+1. [Unit tests for the privacy provider](https://github.com/moodle/moodle/blob/main/question/type/match/tests/privacy/provider_test.php).
+2. Behat test to show that the saved settings are re-used. Many question types have [a `behat/add.feature` file where it is easy to add coverage for this](https://github.com/moodle/moodle/blob/main/question/type/match/tests/behat/add.feature).
+
+The links in that list go to examples of how these are implemented in `qtype_match`.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/access.php b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/access.php
new file mode 100644
index 0000000000..e66eceab49
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/access.php
@@ -0,0 +1,13 @@
+
+$capabilities = [
+ // Ability to use the plugin.
+ 'repository/pluginname:view' => [
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => [
+ 'coursecreator' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW
+ ]
+ ],
+];
diff --git a/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/flickr_public_lib.php b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/flickr_public_lib.php
new file mode 100644
index 0000000000..0cabfe38fe
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/flickr_public_lib.php
@@ -0,0 +1,100 @@
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/repository/lib.php');
+
+class repository_flickr_public extends repository {
+
+ // User specific settings.
+
+ /**
+ * Options names.
+ *
+ * Tell the API that the repositories have specific settings: "email address"
+ *
+ * @return string[] of options.
+ */
+ public static function get_instance_option_names() {
+ return ['email_address'];
+ }
+
+ /**
+ * Repository configuration form.
+ *
+ * Add an "email address" text box to the create/edit repository instance Moodle form
+ *
+ * @param moodleform $mform Moodle form
+ */
+ public static function instance_config_form($mform) {
+ $mform->addElement(
+ 'text',
+ 'email_address',
+ get_string('emailaddress', 'repository_flickr_public')
+ );
+ $mform->addRule(
+ 'email_address',
+ get_string('required'),
+ 'required',
+ null,
+ 'client'
+ );
+ }
+
+ // Global repository plugin settings.
+
+ /**
+ * Repository global settings names.
+ *
+ * We tell the API that the repositories have general settings: "api_key"
+ *
+ * @return string[] of options.
+ */
+ public static function get_type_option_names() {
+ return array('api_key');
+ }
+
+ /**
+ * Repository global settings form.
+ *
+ * We add an "api key" text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)
+ *
+ * @param moodleform $mform Moodle form
+ */
+ public function type_config_form($mform) {
+ //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form
+ $api_key = get_config('flickrpublic', 'api_key');
+
+ $mform->addElement(
+ 'text',
+ 'api_key',
+ get_string('apikey', 'repository_flickr_public'),
+ ['value' => $api_key, 'size' => '40']
+ );
+ $mform->addRule(
+ 'api_key',
+ get_string('required'),
+ 'required',
+ null,
+ 'client'
+ );
+ }
+
+ // Method called when the repostiroy plugin is installed.
+
+ /**
+ * Plugin init method.
+ *
+ * this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.
+ */
+ public static function plugin_init() {
+ //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.
+ repository::static_function(
+ 'flickrpublic',
+ 'create',
+ 'flickrpublic',
+ 0,
+ context_system::instance(),
+ ['name' => 'default instance', 'email_address' => null],
+ 1
+ );
+ }
+}
diff --git a/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/full_list.jsonc b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/full_list.jsonc
new file mode 100644
index 0000000000..d1fae44c77
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/full_list.jsonc
@@ -0,0 +1,68 @@
+// Example of a list array with all the element types.
+{
+ // 'path' is used to build navigation bar to show the current folder, so you need to include all parents folders
+ // array(array('name'=>'root','path'=>'/'), array('name'=>'subfolder', 'path'=>'/subfolder'))
+ // This will result in: /root/subfolder as current directory
+ "path": (array), // this will be used to build navigation bar,
+
+ // 'dynload' tells file picker to fetch list dynamically.
+ // When user clicks the folder, it will send a ajax request to server side.
+ // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true
+ "dynload": (bool), // use dynamic loading,
+
+ // If you are using pagination, 'page' and 'pages' parameters should be set.
+ // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly
+ "page": (int), // which page is this list,
+ "pages": (int), // how many pages. If number of pages is unknown but we know that the next page exists repository may return -1,
+ "manage": (string), // url to file manager for the external repository, if specified will display link in file picker,
+ "help": (string), // url to the help window, if specified will display link in file picker,
+ "nologin": (bool), // requires login, default false, if set to true the login link will be removed from file picker,
+ "norefresh": (bool), // no refresh button, default false,
+ "logouttext": (string), // in case of nologin=false can substitute the text 'Logout' for logout link in file picker,
+ "nosearch": (bool), // no search link, default false, if set to true the search link will be removed from file picker,
+ "issearchresult": (bool), // tells that this listing is the result of search,
+ // for repositories that actually upload a file: set 'upload' option to display an upload form in file picker
+ "upload": { // upload manager
+ "label": (string), // label of the form element,
+ "id": (string) // id of the form element,
+ },
+ // 'list' is used by file picker to build a file/folder tree
+ "list": {
+ { // file
+ "title": (string), // file name,
+ "shorttitle": (string), // optional, if you prefer to display a short title
+ "date": (int), // UNIX timestamp, default value for datemodified and datecreated,
+ "datemodified": (int), // UNIX timestamp when the file was last modified [ 'datecreated' => (int) UNIX timestamp when the file was last created [2.3+](2.3+],
+ "size": (int), // file size in bytes,
+ "thumbnail": (string), // url to thumbnail for the file,
+ "thumbnail_width": (int), // the width of the thumbnail image,
+ "thumbnail_height": (int), // the height of the thumbnail image,
+ "source": (string), // plugin-dependent unique path to the file (id, url, path, etc.),
+ "url": (moodle_url), // the accessible url of file,
+ "icon": (string), // url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [ 'realthumbnail' => (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as 'thumbnail') [2.3+](2.3+],
+ "realicon": (string), // url to image preview in icon size (24x24) [ 'author' => (string) default value for file author,
+ "license": (string), // default value for license (short name, see class license_manager),
+ "image_height": (int), // if the file is an image, image height in pixels, null otherwise [2.3+](2.3+],
+ "image_width": (int) // if the file is an image, image width in pixels, null otherwise [ ),
+ },
+ { // folder - similar to file, has also 'path' and 'children' but no 'source' or 'url'
+ "title": (string), // folder name,
+ "shorttitle": (string), // optional, if you prefer to display a short title
+ "path": (string), // path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children
+ "date": (int),
+ "datemodified": (int),
+ "datecreated": (int),
+ "thumbnail": (string),
+ "icon": ,// see above,
+ "children": [
+ // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array
+ // otherwise it is a nested list of contained files and folders
+ ]
+ }
+ },
+ // The 'object' tag can be used to embed an external web page or application within the filepicker
+ "object": {
+ "type": (string), // e.g. 'text/html', 'application/x-shockwave-flash'
+ "src": (string), // the website address to embed in the object
+ }
+}
diff --git a/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/lib.php b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/lib.php
new file mode 100644
index 0000000000..366cb2711d
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/repository/_examples/lib.php
@@ -0,0 +1,64 @@
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/repository/lib.php');
+
+class repository_pluginname extends repository {
+ /**
+ * Get file listing.
+ *
+ * This is a mandatory method for any repository.
+ *
+ * See repository::get_listing() for details.
+ *
+ * @param string $encodedpath
+ * @param string $page
+ * @return array the list of files, including meta information
+ */
+ public function get_listing($encodedpath = '', $page = '') {
+ // This methods
+ return array('list' => []);
+ }
+
+ /**
+ * Is this repository used to browse moodle files?
+ *
+ * @return boolean
+ */
+ public function has_moodle_files() {
+ return true;
+ }
+
+ /**
+ * Tells how the file can be picked from this repository.
+ *
+ * @return int
+ */
+ public function supported_returntypes() {
+ return FILE_INTERNAL | FILE_REFERENCE;
+ }
+
+ /**
+ * Which return type should be selected by default.
+ *
+ * @return int
+ */
+ public function default_returntype() {
+ return FILE_INTERNAL;
+ }
+
+
+ /**
+ * Optional method for searching files in the repository.
+ *
+ * @param string $search
+ * @param int $page
+ * @return array the list of found files.
+ */
+ public function search($search, $page = 0) {
+ $ret = [];
+ $ret['nologin'] = true;
+ // The found files list.
+ $ret['list'] = [];
+ return $ret;
+ }
+}
diff --git a/versioned_docs/version-4.5/apis/plugintypes/repository/_files/options.png b/versioned_docs/version-4.5/apis/plugintypes/repository/_files/options.png
new file mode 100644
index 0000000000..f49b9288e1
Binary files /dev/null and b/versioned_docs/version-4.5/apis/plugintypes/repository/_files/options.png differ
diff --git a/versioned_docs/version-4.5/apis/plugintypes/repository/index.md b/versioned_docs/version-4.5/apis/plugintypes/repository/index.md
new file mode 100644
index 0000000000..839e22ecfc
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/repository/index.md
@@ -0,0 +1,966 @@
+---
+title: Repository plugins
+tags:
+ - Repositories
+ - Plugins
+---
+
+import {
+ DbAccessPHP,
+ Lang,
+ Lib,
+ VersionPHP,
+} from '../../_files';
+
+Repository plugin allow Moodle to bring contents into Moodle from external repositories.
+
+### Prerequisites
+
+Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.
+
+The 2 different parts to write in order to implement a full repository:
+
+1. Administration - You can customise the way administrators and users can configure their repositories.
+2. File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.
+
+## File structure
+
+Repository plugins are located in the `/repository` 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 `repository_pluginname` plugin.
+
+```console
+ repository/pluginname/
+ |-- db
+ | `-- access.php
+ |-- lang
+ | `-- en
+ | `-- repository_pluginname.php
+ |-- lib.php
+ |-- pix
+ | `-- icon.png
+ `-- version.php
+```
+
+
+
+Some of the important files for the repository 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
+
+
+
+### lang/en/repository_pluginname.php
+
+
+export const langExample = `
+$string['pluginname']= 'Example repository';
+$string['configplugin'] = 'Configuration for Example repository';
+$string['pluginname_help'] = 'A repository description';
+`;
+
+
+
+### lib.php
+
+import RepositoryLibExample from '!!raw-loader!./_examples/lib.php';
+
+
+
+This file contains the main repository class definition, which must extend the core `\repository` class. By extending the base class and overriding some of the class methods, the plugin can configure standard features and behaviours. See [Administration API](../../subsystems/admin/index.md) for more information.
+
+
+
+### db/access.php
+
+import RepositoryAccessExample from '!!raw-loader!./_examples/access.php';
+
+
+
+## Repository API methods
+
+Repository plugins can present, store, and link files in different ways depending on the type of remote system that the repository connects to.
+
+Some of the key API functions are described below.
+
+### supported_returntypes(): int
+
+Return any combination of the following values:
+
+- `FILE_INTERNAL` - the file is stored within the Moodle file system.
+- `FILE_EXTERNAL` - the file is stored in the external repository. It is not stored within the Moodle file system. When accessing the file it is returned from the remote system.
+- `FILE_REFERENCE` - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original.
+- `FILE_CONTROLLED_LINK` - the file remains in the external repository, and ownership of the file in the remote system is changed to the [system account in the external repository](https://docs.moodle.org//en/OAuth\_2\_services). When the file is accessed, the system account is responsible for granting access to users.
+
+
+
+
+> The return types that your plugin supports will be presented as options to the user when they are adding a file from the file picker. The `FILE_EXTERNAL` option is not reflected in this list as this is an internal feature of your API.
+>
+> ![Supported returntypes options settings](./_files/options.png)
+
+
+
+
+```php
+function supported_returntypes() {
+ return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK;
+}
+```
+
+
+
+
+The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot above, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from `FILE_INTERNAL`, `FILE_REFERENCE`, and `FILE_CONTROLLED_LINK` being present. `FILE_REFERENCE` corresponds to the "alias/shortcut" option.
+
+The option `FILE_EXTERNAL` is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, `FILE_EXTERNAL` is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support `FILE_EXTERNAL`.
+
+This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows `FILE_EXTERNAL` and `FILE_REFERENCE`.
+
+In the end, which type is
+used by Moodle depends on the choices made by the end user (for example inserting a link, will result in `FILE_EXTERNAL`-related functions being used, using a 'shortcut/alias' will result in the '`FILE_REFERENCE`'-related functions being used).
+
+### supported_filetypes()
+
+If your plugin only supports certian file types, then you should implement the optional `supported_filetypes()` method.
+
+This method is used to hide repositories when they don't support certain file types - for example, if a user is inserting a video then any repository which does not support videos will not be shown.
+
+Supported file types can be specified using standard mimetypes (such as `image/gif`) or file groups (such as `web_image`). For a full list of the supported mimetypes and groups, see the [`core_filetypes`](https://github.com/moodle/moodle/blob/v4.0.0/lib/classes/filetypes.php#L47) class.
+
+
+
+
+```php
+function supported_filetypes() {
+ // Allow any kind of file.
+ return '*';
+}
+```
+
+
+
+
+```php
+function supported_filetypes() {
+ // Example of image mimetypes.
+ return ['image/gif', 'image/jpeg', 'image/png'];
+}
+```
+
+
+
+
+```php
+function supported_filetypes() {
+ // Example of a file group.
+ return ['web_image'];
+}
+```
+
+
+
+
+### Course and User Repository Instances.
+
+A system-wide instance of a repository is created when it is enabled. It is also possible to support both course, and user specific repositories.. This can be achieved by setting the `enablecourseinstances` and `enableuserinstances` options. There are three ways that this can be done:
+
+1. Define **$string\['enablecourseinstances'\]** and **$string\['enableuserinstances'\]** in your plugin's language file. You can check an example in the [filesystem repository](https://github.com/moodle/moodle/blob/v4.0.0/repository/filesystem/lang/en/repository_filesystem.php).
+2. The plugin must provide a **get_instance_option_names** method which returns at least one instance option name. This method defined the specific instances options, if none instance attribute is needed, the system will not allow the plugin to define course and user instances. Note, you must not define the form fields for these options in the **type_config_form()** function. For example, [filesystem repository](https://github.com/moodle/moodle/blob/v4.0.0/repository/filesystem/lib.php#L439).
+3. Several 'core' repositories use the **db/install.php** to create the original repository instance by constructing an instance of the **repository_type** class. The options can be defined in the array passed as the second parameter to the constructor. For example [Wikipedia repository](https://github.com/moodle/moodle/blob/v4.0.0/repository/wikimedia/db/install.php).
+
+### Developer-defined API
+
+These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration > Plugins > Repositories > pluginname page.
+
+#### get_type_option_names(): array
+
+This function must be declared static
+
+Optional. Return an array of string. These strings are setting names. These settings are shared by all instances.
+Parent function returns an array with a single item - pluginname.
+
+
+ View example
+
+
+
+#### type_config_form($mform, $classname='repository')
+
+This function must be declared static
+
+Optional. This is for modifying the Moodle form displaying the plugin settings. The [Form Definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) documentation has details of all the types of elements you can add to the settings form.
+
+
+ View example
+
+
+For example, to display the standard repository plugin settings along with the custom ones use:
+
+```php
+public static function type_config_form($mform, $classname='repository') {
+ parent::type_config_form($mform);
+
+ $rootpath = get_config('repository_pluginname', 'rootpath');
+ $mform->addElement('text', 'rootpath', get_string('rootpath', 'repository_pluginname'), array('size' => '40'));
+ $mform->setDefault('rootpath', $rootpath);
+}
+```
+
+
+
+
+#### type_form_validation($mform, $data, $errors)
+
+This function must be declared static
+
+Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided ('settingname' => value) for any errors. Then push the items to $error array in the format ("fieldname" => "human readable error message") to have them highlighted in the form.
+
+```php
+public static function type_form_validation($mform, $data, $errors) {
+ if (!is_dir($data['rootpath'])) {
+ $errors['rootpath'] = get_string('invalidrootpath', 'repository_pluginname');
+ }
+ return $errors;
+}
+```
+
+### Instance settings
+
+These functions relate to a specific instance of your plugin (for example the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single 'name' field.
+
+#### get_instance_option_names(): array
+
+This function must be declared static
+
+Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance.
+
+If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.
+
+Parent function returns an empty array. This is equivalent to **get_type_option_names()**, but for a specific instance.
+
+
+ View example
+
+
+```php
+public static function get_instance_option_names() {
+ return ['fs_path']; // From repository_filesystem
+}
+```
+
+
+
+
+#### instance_config_form($mform)
+
+This function must be declared static
+
+Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to **type_config_form($mform, $classname)** but for instances. The [Form Definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) documentation has details of all the types of elements you can add to the settings form.
+
+
+ View example
+
+
+For example, to add a required text box called email_address:
+
+```php
+public static function get_instance_option_names() {
+ $mform->addElement(
+ 'text',
+ 'email_address',
+ get_string('emailaddress', 'repository_pluginname')
+ );
+ $mform->addRule('email_address', $strrequired, 'required', null, 'client');
+}
+```
+
+
+
+
+:::note
+
+**mform** has by default a name text box (cannot be removed).
+
+:::
+
+#### instance_form_validation($mform, $data, $errors)
+
+This function must be declared static
+
+Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to ''type_form_validation($mform, $data, $errors), but for instances. For example:
+
+```php
+public static function instance_form_validation($mform, $data, $errors) {
+ if (empty($data['email_address'])) {
+ $errors['email_address'] = get_string('invalidemailsettingname', 'repository_flickr_public');
+ }
+}
+```
+
+#### Getting / updating settings
+
+Both global and instance settings can be retrieved, from within the plugin, via **$this->get_option('settingname')** and updated via **$this->set_option(array('settingname' => 'value'))**.
+
+:::note
+
+You cannot call **$this** from static methods. If you need access the non static variables, you may have to store the values in the **_construct()** method into private static variables.
+
+:::
+
+#### plugin_init()
+
+This function must be declared static.
+
+Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.
+
+Parent function does nothing.
+
+### Example of using the settings
+
+As an example, let's create a Flickr plugin for accessing a public flickr account. The plugin will be called "Flickr Public".
+
+Firstly the skeleton:
+
+```php title="repository/flickr_public/lib.php"
+
+
+import FlickPublicLib from '!!raw-loader!./_examples/flickr_public_lib.php';
+
+
+
+## Repository APIs
+
+### Quick Start
+
+The File Picker uses Ajax calls to present the repository content. In order to integrate a repository with the the Ajax callbacks there are several possibilities:
+
+- When a plugin requires a special user login (for example OAuth) the plugin must detect user session in the `constructor()` function, and use `print_login()` if required.
+- For plugins that need to connect to a remote repository the connections can be done into the `get_listing()` or `constructor()` function.
+- To retrieve the file that the user selected from a remote server, the plugin must rewrite the `get_file()` method.
+- To provide search feature the plugin must rewrite the `search()` method.
+
+All those methods are descrived below.
+
+### Functions you MUST override
+
+These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin.
+
+#### __construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0)
+
+Should be overridden to do any initialisation required by the repository, including:
+
+- logging in via optional_param, if required - see 'print_login', below
+- getting any options from the database
+
+The possible items in the $options array are:
+
+- 'ajax' - bool, true if the user is using the AJAX filepicker
+- mimetypes' - array of accepted mime types, or '\*' for all types
+
+Calling parent::\__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables:
+
+- this->id - the repository instance id (the ID of the entry in mdl_repository_instances)
+- this->context - the context in which the repository instance can be found
+- this->instance - the repository instance record (from mdl_repository_instances)
+- this->readonly - whether or not the settings can be changed
+- this->options - the above options, combined with the settings saved in the database
+- this->name - as specified by $this->get_name()
+- this->returntypes - as specified by $this->supported_returntypes()
+
+#### get_listing($path="", $page="")
+
+This function will return a list of files to be displayed to the user, the list must be a array.
+
+
+ View example
+
+
+```php
+/**
+ * Get file listing.
+ *
+ * This is a mandatory method for any repository.
+ *
+ * See repository::get_listing() for details.
+ *
+ * @param string $encodedpath
+ * @param string $page
+ * @return array the list of files, including meta information
+ */
+public function get_listing($encodedpath = '', $page = '') {
+ // This methods
+ return [
+ //this will be used to build navigation bar.
+ 'path'=>[
+ [
+ 'name'=>'root'
+ 'path'=>'/'
+ ],
+ [
+ 'name'=>'subfolder',
+ 'path'=>'/subfolder'
+ ],
+ ],
+ 'manage'=>'http://webmgr.moodle.com',
+ 'list'=> [
+ [
+ 'title'=>'filename1',
+ 'date'=>'1340002147',
+ 'size'=>'10451213',
+ 'source'=>'http://www.moodle.com/dl.rar',
+ ],
+ [
+ 'title'=>'folder',
+ 'date'=>'1340002147',
+ 'size'=>'0',
+ 'children'=>[],
+ ],
+ ],
+ ];
+}
+```
+
+
+
+
+Amongst other details, this returns a **title** for each file (to be displayed in the filepicker) and the **source** for the file (which will be included in the request to 'download' the file into Moodle or to generate a link to the file). Directories return a **children** value, which is either an empty array (if 'dynload' is specified) or an array of the files and directories contained within it.
+
+
+ The full specification of list element
+
+
+import FullList from '!!raw-loader!./_examples/full_list.jsonc';
+
+{FullList}
+
+
+
+
+### Dynamically loading
+
+Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won't be listed until user click the folder in file picker treeview.
+
+As a plug-in developer, if you set dynload flag as **true**, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree.
+
+The use of the **object** tag, instead of returning a **list** of files, allows you to embed an external file chooser within the repository panel. See [Repository plugins embedding external file chooser](https://docs.moodle.org/dev/Repository_plugins_embedding_external_file_chooser) for details about how to do this.
+
+### User login (optional)
+
+If our plugin allows a user to log in to a remote service, you can support this using the `print_login()` and `check_login` function, which are desribed below.
+
+#### print_login
+
+For plugins which need to support login to a remote service, the `print_login()` function can be used to return an array of the form elements needed to support the login.
+
+
+ View example
+
+
+:::note
+
+It is important to note that the repository login can be called on both Ajax and non ajax requests. For this reason the `print_login()` should check for `$this->options['ajax']` to know if it should return an array or the full login HTML form.
+
+:::
+
+```php
+public function print_login() { // From repository_pluginname
+ global $OUTPUT;
+
+ if ($this->options['ajax']) {
+ $user_field = (object) [
+ 'label' => get_string('username', 'repository_pluginname'),
+ 'id' => 'pluginname_username',
+ 'type' => 'text',
+ 'name' => 'al_username',
+ ];
+
+ $passwd_field = (object) [
+ 'label' => get_string('password', 'repository_pluginname'),
+ 'id' => 'pluginname_password',
+ 'type' => 'password',
+ 'name' => 'al_password',
+ ];
+
+ $ret = [];
+ $ret['login'] = [$user_field, $passwd_field];
+ return $ret;
+ } else { // Non-AJAX login form - directly output the form elements.
+ // Print the login form HTML including the input username and password fields.
+ $loginform = new repository_pluginname\output\login();
+ echo $OUTPUT->render($loginform);
+ // Example of a login form:
+ //
+ //
+ //
+ //
+ //
+ }
+}
+```
+
+
+
+
+This will help to generate a form by file picker which contains user name and password input elements.
+
+If your login form is static and never changes, you can add `$ret['allowcaching']) = true;` and filepicker will not send the request to the server every time user opens the login/search form.
+
+For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the `__construct` function, via `$submitted = optional_param('fieldname', [PARAM_INT/PARAM_TEXT)`.
+
+
+ View example
+
+
+```php title="lib/alfresco/lib.php"
+public function __construct($repositoryid, $context = SYSCONTEXTID, $options = []) {
+ global $SESSION;
+
+ /* Skipping code that is not relevant to user login */
+
+ $this->alfresco = new Alfresco_Repository($this->options['alfresco_url']);
+ $this->username = optional_param('al_username', '', PARAM_RAW);
+ $this->password = optional_param('al_password', '', PARAM_RAW);
+ try{
+ // deal with user logging in.
+ if (empty($SESSION->{$this->sessname}) && !empty($this->username) && !empty($this->password)) {
+ $this->ticket = $this->alfresco->authenticate($this->username, $this->password);
+ $SESSION->{$this->sessname} = $this->ticket;
+ } else {
+ if (!empty($SESSION->{$this->sessname})) {
+ $this->ticket = $SESSION->{$this->sessname};
+ }
+ }
+ $this->user_session = $this->alfresco->createSession($this->ticket);
+ $this->store = new SpacesStore($this->user_session);
+ } catch (Exception $e) {
+ $this->logout();
+ }
+ $this->current_node = null;
+
+ /* Skipping code that is not relevant to user login */
+
+}
+```
+
+
+
+
+Many types include a single element of type 'popup' with the param 'url' pointing at the URL used to authenticate the repo instance.
+
+
+ View example
+
+
+```php title="Code taken from repository_boxnet"
+public function print_login() {
+ $ticket = $this->boxclient->getTicket();
+ if ($this->options['ajax']) {
+ $loginbtn = (object)[
+ 'type' => 'popup',
+ 'url' => ' https://www.box.com/api/1.0/auth/' . $ticket->get_oauth_tokens(),
+ ];
+ $result = [];
+ $result['login'] = [$loginbtn];
+ return $result;
+ } else {
+ // Print the login form HTML including the input username, password and ticket fields.
+ $loginform = new repository_boxnet\output\login($ticket);
+ echo $OUTPUT->render($loginform);
+ // Example of a login form:
+ //
+ //
+ //
+ //
+ //
+ //
+ }
+}
+```
+
+
+
+
+#### check_login(): bool
+
+This function will return a boolean value to tell Moodle whether the user has logged in.
+By default, this function will return true.
+
+```php
+public function check_login(): bool {
+ global $SESSION;
+ return !empty($SESSION->{$this->sessname});
+}
+```
+
+#### logout
+
+When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling **$this->print_login()**):
+
+```php title="lib/alfresco/lib.php"
+public function logout() {
+ global $SESSION;
+ unset($SESSION->{$this->sessname});
+ return $this->print_login();
+}
+```
+
+### Transferring files to Moodle (optional)
+
+These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are.
+
+#### get_file_reference($source)
+
+This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding.
+
+#### get_file($url, $filename = "")
+
+For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on 'select this file' to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the 'source' returned by 'get_listing'). The $filename should usually be processed by $path = $this->prepare_file($filename), giving the full 'path' where the file should be saved locally. This function then returns an array, containing:
+
+- path - the local path where the file was saved
+- url - the $url param passed into the function
+
+
+
+
+```php
+public function get_file($url, $filename = '') {
+// Default implementation from the base 'repository' class
+ $path = $this->prepare_file($filename); // Generate a unique temporary filename
+ $curlobject = new curl();
+ $result = $curlobject->download_one($url, null, ['filepath' => $path, 'timeout' => self::GETFILE_TIMEOUT]);
+ if ($result !== true) {
+ throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
+ }
+ return ['path'=>$path, 'url'=>$url];
+}
+```
+
+
+
+
+Slightly extended version taken from repository_equella
+
+```php
+public function get_file($reference, $filename = '') {
+ global $USER;
+ // Replace the line below by any method your plugin have to check a reference.
+ $details = example_external_server::get_details_by_reference($reference->reference));
+ if (!isset($details->url) || !($url = $this->appendtoken($details->url))) {
+ // Occurs when the user isn't known..
+ return null;
+ }
+ $path = $this->prepare_file($filename);
+ $cookiepathname = $this->prepare_file($USER->id. '_'. uniqid('', true). '.cookie');
+ $curlobject = new curl(['cookie'=>$cookiepathname]);
+ $result = $curlobject->download_one(
+ $url,
+ null,
+ ['filepath' => $path, 'followlocation' => true, 'timeout' => self::GETFILE_TIMEOUT]
+ );
+ // Delete cookie jar.
+ if (file_exists($cookiepathname)) {
+ unlink($cookiepathname);
+ }
+ if ($result !== true) {
+ throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
+ }
+ return ['path'=>$path, 'url'=>$url];
+}
+```
+
+
+
+
+#### get_link($url)
+
+Used with `FILE_EXTERNAL` to convert a reference (from 'get_file_reference', but ultimately from the output of 'get_listing') into a URL that can be used directly by the end-user's browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin.
+
+#### get_file_source_info($source)
+
+Takes the 'source' field from 'get_listing' (as returned by the user's browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository.
+Examples: 'Dropbox: /filename.jpg', 'http://fullurl.com/path/file', etc.
+
+This value will be used to display warning message if reference can not be restored from backup. Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager.
+
+### Search functions (optional)
+
+These functions allow you to implement search functionality within your repository.
+
+#### print_search
+
+When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.
+
+A custom search form must include the following:
+
+- A text field element named **s**, this is where users will type in their search criteria
+- A hidden element named **repo_id** and the value must be the id of the repository instance
+- A hidden element named **ctx_id** and the value must be the context id of the repository instance
+- A hidden element named **sesskey** and the value must be the session key
+
+
+ View example
+
+
+```php title="The default implementation in class 'repository'"
+public function print_search() {
+ global $PAGE;
+ $renderer = $PAGE->get_renderer('core', 'files');
+ return $renderer->repository_default_searchform();
+ // The default search HTML from repository/renderer.php:
+ // ;
+}
+```
+
+
+
+
+#### search($search_text, $page = 0)
+
+Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param('paramname', [PARAM_INT / PARAM_TEXT);.
+
+The return should return an array containing:
+
+- list - with the same layout as the 'list' element in 'get_listing'
+
+
+ View example
+
+
+
+#### global_search()
+
+Return true if should be included in a search throughout all repositories (currently not available via the UI)
+
+### Repository support for returning file as alias/shortcut
+
+It is possible to link to the file from external (or internal) repository by reference. In UI it is called "create alias/shortcut". This creates a row in `files` table but the contents of the file is not stored. Although it may be cached by repository if developer wants to.
+
+Make sure that function supported_returntypes() returns FILE_REFERENCE among other types.
+
+Note that external file is synchronised by moodle when UI wants to show the file size.
+
+#### get_reference_file_lifetime()
+
+Return minimum number of seconds before checking for changes to the file (default implementation = 1 day)
+
+```php
+public function get_reference_file_lifetime($ref) {
+ return DAYSECS; // One day, 60 * 60 * 24 seconds.
+}
+```
+
+#### sync_individual_file(stored_file $storedfile)
+
+Called after the file has reached the 'lifetime' specified above to see if it should now be synchronised (default implementation is to return true)
+
+```php
+public function sync_individual_file(stored_file $storedfile) {
+ return true;
+}
+```
+
+#### get_reference_details($reference, $filestatus = 0)
+
+Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box).
+
+This is usually prefixed with the repository name, and a semicolon. For example: `Myrepository: http://url.to.file`.
+
+- `$reference` is the 'source' output by `get_listing`
+- `$filestatus` can be either `0` (OK - default) or `666` (source file missing).
+
+
+ View example
+
+
+```php title="lib.php"
+public function get_reference_details($reference, $filestatus = 0) {
+ if (!$filestatus) {
+ // Replace the line below by any method your plugin have to check a reference.
+ $details = example_external_server::get_details_by_reference($reference);
+ return $this->get_name() . ': ' . $details->filename;
+ } else {
+ return get_string('lostsource', 'repository', '');
+ }
+}
+```
+
+
+
+
+#### get_file_by_reference($reference)
+
+Returns up-to-date information about the original file, only called when the 'lifetime' is reached and 'sync_individual_file' returns true.
+
+- For image files - download the file and return either $ret->filepath (full path on the server), $ret->handle (open handle to the file) or $ret->content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated
+- For non-image files - avoid downloading the file (if possible) and just return $ret->filesize to update that information
+- For missing / inaccessible files - return null
+ Remember this function may be called quite a lot, as the filemanager often wants to know the filesize.
+
+
+ View example
+
+
+```php title="/lib.php"
+public function get_file_by_reference($reference) {
+ global $USER;
+ // Replace the line below by any method your plugin have to check a reference.
+ $details = example_external_server::get_details_by_reference($reference->reference));
+ if (!isset($details->url) || !($url = $this->appendtoken($details->url))) {
+ // Occurs when the user isn't known.
+ return null;
+ }
+
+ // Download the file details.
+ $return = null;
+ $cookiepathname = $this->prepare_file($USER->id . '_' . uniqid('', true) . '.cookie');
+ $headparams = ['followlocation' => true, 'timeout' => self::SYNCFILE_TIMEOUT];
+ $curlobject = new curl(['cookie' => $cookiepathname]);
+
+ if (file_extension_in_typegroup($ref->filename, 'web_image')) {
+ // The file is an image - download and return the file path.
+ $path = $this->prepare_file('');
+ $result = $curlobject->download_one($url, null, $headparams);
+ if ($result === true) {
+ $return = (object) ['filepath' => $path];
+ }
+ } else {
+ // The file is not an image - just get the file details.
+
+ $result = $curlobject->head($url, $headparams);
+ }
+
+ // Delete cookie jar.
+ if (file_exists($cookiepathname)) {
+ unlink($cookiepathname);
+ }
+
+ $this->connection_result($ccurlobject->get_errno());
+ $curlinfo = $ccurlobject->get_info();
+ if ($return === null && isset($curlinfo['http_code']('list'])) &&
+ $curlinfo['http_code']== 200 &&
+ array_key_exists('download_content_length', $curlinfo) &&
+ $curlinfo['download_content_length']('http_code']) >= 0) {
+ // We received a correct header and at least can tell the file size.
+ $return = (object) ['filesize' => $curlinfo['download_content_length']];
+ }
+ return $return;
+}
+```
+
+
+
+
+#### send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null)
+
+Send the requested file back to the user's browser. The 'reference' for the file can be found via $storedfile->get_reference(). If the file is not found / no longer exists, the function 'send_file_not_found()' should be used. Otherwise the file should be output directly, via the most appropriate method:
+
+- Use a 'Location: ' header to redirect to the external URL
+- Download the file and cache within the Moodle filesystem (possibly using '$this->import_external_file_contents()'), then call 'send_stored_file'.
+
+:::note
+
+It is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead.
+
+:::
+
+
+ View example
+
+
+```php title="/lib.php"
+public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
+ // Replace the line below by any method your plugin have to check a reference.
+ $details = example_external_server::get_details_by_reference($stored_file->get_reference()));
+ $url = $this->appendtoken($details->url);
+ if ($url) {
+ header('Location: ' . $url);
+ } else {
+ send_file_not_found();
+ }
+}
+```
+
+
+
+
+An example of caching files within the Moodle filesystem can be found in repository_dropbox.
+
+
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/_examples/lang.md b/versioned_docs/version-4.5/apis/plugintypes/theme/_examples/lang.md
new file mode 100644
index 0000000000..09094b1709
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/_examples/lang.md
@@ -0,0 +1,15 @@
+
+
+
+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.
+
+Language strings for the plugin. Required strings:
+
+- **`pluginname`** - name of plugin.
+- **`choosereadme`** - descriptive text displayed beneath the theme information dialog screenshot.
+- **`configtitle`** - settings text for this type of plugin.
+
+You will usually need to add your own strings for two main purposes:
+
+- Creating suitable form controls for users who are editing the theme settings; and
+- Displaying information about the theme.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/_examples/lang.php b/versioned_docs/version-4.5/apis/plugintypes/theme/_examples/lang.php
new file mode 100644
index 0000000000..054a1e8c03
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/_examples/lang.php
@@ -0,0 +1,3 @@
+$string['pluginname'] = 'Boost';
+$string['choosereadme'] = 'Boost is a modern, highly-customisable theme. This theme is intended to be used directly, or as a parent theme when creating new themes utilising Bootstrap 4.';
+$string['configtitle'] = 'Boost';
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/fonts.md b/versioned_docs/version-4.5/apis/plugintypes/theme/fonts.md
new file mode 100644
index 0000000000..c60955d011
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/fonts.md
@@ -0,0 +1,77 @@
+---
+title: Fonts
+tags:
+ - Plugins
+ - Theme
+ - Fonts
+sidebar_position: 3
+---
+
+
+
+CSS3 standard introduced the possibility to specify custom fonts, see [CSS web fonts tutorial](http://www.w3schools.com/css/css3_fonts.asp).
+
+Moodle includes support for plugin or theme fonts. It is very similar to theme images and pix subdirectories.
+
+## Font file locations
+
+Depending on where you intend to use the font put it into one of the following locations:
+
+- `/lib/fonts/` Fonts used in core.
+- `/plugindir/fonts/` Fonts used by plugin.
+- `/theme/sometheme/fonts/` Theme specific fonts.
+
+You can also override core and plugin fonts in theme:
+
+- `/theme/sometheme/fonts_core/` Overridden core fonts.
+- `/theme/sometheme/fonts_plugins/plugintype_pluginname/` Overridden fonts of some plugin.
+
+:::important
+
+- Subdirectories are not allowed.
+- Use only lowercase alphanumeric characters and underscore in font file names.
+- WOFF (Web Open Font Format), TTF (True Type Fonts), OTF (OpenType Fonts), SVG (Scalable Vector Graphic) and EOT (Embedded OpenType) fonts are supported, but it's recommended to use WOFF fonts.
+
+:::
+
+### CSS placeholders
+
+```css title="Use a font in a plugin"
+@font-face {
+ font-family: ThreeDumb;
+ src: url([[font:mod_book|3dumb.woff]]);
+}
+```
+
+The placeholder references file `/mod/book/fonts/3dumb.woff`, the new font face could be for example used for book headings:
+
+```css
+.path-mod-book .book_chapter_title {
+ font-family: ThreeDumb;
+}
+```
+
+If you want to use some font in theme only, you can for example:
+
+```css title="Use a font in theme only"
+@font-face {
+ font-family: goodDogFont;
+ src: url([[font:theme|good_dog.woff]]);
+}
+
+a {font-family:goodDogFont;}
+```
+
+The font would be stored in `/theme/yourtheme/fonts/good_dog.woff` file.
+
+Based on previous example, if you want to use some font stored in `/lib/fonts/` directory, you have to replace `font:theme` by `font:core`.
+
+### More free fonts
+
+Please respect all licenses for font redistribution, you can get some nice free fonts from [http://www.fontsquirrel.com](http://www.fontsquirrel.com) for example.
+
+:::warning
+
+This is not intended for forcing of something like Comic Sans on all your visitors ;-)
+
+:::
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/images.md b/versioned_docs/version-4.5/apis/plugintypes/theme/images.md
new file mode 100644
index 0000000000..2cf176c1c2
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/images.md
@@ -0,0 +1,106 @@
+---
+title: Images and icons in themes
+tags:
+ - Plugins
+ - Theme
+ - Images
+ - Icons
+sidebar_position: 2
+sidebar_label: Images and icons
+---
+
+One of the theme features is the ability to override any of the standard images within Moodle when your theme is in use. At this point, let's explore how to utilize your own images within your theme and how to override the images being used by Moodle.
+So first up a bit about images within Moodle:
+
+1. Images you want to use within your theme **must** to be located within your theme's `pix` directory.
+1. You can use sub directories within the `pix` directory of your theme.
+1. Images used by Moodle's core are located within the `pix` directory of Moodle.
+1. All plugins should store their images within their own `pix` directory.
+
+The following section assumes that there are two image files in the `pix` directory of a theme named `yourthemename`:
+
+- `/theme/yourthemename/pix/imageone.svg`
+- `/theme/yourthemename/pix/subdir/imagetwo.png`
+
+The first image is an SVG, and the second a PNG located in a subdirectory.
+
+## Use images in templates
+
+The following example illustrates how to make use of these images within your layout file so they can be inserted in your layout template.
+
+```php title="theme/yourtheme/layout/somelayout.php"
+$templatecontext = [
+ $imageone => $OUTPUT->pix_url('imageone', 'theme'),
+ $imagetwo => $OUTPUT->pix_url('subdir/imagetwo', 'theme'),
+];
+
+echo $OUTPUT->render_from_template('theme_yourtheme/somelayout', $templatecontext);
+```
+
+:::note
+
+A method of Moodle's output library is utilized to generate the URL to the image. It's not too important how that function works, but it is important that it's used, as it's what allows images within Moodle to be overridden.
+
+:::
+
+:::danger Important
+
+**DO NOT** include the image file extension. Moodle will work it out automatically and it will not work if you do include it.
+
+:::
+
+```handlebars title="theme/yourtheme/templates/somelayout.mustache"
+
+
+```
+
+## Use images in CSS
+
+The following is how you would use the images from within CSS/SCSS as background images.
+
+```css
+.divone {
+ background-image: url([[pix:theme|imageone]]);
+}
+
+.divtwo {
+ background-image: url([[pix:theme|subdir/imagetwo]]);
+}
+```
+
+A placeholder is used within the CSS to allow use of a pix icon. During the CSS/SCSS compilation, these placeholders are converted into a URL which the browser can fetch and serve.
+
+:::note
+
+Notice that the image file extension included. The reason for this leads us into the next topic, how to override images.
+
+:::
+
+## Override images
+
+From within a theme you can **very** easily override any standard image within Moodle by simply adding the replacement image to the theme's pix directory in the same sub directory structure as it is in Moodle.
+So, for instance, if there is a need to override the following images:
+
+1. `/pix/moodlelogo.png`
+1. `/pix/i/user.svg`
+1. `/mod/chat/pix/monologo.svg`
+
+Simply add the replacement images to the theme in the following locations:
+
+1. `/theme/themename/pix_core/moodlelogo.png`
+1. `/theme/themename/pix_core/i/user.svg`
+1. `/theme/themename/pix_plugins/mod/chat/monologo.svg`
+
+:::note
+
+A `pix_core` directory has been created in the theme to store the replacement images. For a specific activity module like chat, the directory `pix_plugins/mod/chat` is needed. This directory is `pix_plugins` and then the plugin type (`mod`) and then the plugin name (`chat`).
+
+:::
+
+Another noteworthy aspect is that Moodle not only searches for replacements of the same image type (svg, png, jpg, gif, and so on) but also replacements in any image format. This is why the image file extension was never specified when working with our images above.
+This means that the following would also work:
+
+1. `/theme/themename/pix_core/moodlelogo.svg`
+1. `/theme/themename/pix_core/i/user.jpg`
+
+For a more detailed description of how this all works see the page on [Using images in a theme](https://docs.moodle.org/dev/Using_images_in_a_theme).
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/index.md b/versioned_docs/version-4.5/apis/plugintypes/theme/index.md
new file mode 100644
index 0000000000..509f70cafa
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/index.md
@@ -0,0 +1,512 @@
+---
+title: Theme plugins
+tags:
+ - Plugins
+ - Theme
+---
+
+A Moodle theme allows users to customize the appearance and functionality of their Moodle site, from overall design to specific activities. Users can create their own themes or modify existing ones, leveraging CSS and JavaScript for customization. The theme architecture ensures smooth fallbacks for minimal changes, fostering flexibility and ease of use.
+
+## File structure
+
+import {
+ Lang,
+ Lib,
+ VersionPHP,
+} from '../../_files';
+
+Theme plugins are located in the `/theme` 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 a `theme_example` plugin.
+
+```console
+ theme/example
+ |-- amd
+ | └-- src
+ |-- classes
+ | └-- output
+ |-- fonts
+ |-- fonts_core
+ |-- fonts_plugins
+ | └-- plugintype
+ | └-- pluginname
+ |-- lang
+ | └-- en
+ | └-- theme_example.php
+ |-- layout
+ |-- pix
+ | └-- favicon.ico
+ | └-- screenshot.png
+ |-- pix_plugins
+ | └-- plugintype
+ | └-- pluginname
+ |-- style
+ |-- scss
+ |-- templates
+ |-- config.php
+ |-- settings.php
+ └-- version.php
+```
+
+
+
+Some of the important files for the Theme plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin.
+
+:::tip Override icons and templates
+
+You can customize icons in base themes:
+
+1. Without altering core code by placing them in `$CFG->dataroot/pix` and `$CFG->dataroot/pix_plugins`. If a theme extends a base theme and includes its own icons, those will take precedence.
+1. Adding custom icons to a theme by placing them in the theme's `pix_core` and `pix_plugins` directories, as described in the [Override images section](./theme/images#override-images).
+
+:::
+
+Similarly, mustache templates in base themes can be overridden without impacting core code by placing them in `templates/[componentname]/[templatename].mustache`.
+
+### config.php
+
+All theme options are set within the `config.php` file for the theme.
+
+
+ View basic theme config.php
+
+
+```php
+.
+
+/**
+ * Boost config.
+ *
+ * @package theme_boost
+ * @copyright 2016 Frédéric Massart
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/lib.php');
+
+$THEME->name = 'boost';
+$THEME->sheets = [];
+$THEME->editor_sheets = [];
+$THEME->editor_scss = ['editor'];
+$THEME->usefallback = true;
+$THEME->scss = function($theme) {
+ return theme_boost_get_main_scss_content($theme);
+};
+
+$THEME->layouts = [
+ // Most backwards compatible layout without the blocks.
+ 'base' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array(),
+ ),
+ // Standard layout with blocks.
+ 'standard' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ // Main course page.
+ 'course' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ 'options' => array('langmenu' => true),
+ ),
+ 'coursecategory' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ // Part of course, typical for modules - default page layout if $cm specified in require_login().
+ 'incourse' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ // The site home page.
+ 'frontpage' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ 'options' => array('nonavbar' => true),
+ ),
+ // Server administration scripts.
+ 'admin' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ // My courses page.
+ 'mycourses' => array(
+ 'file' => 'drawers.php',
+ 'regions' => ['side-pre'],
+ 'defaultregion' => 'side-pre',
+ 'options' => array('nonavbar' => true),
+ ),
+ // My dashboard page.
+ 'mydashboard' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ 'options' => array('nonavbar' => true, 'langmenu' => true),
+ ),
+ // My public page.
+ 'mypublic' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ 'login' => array(
+ 'file' => 'login.php',
+ 'regions' => array(),
+ 'options' => array('langmenu' => true),
+ ),
+
+ // Pages that appear in pop-up windows - no navigation, no blocks, no header and bare activity header.
+ 'popup' => array(
+ 'file' => 'columns1.php',
+ 'regions' => array(),
+ 'options' => array(
+ 'nofooter' => true,
+ 'nonavbar' => true,
+ 'activityheader' => [
+ 'notitle' => true,
+ 'nocompletion' => true,
+ 'nodescription' => true
+ ]
+ )
+ ),
+ // No blocks and minimal footer - used for legacy frame layouts only!
+ 'frametop' => array(
+ 'file' => 'columns1.php',
+ 'regions' => array(),
+ 'options' => array(
+ 'nofooter' => true,
+ 'nocoursefooter' => true,
+ 'activityheader' => [
+ 'nocompletion' => true
+ ]
+ ),
+ ),
+ // Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible.
+ 'embedded' => array(
+ 'file' => 'embedded.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ // Used during upgrade and install, and for the 'This site is undergoing maintenance' message.
+ // This must not have any blocks, links, or API calls that would lead to database or cache interaction.
+ // Please be extremely careful if you are modifying this layout.
+ 'maintenance' => array(
+ 'file' => 'maintenance.php',
+ 'regions' => array(),
+ ),
+ // Should display the content and basic headers only.
+ 'print' => array(
+ 'file' => 'columns1.php',
+ 'regions' => array(),
+ 'options' => array('nofooter' => true, 'nonavbar' => false, 'noactivityheader' => true),
+ ),
+ // The pagelayout used when a redirection is occuring.
+ 'redirect' => array(
+ 'file' => 'embedded.php',
+ 'regions' => array(),
+ ),
+ // The pagelayout used for reports.
+ 'report' => array(
+ 'file' => 'drawers.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
+ ),
+ // The pagelayout used for safebrowser and securewindow.
+ 'secure' => array(
+ 'file' => 'secure.php',
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre'
+ )
+];
+
+$THEME->parents = [];
+$THEME->enable_dock = false;
+$THEME->extrascsscallback = 'theme_boost_get_extra_scss';
+$THEME->prescsscallback = 'theme_boost_get_pre_scss';
+$THEME->precompiledcsscallback = 'theme_boost_get_precompiled_css';
+$THEME->yuicssmodules = [];
+$THEME->rendererfactory = 'theme_overridden_renderer_factory';
+$THEME->requiredblocks = '';
+$THEME->addblockposition = BLOCK_ADDBLOCK_POSITION_FLATNAV;
+$THEME->iconsystem = \core\output\icon_system::FONTAWESOME;
+$THEME->haseditswitch = true;
+$THEME->usescourseindex = true;
+// By default, all boost theme do not need their titles displayed.
+$THEME->activityheaderconfig = [
+ 'notitle' => true
+];
+```
+
+
+
+
+Everything is added to `$THEME`. This is the theme's configuration object, it is created by Moodle using default settings and is then updated by whatever settings are added to it.
+
+- `$THEME->name`. The theme's name should simply be whatever the theme's name is, most likely whatever the theme directory is named.
+- `$THEME->sheets`. An array containing the names of the CSS stylesheets to include for this theme. Boost uses SCSS instead of CSS so it doesn't list any files here.
+
+:::note
+
+It is just the name of the stylesheet and does not contain the directory or the file extension. Moodle assumes that the theme's stylesheets will be located in the `{theme}/style` directory of the theme and have .css as an extension.
+
+:::
+
+- `$THEME->editorsheets`. An array containing the names of the CSS stylesheets to include for text editor content area. Boost does not list any stylesheets here so text editors will use plain text styles.
+- `$THEME->layouts`. Any of the different layout types can be mapped. For more information see the [layouts section](./theme/layout).
+- `$THEME->parents`. Defines the themes that the theme will extend. Boost has no parents, but if a theme is extending boost, it should be listed it here like:
+
+```php
+$THEME->parents = ['boost'];
+```
+
+- `$THEME->enable_dock`. Boost does not support docking blocks.
+- `$THEME->csstreepostprocessor`. Boost uses a function to post process the CSS. This is an advanced feature and is used in boost to automatically apply vendor prefixes to CSS styles.
+- `$THEME->rendererfactory`. Almost all themes need this setting to be set to `theme_overridden_renderer_factory` or the theme will not be able to customise any core renderers.
+- `$THEME->undeletableblocktypes`. This is a comma separated list of block types that cannot be deleted in this theme. If you don't define this, the admin and settings blocks will be undeletable by default. Because Boost provides alternate ways to navigate it does not require any blocks.
+
+:::tip
+
+When you first begin writing themes, make sure you take a look at the configuration files of the other themes that get shipped with Moodle. You will get a good picture of how everything works, and what is going on in a theme, simply by reading it and taking notice of what it is including or excluding.
+
+:::
+
+Have a look at the following theme options for a complete list of theme options which include lesser used specialised or advanced settings:
+
+
+ Complete theme options
+
+
+#### `$THEME->blockrtlmanipulations`
+
+Allows the theme to manipulate how the blocks are displayed in a *right-to-left* language. Not recommended CSS is automatically flipped for RTL.
+
+#### `$THEME->csspostprocess`
+
+Allows the user to provide the name of a function that all CSS should be passed to before being delivered.
+
+#### `$THEME->csstreepostprocessor`
+
+
+
+Allows the user to provide the name of a function that can perform manipulations on an in-memory representation of the CSS tree. Some useful manipulations are available such as the `theme_boost\autoprefixer` which will automatically add vendor prefixes to all CSS that requires them.
+
+#### `$THEME->doctype`
+
+The doctype of the served documents.
+
+#### `$THEME->editor_sheets`
+
+An array of stylesheets to include just within the body of the text editors like Tiny. This is required if you want content to resemble its final appearance in the page, while it is being edited in the text editor.
+
+#### `$THEME->enablecourseajax`
+
+If set to false the course AJAX features will be disabled.
+
+#### `$THEME->enable_dock`
+
+If set to true the side dock is enabled for blocks.
+
+#### `$THEME->prescsscallback`
+
+
+
+The name of a function that will return some SCSS code to inject at the beginning of the SCSS file specified in `$THEME->scss`.
+
+#### `$THEME->extrascsscallback`
+
+
+
+The name of a function that will return some SCSS code to inject at the end of the SCSS file specified in `$THEME->scss`.
+
+#### `$THEME->hidefromselector`
+
+Used to hide a theme from the theme selector (unless theme designer mode is on). Accepts true or false.
+
+#### `$THEME->javascripts`
+
+
+
+An array containing the names of JavaScript files located in `/javascript/` to include in the theme.
+
+:::danger Deprecated
+
+The `$THEME->javascripts` setting should no longer be used. Please use AMD [JavaScript Modules](../../../guides/javascript/modules.md) instead.
+
+:::
+
+#### `$THEME->javascripts_footer`
+
+
+
+As above but will be included in the page footer.
+
+:::danger Deprecated
+
+The `$THEME->javascripts_footer` setting should no longer be used. Please use AMD [JavaScript Modules](../../../guides/javascript/modules.md) instead.
+
+:::
+
+#### `$THEME->layouts`
+
+An array setting the layouts for the theme.
+
+#### `$THEME->scss`
+
+
+
+The name of a SCSS file in the theme's `scss/` folder to compile on the fly. Sheets with the same name will be ignored. This can also be a function which returns SCSS, in which case all import paths will be relative to the scss folder in this theme or any of it's parents.
+
+#### `$THEME->name`
+
+Name of the theme. Most likely the name of the directory in which this file resides.
+
+#### `$THEME->parents`
+
+An array of themes to inherit from. If the theme you inherit from inherits from a parent as well, you need to indicate the grandparent here too.
+
+#### `$THEME->parents_exclude_javascripts`
+
+An array of JavaScript files NOT to inherit from the themes parents.
+
+#### `$THEME->parents_exclude_sheets`
+
+An array of stylesheets NOT to inherit from the themes parents.
+
+#### `$THEME->plugins_exclude_sheets`
+
+An array of plugin sheets to ignore and NOT include.
+
+#### `$THEME->renderfactory`
+
+Sets a custom render factory to use with the theme, used when working with custom renderers. You most likely want this set to `theme_overridden_renderer_factory`.
+
+#### `$THEME->sheets`
+
+An array of stylesheets to include for this theme. Should be located in the theme's style directory. Not required if using SCSS.
+
+#### `$THEME->yuicssmodules`
+
+Old setting to define a list of YUI CSS modules to be included. These files interfere with existing styles and it is recommended to set this to an empty string to prevent any files being included.
+
+:::danger Attention
+
+This setting should probably be set to `''` to prevent and YUI CSS being included.
+
+:::
+
+#### `$THEME->undeletableblocktypes`
+
+An array of block types that must exist on all pages in this theme or this theme will be unusable. If a block type listed here is missing when a page is loaded. It will be auto-created (but only shown for themes that require it).
+
+#### `$THEME->addblockposition`
+
+Either `BLOCK_ADDBLOCK_POSITION_FLATNAV`, `BLOCK_ADDBLOCK_POSITION_DEFAULT` or `BLOCK_ADDBLOCK_POSITION_CUSTOM`. Defines where to put the "Add a block" controls when editing is enabled.
+
+
+
+
+
+
+### lang/en/themename.php
+
+import langExample from '!!raw-loader!./_examples/lang.php';
+import langDescription from './_examples/lang.md';
+
+
+
+### version.php
+
+
+
+## Insights
+
+### Getting your theme to appear correctly in theme selector
+
+If you follow the examples on this page to the letter, when you go to the `Theme Selector` page you may be discouraged to find that your theme does not appear like the other themes do. In fact, instead of your theme's name, you will see something along the lines of `[pluginname](https://docs.moodle.org/dev/pluginname)`.
+
+To correct this, you must add the `theme/THEMENAME/lang/en/theme_THEMENAME.php` file, where `THEMENAME` is the name of the theme folder. Inside that file, add the string `$string[]('pluginname') = 'THEMENAME';`. Make `THEMENAME` the name of your theme, however you want it displayed in the Theme selector.
+
+Also, make sure to change your [`config.php`](#configphp) file and [`version.php`](#versionphp) file to reflect the correct name:
+
+```php title="config.php"
+
+$THEME->name = 'NAME';
+
+```
+
+```php title="version.php"
+
+$plugin->component = 'THEMENAME'; // Full name of the plugin (used for diagnostics)
+
+```
+
+The `screenshot` for the theme should be about `500 x 400 px`.
+
+### Required theme `divs`
+
+Some parts of Moodle may rely on particular `divs`, for example the div with id `page-header`. Consequently all themes must include at least the `divs` (with the same ids) that are present in the `boost` theme.
+
+Missing out these elements may result in unexpected behaviour within specific modules or other plugins.
+
+## Caching
+
+When Moodle is not running in theme designer mode it will look for a cached version of the compiled CSS for the current theme to serve to the browser requesting the page. If the cached file doesn't yet exist then the CSS will be built and cached during the page request.
+
+The cached CSS is located on disk in Moodle's local cache:
+
+```
+ /localcache/theme///css/all_.css
+```
+
+The cache path consists of a global theme revision (`themerev` config value) and a per theme sub-revision (`themesubrev` plugin config value). If either of those are incremented it will change the path to the cache file and cause a new file to be generated.
+
+Individual theme's CSS cache can be built by using the admin CLI script:
+
+```bash
+ php admin/cli/build_theme_css.php --themes boost
+```
+
+The script will only increment the theme sub-revision of the theme(s) being built which means existing theme cache's remain untouched.
+
+## See also
+
+
+
+- MoodleAcademy courses. For instance:
+ - [Moodle Page Layout and Site Navigation](https://moodle.academy/course/view.php?id=110)
+ - [Accessible Development Practices](https://moodle.academy/course/view.php?id=54)
+
+- MoodleBites Theme Design. Completely online courses [Level 1](https://www.moodlebites.com/mod/page/view.php?id=3208) and [Level 2](https://www.moodlebites.com/mod/page/view.php?id=3210) are designed to assist Moodle administrators, designers, and developers get up-to-speed with Moodle Theme design, and are run by [HRDNZ](https://www.hrdnz.com) (Certified Moodle Partner since 2006).
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/layout.md b/versioned_docs/version-4.5/apis/plugintypes/theme/layout.md
new file mode 100644
index 0000000000..02a1a56898
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/layout.md
@@ -0,0 +1,330 @@
+---
+title: Layout
+tags:
+ - Plugins
+ - Theme
+ - Layout
+sidebar_position: 1
+---
+
+All themes are required to define the layouts they wish to be responsible for as well as create; however, many layout files are required by those layouts. If the theme is overriding another theme then it is a case of deciding which layouts this new theme should override. If the theme is a completely fresh start then you will need to define a layout for each of the different possibilities.
+
+:::tip
+
+Note that a new theme that will base itself on another theme (overriding it) does not need to define any layouts or use any layout files if there are no changes that it wishes to make to the layouts of the existing theme.
+
+:::
+
+Layouts are defined in [`config.php`](../theme#configphp) within `$THEME->layouts`.
+
+The following is an example of one such layout definition:
+
+ View example of one layout definition in config.php
+
+
+```php
+$THEME->layouts = [
+ // Standard layout with blocks, this is recommended for most pages with general information
+ 'standard' => [
+ 'theme' => 'boost',
+ 'file' => 'columns2.php',
+ 'regions' => ['side-pre'],
+ 'defaultregion' => 'side-pre',
+ ],
+],
+```
+
+
+
+
+The first thing Moodle looks at is the name of the layout, in this case it is `standard` (the array key in PHP), it then looks at the settings for the layout, this is the theme, file, regions, and default region. There are also a couple of other options that can be set by a layout:
+
+- `theme` [ *Optional* ]. Is the theme the layout file exists in. That's right: you can make use of layouts from other installed themes.
+- `file` [ *Required* ]. Is the name of the layout file this layout wants to use.
+- `regions` [ *Required* ]. Is the different block regions (places you can put blocks) within the theme.
+- `defaultregion` [ *Required if regions is non-empty, otherwise optional* ]. Is the default location when adding new blocks.
+- `options` [ *Optional* ]. An array of layout specific options described in detail below.
+
+The **theme** is optional. Normally the layout file is looked for in the current theme, or, if it is not there, in the parent theme. However, you can use a layout file from any other theme by giving the theme name here.
+
+You can define whatever regions you like. You just need to pick a name for each one. Most themes just use one or both of `side_pre` and `side_post`, which is like 'left side' and 'right side', except in right to left languages, when they are reversed. If you say in [`config.php`](../theme#configphp) that your the layout provides regions called `fred` and `barney`, then you must call `$OUTPUT->blocks_for_region('fred')` and `$OUTPUT->blocks_for_region('barney')` somewhere in the layout file.
+
+The final setting **options** is a special case that only needs to be set if you want to make use of it. This setting allows the theme designer to specify special options that they would like to create that can be later accessed within the layout file. This allows the theme to make design decisions during the definition and react upon those decisions in what ever layout file is being used.
+
+One such place this has been used is within the boost theme. If you take a look first at `theme/boost/config.php` you will notice that several layouts specify options `langmenu` and `nonavbar` which can both be set to either true or false. The layout options can then be used on the `layout .php` files, mustache templates and renderers.
+
+```php
+layout_options['nonavbar']) && $PAGE->has_navbar());
+$hasfooter = (empty($PAGE->layout_options['nofooter']));
+```
+
+## Layout files
+
+Layout files are used to provide a different layout of the elements of the page for different types of pages in Moodle.
+
+In the [`config.php`](../theme#configphp) for a theme there is a list of **layouts** which map a page type to a specific PHP page in the layout folder for the theme.
+
+
+ View example of layout definition for popup
+
+
+```php title="theme/boost/config.php"
+ 'popup' => [
+ 'file' => 'columns1.php',
+ 'regions' => [],
+ 'options' => ['nofooter' => true, 'nonavbar' => true],
+ ],
+```
+
+This example means every page that has pagetype `popup` will be displayed with the `theme/themename/layout/columns1.php` file, it will have no block regions and there are some options that will be available to the page in the global variable `$PAGE->layout_options`.
+
+
+
+
+:::tip
+
+It is possible to implement a layout file directly in PHP by echoing the HTML for the page, or mixing PHP tags with HTML, but a better way to create a layout file is to gather all the information required for the layout into a context and render it with a mustache template.
+
+Using templates for layout files makes a lot of sense because they are easier to read and maintain than mixing PHP and HTML in the same file.
+
+[Read about mustache templates](../../../guides/templates/index.md)
+
+:::
+
+
+ View example of a layout file using a template
+
+
+```php title="theme/boost/layout/columns1.php"
+body_attributes([]);
+
+$templatecontext = [
+ 'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
+ 'output' => $OUTPUT,
+ 'bodyattributes' => $bodyattributes
+];
+
+echo $OUTPUT->render_from_template('theme_boost/columns1', $templatecontext);
+```
+
+This example puts some variables into a `templatecontext` and then calls `render_from_template` to render the mustache template for this layout. The template is located at `theme/boost/templates/columns1.mustache`.
+It is possible to put PHP classes in the context for the mustache template and any public properties or methods which accept no arguments will be available to the template. `$OUTPUT` has several useful public methods which accept no arguments and is a valuable class when creating a layout template in mustache.
+
+
+
+
+
+ View example of the mustache template for the previous layout
+
+{{{ output.standard_end_of_body_html }}}
+
+
+{{#js}}
+require(['theme_boost/loader']);
+{{/js}}
+```
+
+Explaining each line of this template will answer a lot of questions. This example contains only the very minimal required functions to generate a valid layout. You should consider all of the sections below as required in every layout file (although any of the HTML tags can and should be altered).
+
+- Calling a function on `$OUTPUT` in `PHP`. Because there is a public method on the output class named `doctype` which accepts no arguments, mustache will call it and return the output. A function to generate the `doctype` tag is called because calling this function returns the correct HTML for the document type for this theme AND it sets a different content type header (including the charset) depending on the doc type for the theme. Setting a correct charset in every page is important to prevent a class of XSS attacks.
+
+```handlebars
+{{{ output.doctype }}}
+```
+
+- The root tag, the HTML tag, has been reintroduced. A set of default attributes for the page has been included by invoking the `htmlattributes` function of the output class. This encompasses the appropriate language attribute for the entire page and potentially an XML namespace for XHTML documents.
+
+```handlebars
+
+```
+
+- The head section of the document has been initiated, and the title for the page has been set. Note that the title is already escaped by the output class, so triple mustache tags `{{{` are being used to prevent double escaping.
+
+```handlebars
+
+ {{{ output.page_title }}}
+```
+
+- A function is called to obtain the URL to the favicon. The favicon resides in the theme pix directory and is served through the `theme/image.php` file, which adds special caching headers for images.
+
+```handlebars
+
+```
+
+- The standard head HTML function handles a significant amount of necessary setup for our page. It internally creates the block regions, generates meta tags including keywords for SEO, initializes common JavaScript modules, generates links to the style sheets, and injects any additional HTML set by the `$CFG->additionalhtmlhead` setting.
+
+```handlebars
+ {{{ output.standard_head_html }}}
+```
+
+- This `viewport` meta tag is recommended by bootstrap for "proper viewport rendering and touch zooming".
+
+```handlebars
+
+
+```
+
+- The body attributes include the language direction and standard classes for the page.
+
+```handlebars
+
+
+```
+
+- In the Boost theme, a `page-wrapper` div is utilized to prevent content from disappearing under the fixed header.
+
+```handlebars
+
+```
+
+- The `standard_top_of_body_html` should be included in every layout and includes skip links for accessibility as well as initialising JQuery, YUI and own static JavaScript files.
+
+```handlebars
+ {{{ output.standard_top_of_body_html }}}
+```
+
+- This is standard HTML tags defining the content region for this page. The classes come from Bootstrap 4.
+
+```handlebars
+
+
+
+
+
+```
+
+- The course content header allows Moodle plugins to inject things in the top of the page. This is used for "notifications" for example (which are the alert boxes you see after submitting a form).
+
+```handlebars
+ {{{ output.course_content_header }}}
+```
+
+- The main content function returns the real content for the page.
+
+```handlebars
+ {{{ output.main_content }}}
+```
+
+- The course content footer is used mainly by course formats to insert things after the main content.
+
+```handlebars
+ {{{ output.course_content_footer }}}
+```
+
+- Close all the open tags.
+
+```handlebars
+
+
+
+
+
+
+```
+
+- This function will add all of the JavaScript that was required while rendering the page. JavaScript is added at the end of the document so that it does not block rendering the page.
+
+```handlebars
+{{{ output.standard_end_of_body_html }}}
+```
+
+- Finish the HTML for the page.
+
+```handlebars
+
+
+```
+
+- The final section is required for Bootstrap 4 themes and loads all the Bootstrap 4 JavaScript dependencies.
+
+```handlebars
+{{#js}}
+require(['theme_boost/loader']);
+{{/js}}
+```
+
+
+
+
+If we had block regions in this layout we would need to insert them in the template. The way we would do this is by getting the HTML for the block region in our layout PHP file, adding it to the context and then including it in our template.
+
+```php title="theme/boost/layout/columns2.php"
+$blockshtml = $OUTPUT->blocks('side-pre');
+$hasblocks = strpos($blockshtml, 'data-block=') !== false;
+...
+$templatecontext = [
+...
+ 'sidepreblocks' => $blockshtml,
+ 'hasblocks' => $hasblocks,
+...
+];
+echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext);
+```
+
+```handlebars title="theme/boost/templates/columns2.mustache"
+ {{#hasblocks}}
+
+ {{{ sidepreblocks }}}
+
+ {{/hasblocks}}
+```
+
+When writing layout files, consider the variations in layouts and how the HTML utilized in each may differ. It is often unnecessary to create a distinct layout file for every layout; instead, existing layout files can often be reused across multiple layouts. Additionally, employing layout options can further minimize the need for creating additional layout files.
+
+It's important to note again that when customizing an existing theme, the creation of layouts or layout files may not be necessary.
+
+## Layout types
+
+- `base`. Most backwards compatible layout without the blocks. This is the layout used by default.
+- `standard`. Standard layout with blocks. This is recommended for most pages with general information.
+- `course`. Main course page.
+- `coursecategory`. Use for browsing through course categories.
+- `incourse`. Default layout while browsing a course, typical for modules.
+- `frontpage`. The site home page.
+- `admin`. Administration pages and scripts.
+- `mycourses`. My courses page.
+- `mydashboard`. My dashboard page.
+- `mypublic`. My public page.
+- `login`. The login page.
+- `popup`. Pages that appear in pop-up windows (no navigation, no blocks, no header).
+- `frametop`. Used for legacy frame layouts only. No blocks and minimal footer.
+- `embedded`. Used for embedded pages, like iframe/object embedded in moodleform. It needs as much space as possible.
+- `maintenance`. Used during upgrade and install. This must not have any blocks, and it is a good idea if it does not have links to other places. For example there should not be a home link in the footer.
+- `print`. Used when the page is being displayed specifically for printing.
+- `redirect`. Used when a redirection is occurring.
+- `report`. Used for reports.
+- `secure`. Used for `safebrowser` and `securewindow`.
diff --git a/versioned_docs/version-4.5/apis/plugintypes/theme/styles.md b/versioned_docs/version-4.5/apis/plugintypes/theme/styles.md
new file mode 100644
index 0000000000..bb8b26c81c
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/theme/styles.md
@@ -0,0 +1,136 @@
+---
+title: Styles
+tags:
+ - Plugins
+ - Theme
+ - Styles
+sidebar_position: 4
+---
+
+Let's begin by exploring the various locations within Moodle from which CSS can be included:
+
+- `\theme\themename\style\*.css`. This is the default location for all of the stylesheets that are used by a theme and the place which should be used by a theme designer if this theme is using CSS. Alternative to CSS is SCSS which is more powerful, flexible and easier to maintain.
+New theme developers should note that the order in which CSS files are found and included creates a hierarchy. This order ensures that the rules, within a theme's style sheets, take precedence over identical rules in other files that may have been introduced before. This can both extend another files definitions (see parent array in the config file) and also ensures that the current theme's CSS rules/definitions have the last say.
+There are other locations that can be used (although very rarely) to include CSS in a page. A developer of a php file can manually specify a stylesheet from anywhere within Moodle, like the database. Usually, if code is doing this, it is because there is a non-theme config or plugin setting that contains information requires special CSS information. As a theme designer you should be aware of, but not have to worry about, these locations of CSS files. Here are some examples:
+- `{pluginpath}\styles.css` (for instance, `\block\blockname\styles.css` or `\mod\modname\styles.css`). Every plugin can have its own styles.css file. This file should only contain the required CSS rules for the module and should not add anything to the look of the plugin such as colours, font sizes, or margins other than those that are truly required. Theme specific styles for a plugin should be located within the themes styles directory.
+- `{pluginpath}\styles_themename.css`. This should only ever be used by plugin developers. It allows them to write CSS that is designed for a specific theme without having to make changes to that theme. You will notice that this is never used within Moodle and is designed to be used only by contributed code.
+
+:::tip
+
+As theme designers, only the first method of introducing CSS will be used: adding rules to a stylesheet file located in the theme's style directory.
+
+:::
+
+## CSS pre-processors
+
+Browsers understand CSS well, but it is hard to write and maintain. The language does not support inheritance and reuse. [Support for variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) exists in more modern browsers only. This is why CSS pre-processors were invented. Moodle supports SASS, which is recommended by far.
+
+:::info Information
+
+To use SASS, define `$THEME->scss = 'filename';` in your themes `config.php`. Moodle will then use one of it's in-built CSS pre-processor to compile the CSS the first time it is loaded (or every time if `themedesignermode` is enabled in `$CFG`).
+
+For more information see: [SASS (wikipedia)](https://en.wikipedia.org/wiki/Sass_(stylesheet_language))
+
+:::
+
+## Core CSS organisation
+
+None of the themes provided in the standard install by Moodle use CSS stylesheets directly. Instead they use either SCSS to generate the style sheets. They all list one main SCSS file named `moodle` or scss folders and this file uses imports to load all other required files.
+
+:::note
+
+As a theme designer, it is entirely up to you how you create and organize your CSS and the rules you create.
+
+:::
+
+## How to write effective CSS rules within Moodle
+
+:::warning Important
+
+Writing good CSS rules is incredibly important.
+
+:::
+
+Due to performance requirements and browser limitations, all of the CSS files are combined into a single CSS file that gets included every time. This means that rules need to be written in such a way as to minimise the chances of a collision leading to unwanted styles being applied. Whilst writing good CSS is something most designers strive for we have implemented several new body classes and prompt developers to use appropriate classnames.
+
+### \ CSS id and classes
+
+The `ID` tag that gets applied to the body will always be a representation of the URI. For example if you are looking at a forum posting and the URI is `/mod/forum/view.php` then the body tags `ID` will be `#page-mod-forum-view`.
+
+As well as the body's ID attribute the URI is also exploded to form several CSS classes that get added to the body tag, so in the above example `/mod/forum/view` you would end up with the following classes being added to the body tag: `.path-mod` and `.path-mod-forum`.
+
+:::note
+
+`.path-mod-forum-view` is not added as a class, this is intentionally left out to lessen confusion and duplication as rules can relate directly to the page by using the ID and do not require the final class.
+
+:::
+
+The body ID and body classes described above will form the bread and butter for many of the CSS rules you will need to write for your theme, however there are also several other very handy classes that get added to the body tag that will be beneficial to you once you start your journey down the rabbit hole that is theming. Some of the more interesting classes are listed below.
+
+- If JavaScript is enabled then `jsenabled` will be added as a class to the body tag allowing you to style based on JavaScript being enabled or not.
+- Either `dir-rtl` or `dir-ltr` will be added to the body as a class depending on the direction of the language pack. This allows you to determine your text-alignment based on language if required:
+ - `rtl` = right to left
+ - `ltr` = left to right.
+- A class will be added to represent the language pack currently in use, by default `en_utf8` is used by Moodle and will result in the class `lang-en_utf8` being added to the body tag.
+- The `wwwroot` for Moodle will also be converted to a class and added to the body tag allowing you to stylise your theme based on the URL through which it was reached. For example, `http://sam.moodle.local/moodle/` will become `.sam-moodle-local—moodle`.
+- If the current user is not logged then `.notloggedin` will be added to the body tag.
+- The course format type will be added such as `format-weeks`.
+- The course id, context id and category id are all added as in `course-11 context-616 cmid-202 category-1`.
+- The `pagelayout` is added as `pagelayout-incourse`.
+
+:::info What does all of this look like in practise?
+
+Using the above example `/mod/forum/view.php` this will be the body tag:
+
+```html
+
+```
+
+:::
+
+### Writing your rules
+
+By following the [CSS coding style](https://docs.moodle.org/dev/CSS_coding_style) and CSS best-practices and understanding the [cascading order](http://htmlhelp.com/reference/css/structure.html#cascade) of CSS a theme developer will reduce collisions and lines of CSS that is written. CSS classes have been placed where it is believed anyone may want to apply their own styles.
+
+When starting to write rules make sure that you have a good understanding of where you want those rules to be applied, it is a good idea to make the most of the body classes mentioned above.
+
+- If you want to write a rule for a specific page make use of the body tag's ID:
+
+```css
+ #page-mod-forum-view .forumpost {
+ border: 1px solid blue;
+}
+```
+
+- If you want to write a rule that will be applied all throughout the forum:
+
+```css
+.path-mod-forum .forumpost {
+ border: 1px solid blue;
+}
+```
+
+The other very important thing to take into consideration is the structure leading up to the tag you want to style. Browsers apply conflicting styles with priority on the more specific selectors. It can be very beneficial to keep this in mind and write full selectors that rely on the structure of the tags leading to the tag you wish to style.
+
+By making use of body id's and classes and writing selectors to take into account the leading structure you can greatly minimise the chance of a collision both with Moodle now and in the future.
+
+:::tip
+
+It is also important to **write as FEW rules as possible**. CSS is extremely hard to maintain and lots of CSS is bad for client side performance.
+
+:::
+
+Themes based on the Bootstrap CSS framework can achieve most things without writing a single additional CSS rule. Please read [Bootstrap 4](https://getbootstrap.com/docs/4.0/getting-started/introduction/ the Bootstrap documentation) and learn how to use Bootstrap well to avoid adding unnecessary CSS rules for things already provided by the framework.
+
+## Compiling SCSS on the fly
+
+
+
+You can provide a SCSS file that will be compiled (and cached) on the fly. The purpose of this feature is to dynamically allow the customisation of SCSS variables. See the [dedicated page on SCSS](https://docs.moodle.org/dev/SCSS).
diff --git a/versioned_docs/version-4.5/apis/plugintypes/tiny/index.md b/versioned_docs/version-4.5/apis/plugintypes/tiny/index.md
new file mode 100644
index 0000000000..743b367c3e
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/tiny/index.md
@@ -0,0 +1,598 @@
+---
+title: TinyMCE Editor Plugins
+tags:
+ - editor_tiny
+ - tiny
+ - Editor
+ - Text editor
+ - HTML editor
+ - WYSIWYG
+ - TinyMCE
+---
+
+Moodle includes the TinyMCE text editor as standard from Moodle 4.1, and it can be installed from the plugins database for Moodle versions 3.11, and 4.0.
+
+The `editor_tiny` editor supports the inclusion of subplugins, which have the namespace `tiny_[pluginname]`.
+
+import {
+ Lang,
+} from '../../_files';
+
+## File structure
+
+TinyMCE subplugins are located in the `/lib/editor/tiny/plugins` directory. A plugin should not include any custom files outside of 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 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.
+
+:::
+
+
+ The directory layout for the `tiny_example` plugin.
+
+```console
+lib/editor/tiny/plugins/example
+├── amd
+│ ├── build
+│ │ ├── commands.min.js
+│ │ ├── commands.min.js.map
+│ │ ├── common.min.js
+│ │ ├── common.min.js.map
+│ │ ├── configuration.min.js
+│ │ ├── configuration.min.js.map
+│ │ ├── options.min.js
+│ │ ├── options.min.js.map
+│ │ ├── plugin.min.js
+│ │ └── plugin.min.js.map
+│ └── src
+│ ├── commands.js
+│ ├── common.js
+│ ├── configuration.js
+│ ├── options.js
+│ └── plugin.js
+├── classes
+│ ├── plugininfo.php
+│ └── privacy
+│ └── provider.php
+├── lang
+│ └── en
+│ └── tiny_example.php
+├── settings.php
+└── version.php
+```
+
+
+
+:::info
+
+You will notice that the JavaScript is broken down into a number of source files.
+
+This separation is optional, but fits a convention demonstrate in the TinyMCE codebase, and make the code easier to read and understand.
+
+:::
+
+## Creating a new plugin
+
+We highly recommend using the [Plugin Skeleton Generator](https://moodle.org/plugins/tool_pluginskel) when creating a new plugin.
+
+For the sake of simplicity, this documentation assumes that you have created a new Plugin using the following skeleton configuration:
+
+```yml title="tiny_example.yml"
+component: tiny_example
+name: Example Plugin
+release: "0.1.0"
+copyright: 2022 Andrew Lyons
+features:
+ settings: true
+privacy:
+ haspersonaldata: false
+ uselegacypolyfill: false
+tiny_features:
+ buttons:
+ - name: startdemo
+ category: content
+ text: Start demo
+ menuitems:
+ - name: startdemo
+ category: file
+ text: 'Start the demo'
+ options:
+ - name: myFirstProperty
+ type: string
+```
+
+### Generating the plugin skeleton
+
+Once you have created a plugin skeleton configuration, you can generate your plugin using the `cli/generate.php` command:
+
+```console
+php admin/tool/pluginskel/cli/generate.php tiny_example.yml
+```
+
+This will generate a working skeleton file for your plugin. Remember that you component name must start with `tiny_`.
+
+:::note Compiled JavaScript
+
+The plugin skeleton only produces source files for JavaScript. You will need to run `grunt` to compile this code.
+
+We highly recommend using `grunt watch` during development to simplify your workflow.
+
+```console
+cd lib/editor/tiny/plugins/example && npx grunt amd && cd -
+```
+
+:::
+
+## Key files
+
+There are a number of key files within the generated plugin skeleton, described below.
+
+### common.js
+
+The common.js file is used to store a set of variables used by other parts of the plugin.
+
+Its usage is optional, but recommended as it reduces code duplication, and the potential for typos and mistakes. It also makes it easier to refactor code later.
+
+
+ An example common.js file generated by the plugin skeleton generator
+
+This example includes:
+
+- the plugin name (`tiny_example/plugin`);
+- an icon, whose name is `tiny_example`;
+- a button for the start demo action, whose name is `tiny_example_startdemo`; and
+- a menu item for the start demo action, whose name is `tiny_example_startdemo`.
+
+```javascript title="commons=.js"
+const component = 'tiny_example';
+
+export default {
+ component,
+ pluginName: `${component}/plugin`,
+ icon: component,
+ startdemoButtonName: `${component}_startdemo`,
+ startdemoMenuItemName: `${component}_startdemo`,
+};
+```
+
+
+
+Typically this file will be included in other JS files in the plugin, usually only fetching the required variables, for example:
+
+```javascript
+import {component, pluginName} from './common';
+```
+
+### plugin.js
+
+The plugin.js is the entrypoint to the plugin code. It is primarily responsible for registering the plugin with the TinyMCE API, and the Moodle Integration of the Editor.
+
+
+ An example plugin.js file generated by the plugin skeleton generator
+
+```javascript title="plugin.js"
+import {getTinyMCE} from 'editor_tiny/loader';
+import {getPluginMetadata} from 'editor_tiny/utils';
+
+import {component, pluginName} from './common';
+import {register as registerOptions} from './options';
+import {getSetup as getCommandSetup} from './commands';
+import * as Configuration from './configuration';
+
+// Setup the tiny_example Plugin.
+export default new Promise(async(resolve) => {
+ // Note: The PluginManager.add function does not support asynchronous configuration.
+ // Perform any asynchronous configuration here, and then call the PluginManager.add function.
+ const [
+ tinyMCE,
+ pluginMetadata,
+ setupCommands,
+ ] = await Promise.all([
+ getTinyMCE(),
+ getPluginMetadata(component, pluginName),
+ getCommandSetup(),
+ ]);
+
+ // Reminder: Any asynchronous code must be run before this point.
+ tinyMCE.PluginManager.add(pluginName, (editor) => {
+ // Register any options that your plugin has
+ registerOptions(editor);
+
+ // Setup any commands such as buttons, menu items, and so on.
+ setupCommands(editor);
+
+ // Return the pluginMetadata object. This is used by TinyMCE to display a help link for your plugin.
+ return pluginMetadata;
+ });
+
+ resolve([pluginName, Configuration]);
+});
+```
+
+
+
+The plugin can be broadly broken down into several different areas:
+
+#### The default export
+
+Every plugin must return a default export containing a new Promise.
+
+This allows the API to load multiple plugins in parallel with minimal blocking.
+
+```javascript
+// Imports go here.
+export default new Promise(async(resolve) => {
+ // Configure the plugin here.
+
+ // Resolve when the plugin has been configured.
+ resolve([pluginName, Configuration]);
+});
+```
+
+#### Preparation
+
+The TinyMCE API does not support asynchronous code in the plugin registration. Therefore any asynchronous tasks must be complete before registering the plugin with the TinyMCE API, and before resolving the Promise.
+
+In the following example, we fetch the tinyMCE API, a set of plugin metadata to use, and a command setup function to call later on.
+
+```javascript
+// Note: The PluginManager.add function does not support asynchronous configuration.
+// Perform any asynchronous configuration here, and then call the PluginManager.add function.
+const [
+ tinyMCE,
+ pluginMetadata,
+ setupCommands,
+] = await Promise.all([
+ getTinyMCE(),
+ getPluginMetadata(component, pluginName),
+ getCommandSetup(),
+]);
+```
+
+#### Registration of the plugin
+
+Once all of the dependencies are available, we can register the plugin with the TinyMCE PluginManager API.
+
+In this example, we register a plugin whose name is represented as a string in the `pluginName` variable.
+
+Whenever a new editor instance is created, it will call the callback providing the `editor` argument.
+
+At the end of the plugin instantiation, it returns a `pluginMetadata` object, which contains information about the plugin displayed in the help dialogue for the plugin.
+
+```javascript
+// Reminder: Any asynchronous code must be run before this point.
+tinyMCE.PluginManager.add(pluginName, (editor) => {
+ // Register any options that your plugin has
+ registerOptions(editor);
+
+ // Setup any commands such as buttons, menu items, and so on.
+ setupCommands(editor);
+
+ // Return the pluginMetadata object. This is used by TinyMCE to display a help link for your plugin.
+ return pluginMetadata;
+});
+```
+
+In this example, the plugin describes a set of options which will be passed from the PHP description of the plugin - these are handled by the `registerOptions(editor)` call.
+
+It also has a set of 'commands', which are a generic term used to describe any Buttons, MenuItems, and related UI features of the editor.
+
+### commands.js
+
+TinyMCE supports a range of commands. These are further defined in the TinyMCE API: [tinymce.editor.ui.Registry](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/).
+
+Most plugins will make use of one or more of the following commands, but others are also available:
+
+- [addIcon](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addIcon)
+- [addButton](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addButton)
+- [addToggleButton](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addToggleButton)
+- [addMenuItem](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addMenuItem)
+- [addToggleMenuItem](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addToggleMenuItem)
+
+Plugins may use any parts of the TinyMCE API that they need.
+
+
+ An example commands.js file generated by the plugin skeleton generator
+
+```javascript tilte="commands.js"
+import {getButtonImage} from 'editor_tiny/utils';
+import {get_string as getString} from 'core/str';
+import {
+ component,
+ startdemoButtonName,
+ startdemoMenuItemName,
+ icon,
+} from './common';
+
+/**
+ * Handle the action for your plugin.
+ * @param {TinyMCE.editor} editor The tinyMCE editor instance.
+ */
+const handleAction = (editor) => {
+ // TODO Handle the action.
+ window.console.log(editor);
+};
+
+export const getSetup = async() => {
+ const [
+ startdemoButtonNameTitle,
+ startdemoMenuItemNameTitle,
+ buttonImage,
+ ] = await Promise.all([
+ getString('button_startdemo', component),
+ getString('menuitem_startdemo', component),
+ getButtonImage('icon', component),
+ ]);
+
+ return (editor) => {
+ // Register the Moodle SVG as an icon suitable for use as a TinyMCE toolbar button.
+ editor.ui.registry.addIcon(icon, buttonImage.html);
+
+ // Register the startdemo Toolbar Button.
+ editor.ui.registry.addButton(startdemoButtonName, {
+ icon,
+ tooltip: startdemoButtonNameTitle,
+ onAction: () => handleAction(editor),
+ });
+
+ // Add the startdemo Menu Item.
+ // This allows it to be added to a standard menu, or a context menu.
+ editor.ui.registry.addMenuItem(startdemoMenuItemName, {
+ icon,
+ text: startdemoMenuItemNameTitle,
+ onAction: () => handleAction(editor),
+ });
+ };
+};
+```
+
+
+
+:::important A note about synchronicity
+
+The TinyMCE `PluginManager.add` function requires all code to be called synchronously - that is to say that all Promises must be resolved before it is called.
+
+See more information on the Editor instance in the [tinymce.Editor](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor/) API documentation.
+
+:::
+
+#### `handleAction(editor)`
+
+The handleAction function is an example of one way in which the various buttons and menu items can handle their activation.
+
+The action passes a reference to the _instance_ of the TinyMCE editor in the `editor` variable.
+
+It should be possible to interact with all required parts fo the TinyMCE API using this value.
+
+#### `getSetup()`
+
+`getSetup()` function in the example above is an asynchronous function which returns a synchronous function.
+
+This is important because the TinyMCE PluginManager API requires all code to be synchronous and already exist.
+
+In this example strings are fetched fro the button and menu titles, and the icon is fetched using a Mustache Template. All of these functions return a Promise and therefore we must wait for them to resolve before returning the function which uses them.
+
+#### The curried setup function
+
+The `getSetup()` function returns a new function which is called from `plugin.js` during the instantiation of _each editor instance_. If you have five editors on a page, then this function is called five times - once per editor instance.
+
+This function is passed a partially-configured [tinymce.Editor](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor/) instance on which it can call the registry commands to define the various buttons.
+
+### Plugin options - `options.js` and `plugininfo.php`
+
+There are often times that you will want to pass options, or values stored in Moodle's PHP API, to the JavaScript API in your plugin. TinyMCE has a detailed API to support parsing and validation of per-instance options which make this relatively easy.
+
+To help make this easier, several helpers exist.
+
+On the PHP side of the API, the `plugininfo.php` file can implement an optional `plugin_with_configuration` interface and define a `get_plugin_configuration_for_context` function:
+
+```php title="lib/editor/tiny/plugins/example/classes/plugininfo.php"
+ 'TODO Calculate your values here',
+ ];
+ }
+}
+```
+
+The values passed in may come from location including as site configuration values (that is `get_config()`), values based on capabilities, or values based on the `$options` passed in.
+
+On the JavaScript side of the API, an `options.js` file is used to handle parsing, validation, and fetching of the values.
+
+
+
+A complete example of the options.js implementation
+
+```javascript title="lib/editor/tiny/plugins/example/amd/src/options.js"
+import {getPluginOptionName} from 'editor_tiny/options';
+import {pluginName} from './common';
+
+// Helper variables for the option names.
+const myFirstPropertyName = getPluginOptionName(pluginName, 'myFirstProperty');
+
+/**
+ * Options registration function.
+ *
+ * @param {tinyMCE} editor
+ */
+export const register = (editor) => {
+ const registerOption = editor.options.register;
+
+ // For each option, register it with the editor.
+ // Valid type are defined in https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editoroptions/
+ registerOption(myFirstPropertyName, {
+ processor: 'number',
+ });
+};
+
+/**
+ * Fetch the myFirstProperty value for this editor instance.
+ *
+ * @param {tinyMCE} editor The editor instance to fetch the value for
+ * @returns {object} The value of the myFirstProperty option
+ */
+export const getMyFirstProperty = (editor) => editor.options.get(myFirstPropertyName);
+```
+
+
+
+After being passed from the PHP API, the Moodle integration names properties according to the plugin from which they originate. In order to fetch the value out, you must use the `getPluginOptionName()` function to translate this value.
+
+```javascript title="Getting the namespaced option name"
+const myFirstPropertyName = getPluginOptionName(pluginName, 'myFirstProperty');
+```
+
+Before being set, or fetched back out, each property must be registered with the TinyMCE API. This is done for each _instance_ of the editor:
+
+```javascript title="Registering the option"
+export const register = (editor) => {
+ const registerOption = editor.options.register;
+
+ // For each option, register it with the editor.
+ // Valid type are defined in https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editoroptions/
+ registerOption(myFirstPropertyName, {
+ processor: 'string',
+ });
+};
+```
+
+:::info
+
+See the [tinymce.EditorOptions](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editoroptions/) API documentation for further details on the `register` method.
+
+:::
+
+Finally, it is recommended that you then create helpers to fetch the values back out.
+
+```javascript title="Retrieving the stored value"
+export const getMyFirstProperty = (editor) => editor.options.get(myFirstPropertyName);
+```
+
+:::info
+
+Editor options are specific to an _instance_ of the editor, therefore the `editor` must be passed as an argument.
+
+:::
+
+You may have multiple helpers, for example you may have a helper to process the value and return a boolean state of the value.
+
+```javascript title="Processing an option"
+export const hasMyFirstProperty = (editor) => editor.options.isSet(myFirstPropertyName);
+```
+
+### Editor configuration - `configuration.js`
+
+The TinyMCE Editor only allows for very minimal configuration by administrators. This is a deliberate decision made to reduce the complexity of the User Interface, and to encourage consistency.
+
+To support this, for a plugin to place commands (that is buttons, and menu items) in the User Interface, they must modify the TinyMCE configuration.
+
+The TinyMCE [Menus](https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/#menu) and [Toolbar](https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/) configuration describes the expected format of this configuration.
+
+In order to make your configuration changes you must create a `Configuration` object, which contains a `configure()` function. This value should be resolved at the end of your `plugin.js` file, for example:
+
+```javascript title="Defining a Configuration"
+import * as Configuration from './configuration';
+
+export default new Promise(async(resolve) => {
+ // ... Plugin code goes here.
+
+ resolve([pluginName, Configuration]);
+})
+```
+
+The Moodle TinyMCE Integration will call `Configuration.configure` after the initial editor configuration has been put in place.
+
+The `configure` function is passed the current configuration and must return an object which is merged into the configuration.
+
+For example, to add an item to the 'content' toolbar region, you would define your `configure` function return an Object containing the `toolbar` key:
+
+```javascript title="configuration.js"
+export const configure = (instanceConfig) => {
+ return {
+ toolbar: addToolbarButtons(instanceConfig.toolbar, 'content', buttonName),
+ };
+};
+```
+
+This is used to replace any matching keys in the existing `instanceConfig`.
+
+:::note
+
+The above example makes use of a helper from the `editor_tiny/utils` module, which contains a number of useful helpers.
+
+:::
+
+
+
+A complete example of configuring a menu and toolbar for tiny_example
+
+```javascript title="configuration.js"
+
+import {
+ startdemoButtonName,
+ startdemoMenuItemName,
+} from './common';
+
+import {
+ addMenubarItem,
+ addToolbarButtons,
+} from 'editor_tiny/utils';
+
+const getToolbarConfiguration = (instanceConfig) => {
+ let toolbar = instanceConfig.toolbar;
+ toolbar = addToolbarButtons(toolbar, 'content', [
+ startdemoButtonName,
+ ]);
+
+ return toolbar;
+};
+
+const getMenuConfiguration = (instanceConfig) => {
+ let menu = instanceConfig.menu;
+ menu = addMenubarItem(menu, 'file', [
+ startdemoMenuItemName,
+ ].join(' '));
+
+ return menu;
+};
+
+export const configure = (instanceConfig) => {
+ return {
+ toolbar: getToolbarConfiguration(instanceConfig),
+ menu: getMenuConfiguration(instanceConfig),
+ };
+};
+```
+
+
+
+## CSS styles
+
+CSS styles that apply to the editor controls and its popup windows can be placed in the normal Moodle `styles.css` file, because these controls are part of the normal Moodle page.
+
+If there are styles that need to apply to the actual content being edited, this appears in a separate iframe so the normal styles have no effect. These styles should be placed in a file called `editor_styles.css`. You might need to do this if your plugin inserts HTML within the content such as a custom `` tag which needs particular styling.
+
+:::note
+
+Care should be taken when adding content styles so that generated content is not tied to the editor plugin. If the plugin is removed (or users choose a different editor), it should not break content.
+
+:::
diff --git a/versioned_docs/version-4.5/apis/plugintypes/tiny/testing.md b/versioned_docs/version-4.5/apis/plugintypes/tiny/testing.md
new file mode 100644
index 0000000000..b08ca16a41
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/plugintypes/tiny/testing.md
@@ -0,0 +1,181 @@
+---
+title: Testing
+tags:
+ - editor_tiny
+ - tiny
+ - Editor
+ - Behat
+ - Testing
+---
+
+Testing is an important part of development and we strongly encourage you to write appropriate tests for your plugins.
+
+:::note
+
+When writing tests which only _use_ a text editor, and are not either tests specifically for that editor, or one of its plugins, you should attempt not to use editor-specific tests.
+
+:::
+
+## Behat
+
+### Tags
+
+If you are writing a test for the TinyMCE editor, please note that your test **must** have one or more of the following Behat tags:
+
+- `@editor_tiny`
+- `@tiny_[pluginname]`
+
+By using these tags appropriately, Moodle will ensure that TinyMCE is set as the default editor for the site for the duration of your test, regardless of any other editors installed.
+
+### Useful steps
+
+A number of useful Behat steps have been defined in [TinyMCE Behat context](https://github.com/moodle/moodle/blob/main/lib/editor/tiny/tests/behat/behat_editor_tiny.php).
+
+:::caution Use generic steps where possible
+
+Typically, when interacting with an Editor, you should use the _generic_ step definitions rather than writing specific steps, or writing sequences of steps to do so.
+
+:::
+
+#### Generic steps to interact with editors
+
+Most of these steps are defined in the [Behat Forms context](https://github.com/moodle/moodle/blob/main/lib/tests/behat/behat_forms.php) and this documentation should not be treated as a complete list, rather an indication of approaches that you may consider taking.
+
+##### Setting content of a field
+
+```gherkin title="Set the content of the field with the label 'Description'"
+Given I set the field "Description" to "
Some value which includes valid HTML
"
+Given I set the field "Description" to multiline:
+"""
+
Some value which includes valid HTML
+"""
+```
+
+```gherkin title="Set the content of the field with the label 'Description' within the 'Create event' modal"
+Given I set the field "Description" in the "Create event" "dialogue" to "
Some value which includes valid HTML
"
+```
+
+```gherkin title="Set the content of the field with the label 'Description' along side some other fields"
+Given I set the following fields to these values:
+ | Name | Some name here |
+ | Description |
Some value which includes valid HTML
|
+```
+
+```gherkin title="Set the content of the field with the label 'Description' along side some other fields within the 'Create event' modal"
+Given I set the following fields in the "Create event" "dialogue" to these values:
+ | Name | Some name here |
+ | Description |
Some value which includes valid HTML
|
+```
+
+##### Checking the content of a field
+
+```gherkin title="Check that the content of the field with the label 'Description' matches a value"
+Then the field "Description" matches value "
Some value which includes valid HTML
"
+Then the field "Description" matches multiline:
+"""
+
Some value which includes valid HTML
+"""
+```
+
+```gherkin title="Check that the content of the field with the label 'Description' does not match a value"
+Then the field "Description" does not match value "
Some value which includes valid HTML
"
+```
+
+```gherkin title="Check that the content of the field with the label 'Description' within the 'Create event' modal matches a value"
+Then the field "Description" in the "Create event" "dialogue" matches value "
Some value which includes valid HTML
"
+```
+
+```gherkin title="Check that the content of a set of fields match values"
+Then the following fields match these values:
+ | Name | Some name here |
+ | Description |
Some value which includes valid HTML
|
+```
+
+```gherkin title="Check that the content of a set of fields within the 'Create event' modal match values"
+Then the following fields in the "Create event" "dialogue" match these values:
+ | Name | Some name here |
+ | Description |
Some value which includes valid HTML
|
+```
+
+#### TinyMCE specific steps
+
+If you are writing a TinyMCE plugin or feature, then you may need to interact with the editor interface, or to check and set the current value of the editor.
+
+:::note iFrames
+
+Behat makes use of WebDriver to control browsers. The WebDriver specification limits the scope of a command to the current _context_ where a context is a browser Window, Frame, or iFrame.
+
+TinyMCE places the Editor UI within its own iFrame. To control any of its interface, you _may_ need to switch to the iFrame for your editor first.
+
+Most of the steps written for TinyMCE are already aware of the iFrame.
+
+:::
+
+##### Working with toolbar buttons
+
+```gherkin title="Expanding all toolbar buttons"
+Given I expand all toolbars for the "Description" TinyMCE editor
+```
+
+:::caution
+
+The default TinyMCE configuration will automatically collapse the toolbar to show as many elements as will fit on the page.
+
+You should always use the "I expand all toolbars" step before interacting with a button on the toolbar as it may not be visible in all cases.
+
+This step is safe to call if the toolbar is already expanded, or does not need to be expanded.
+
+:::
+
+```gherkin title="Clicking on a button for your editor"
+When I click on the "Bold" button for the "Description" TinyMCE editor
+```
+
+```gherkin title="Checking the state of a button in your editor"
+Then the "Bold" button of the "Description" TinyMCE editor has state "true"
+```
+
+##### Working with the menu bar
+
+```gherkin title="Clicking on a menu bar button"
+I click on the "Insert" menu item for the "Description" TinyMCE editor
+```
+
+```gherkin title="Clicking on a nested menu bar button"
+I click on the "Insert > Image" menu item for the "Description" TinyMCE editor
+```
+
+##### Selecting content
+
+```gherkin title="Selecting content by tag name and index"
+I select the "p" element in position "2" of the "Description" TinyMCE editor
+```
+
+```gherkin title="Selecting content using a selector and type"
+I select the ".example_placeholder" "css_element" in the "Description" TinyMCE editor
+```
+
+:::note
+
+All content selection is performed within the context of the editor's own iframe. Please note that some of the standard Behat locators will not work as they expect certain elements to be present within the page body.
+
+:::
+
+### Writing your own steps
+
+If you are writing your own steps, then we recommend that you:
+
+- include the name of your plugin, or "TinyMCE editor" in the wording of the step so that it is not mis-used for another purpose
+- check that the Feature, or Scenario, includes the frankenstyle component name for your plugin
+
+```php title="Checking for a tag on your Feature or Scenario"
+if (!$this->has_tag('tiny_myplugin')) {
+ throw new DriverException(
+ 'This step must have the @tiny_myplugin tag on either the Feature, or the Scenario.'
+ );
+}
+```
+
+### Important notes
+
+Some of these steps will be improved in coming changes. See MDL-75926 and MDL-76853 for more information.
diff --git a/versioned_docs/version-4.5/apis/subsystems/_category_.yml b/versioned_docs/version-4.5/apis/subsystems/_category_.yml
new file mode 100644
index 0000000000..5586419cca
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/_category_.yml
@@ -0,0 +1,3 @@
+label: Subsystems
+link:
+ type: generated-index
diff --git a/versioned_docs/version-4.5/apis/subsystems/_roles/Moodle-contexts-1.8.png b/versioned_docs/version-4.5/apis/subsystems/_roles/Moodle-contexts-1.8.png
new file mode 100644
index 0000000000..18e8fc4001
Binary files /dev/null and b/versioned_docs/version-4.5/apis/subsystems/_roles/Moodle-contexts-1.8.png differ
diff --git a/versioned_docs/version-4.5/apis/subsystems/access.md b/versioned_docs/version-4.5/apis/subsystems/access.md
new file mode 100644
index 0000000000..28e85e6e54
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/access.md
@@ -0,0 +1,235 @@
+---
+title: Access API
+tags:
+ - Access
+---
+
+
+
+The Access API gives you functions so you can determine what the current user is allowed to do. It also allows plugins to extend Moodle with new capabilities.
+
+## Overview
+
+Moodle uses a role-based access control model. Entities are represented by contexts which are arranged into a tree-like hierarchy known as the context tree.
+
+The following context types are available:
+
+| Context name | Represents | Immediate contents | Notes |
+| --- | --- | --- | --- |
+| `context_system` | The site as a whole | user, course category, module, and block | The System context is root context in the tree. There is only one System context |
+| `context_user` | An individual user | block | Each user has their own, unique, context |
+| `context_coursecat` | A single course category | course category, course, block | |
+| `context_course` | A single course | module, block | |
+| `context_module` | An activity | block | |
+| `context_block` | A block | none | |
+
+A Role is a set of capability definitions, where each capability represents something that the user is able to do. Roles are defined at the top most
+context in the context tree, the System context.
+
+Roles can be overridden by contexts further down the tree.
+
+User access is calculated from the combination of roles which are assigned to each user.
+
+All users that did not log-in yet automatically get the default role defined in `$CFG->notloggedinroleid`, it is not possible to assign any other role to this non-existent user id. There is one special guest user account that is used when user logs in using the guest login button or when guest auto-login is enabled. Again you can not assign any roles to the guest account directly, this account gets the `$CFG->guestroleid` automatically. All other authenticated users get the default user role specified in `$CFG->defaultuserroleid` and in the frontpage context the role specified in `$CFG->defaultfrontpageroleid`.
+
+
+
+## How to define new capabilities in plugins
+
+Capabilities are defined by `$capabilities` array defined in `db/access.php` files. The name of the capability consists of `plugintype/pluginname:capabilityname`.
+
+For example:
+
+```php title="mod/folder/db/access.php"
+$capabilities = [
+ 'mod/folder:managefiles' => [
+ 'riskbitmask' => RISK_SPAM,
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => [
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+];
+```
+
+Where the meaning of array keys is:
+
+| Field | Description |
+| --- | --- |
+| `riskbitmask` | associated risks. These are explained on [Hardening new Roles system](./roles.md). |
+| `captype` | _read_ or _write_ capability type, for security reasons system prevents all write capabilities for guest account and not-logged-in users |
+| `contextlevel` | specified as context level constant. Declares the typical context level where this capability is checked. This capability can be checked with contexts that are at a lower level (e.g. `moodle/site:accessallgroups` could be checked with CONTEXT_MODULE). |
+| `archetypes` | specifies defaults for roles with standard archetypes, this is used in installs, upgrades and when resetting roles (it is recommended to use only CAP_ALLOW here). Archetypes are defined in mdl_role table. See also [Role archetypes](https://docs.moodle.org/dev/Role_archetypes). |
+| `clonepermissionsfrom` | when you are adding a new capability, you can tell Moodle to copy the permissions for each role from the current settings for another capability. This may give better defaults than just using archetypes for administrators who have heavily customised their roles configuration. The full syntax is: `clonepermissionsfrom` => `moodle/quiz:attempt` |
+
+It is necessary to bump up plugin version number after any change in db/access.php, so that the upgrade scripts can make the necessary changes to the database. To run the upgrade scripts, log in to Moodle as administrator, navigate to the site home page, and follow the instructions. (If you need to test the upgrade script without changing the plugin version, it is also possible to set back the version number in the mdl_block or mdl_modules table in the database.)
+
+The capability names are defined in plugin language files, the name of the string consists of "pluginname:capabilityname", in the example above it would be:
+
+```php title="mod/folder/lang/en/folder.php"
+$string['folder:managefiles'] = 'Manage files in folder module';
+```
+
+### Deprecating a capability
+
+When a capability is no longer needed or is replaced by another, it should be deprecated. The timeline for deprecation should follow the normal [Deprecation](/general/development/policies/deprecation) process.
+
+To mark a capability as deprecated, edit the access.php containing the capability, remove it from the `$capabilities` array, and add it to the `$deprecatedcapabilities` array in this file.
+
+Entries in `$deprecatedcapabilities` can have a `replacement` key indicating a new or existing capability that replaces the deprecated one. If this is specified, any checks to the deprecated capability will check the replacement capability instead. A debugging message will always be output at `DEBUG_DEVELOPER` level if a deprecated capability is checked.
+
+`$deprecatedcapabilities` can also define an optional `message` explaining the deprecation.
+
+The following example demonstrates an access.php file where a capability has been deprecated and replaced with another.
+
+```php title="mod/folder/db/access.php"
+$capabilities = [
+ 'mod/folder:newmanagefiles' => [
+ 'riskbitmask' => RISK_SPAM,
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => [
+ 'editingteacher' => CAP_ALLOW,
+ ],
+ ],
+];
+
+$deprecatedcapabilities = [
+ 'mod/folder:managefiles' => [
+ 'replacement' => 'mod/folder:newmanagefiles',
+ 'message' => 'This was replaced with another capability'
+ ],
+];
+```
+
+## Useful functions and classes
+
+### Context fetching
+
+In plugins context instances are usually only instantiated because they are instantiated and deleted automatically by the system.
+
+Fetching by object id:
+
+```php
+$systemcontext = context_system::instance();
+$usercontext = context_user::instance($user->id);
+$categorycontext = context_coursecat::instance($category->id);
+$coursecontext = context_course::instance($course->id);
+$contextmodule = context_module::instance($cm->id);
+$contextblock = context_block::instance($this->instance->id);
+```
+
+Fetching by context id:
+
+```php
+$context = context::instance_by_id($contextid);
+```
+
+Notes:
+
+- by default exception is thrown if context can not be created
+- deleted users do not have contexts any more
+
+### Determining that a user has a given capability
+
+When implementing access control always ask "Does the user have capability to do something?". It is incorrect to ask "Does the user have a role somewhere?".
+
+#### has_capability()
+
+`has_capability()` is the most important function:
+
+```php
+function has_capability(
+ string $capability,
+ context $context,
+ object $user = null,
+ bool $doanything = true
+): bool;
+```
+
+Check whether a user has a particular capability in a given context. For example:
+
+```php
+$context = context_module::instance($cm->id);
+if (has_capability('mod/folder:managefiles', $context)) {
+ // Do or display something.
+}
+```
+
+By default checks the capabilities of the current user, but you can pass a different user id. By default will return true for admin users, it is not recommended to use false here.
+
+#### require_capability()
+
+Function require_capability() is very similar, it is throwing access control exception if user does not have the capability.
+
+```php
+function require_capability($capability, context $context, $userid = null, $doanything = true, $errormessage = 'nopermissions', $stringfile = _) {
+```
+
+### Enrolment functions
+
+See [Enrolment API](./enrol.md).
+
+### Other related functions
+
+```php
+function require_login($courseorid = null, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false)
+function require_course_login($courseorid, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false)
+function get_users_by_capability(context $context, $capability, $fields = _, $sort = _, $limitfrom = _, $limitnum = _,
+ $groups = _, $exceptions = _, $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false)
+function isguestuser($user = null)
+function isloggedin()
+function is_siteadmin($user_or_id = null)
+function is_guest(context $context, $user = null)
+function is_viewing(context $context, $user = null, $withcapability = _)
+```
+
+#### `require_login()`
+
+Each plugin script should include require_login() or require_course_login() after setting up PAGE->url.
+
+This function does following:
+
+- it verifies that user is logged in before accessing any course or activities (not-logged-in users can not enter any courses).
+- user is logged in as gu
+- verify access to hidden courses and activities
+- if an activity is specified, verify any [availability restrictions](./availability/index.md) for the activity
+- verify that user is either enrolled or has capability 'moodle/course:view' or some enrol plugin gives them temporary guest access
+- logs access to courses
+
+#### `require_course_login()`
+
+This function is supposed to be used only in activities that want to allow read access to content on the frontpage without logging-in. For example view resource files, reading of glossary entries, etc.
+
+#### `isguestuser()`, `isloggedin()` and `is_siteadmin()`
+
+These function were previously needed for limiting of access of special accounts. It is usually not necessary any more, because any *write* or *risky* capabilities are now automatically prevented in has_capability().
+
+It is strongly discouraged to use is_siteadmin() in activity modules, please use standard capabilities and enrolment status instead.
+
+#### `is_guest()`, `is_viewing()` and `is_enrolled()`
+
+In order to access course data one of these functions must return true for user:
+
+- `is_enrolled()` - user has active record in user_enrolments table
+- `is_viewing()` - user has 'moodle/course:view' capability (may access course, but is not considered to be participant)
+- `is_guest()` - user was given temporary guest access by some enrolment plugin
+
+#### `get_users_by_capability()`
+
+This method returns list of users with given capability, it ignores enrolment status and should be used only above the course context.
+
+## See also
+
+- [API guides](../../apis.md)
+- [Roles](./roles.md)
+- [Role archetypes](https://docs.moodle.org/dev/Role_archetypes)
+- [Hardening new Roles system](./roles.md)
+- [Roles and modules](https://docs.moodle.org/dev/Roles_and_modules)
+- [NEWMODULE Adding capabilities](https://docs.moodle.org/dev/NEWMODULE_Adding_capabilities)
+- [New permissions evaluation in 2.0](https://docs.moodle.org/dev/New_permissions_evaluation_in_2.0)
+- [(Forums) How to check if current user is student?](https://moodle.org/mod/forum/discuss.php?d=257611)
diff --git a/versioned_docs/version-4.5/apis/subsystems/admin/index.md b/versioned_docs/version-4.5/apis/subsystems/admin/index.md
new file mode 100644
index 0000000000..ca5e03e18c
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/admin/index.md
@@ -0,0 +1,224 @@
+---
+title: Admin settings
+tags: []
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Moodle's configuration is stored in a mixture of the config, config_plugins, and a few other tables. These settings are edited through the administration screens, which can be accessed by going to the `.../admin/index.php` URL on your moodle site, or using the Administration block that appears to administrators of the Moodle front page. This page explains how the code for displaying and editing of these settings works.
+
+## Where to find the code
+
+This is explained further below, but in summary:
+
+- The library code is all in `lib/adminlib.php`.
+- The definition of all the parts of the admin tree is in admin/settings/* some of which call out to plugins to see if they have settings they want to add.
+- The editing and saving of settings is managed by `admin/settings.php`, `admin/upgradesettings.php`, and `admin/search.php`.
+- The administration blocks that appear on the front page and on most of the admin screens is in `blocks/admin_tree` and `blocks/admin_bookmarks`.
+
+For further details refer the code.
+
+## The building blocks
+
+All the settings are arranged into a tree structure. This tree structure is represented in memory as a tree of PHP objects.
+
+At the root of the tree is an **admin_root** object.
+
+That has children that are **admin_category**s.
+
+Admin categories contain other categories, **admin_settingpage**s, and **admin_externalpage**s.
+
+Settings pages contain individual **admin_setting**s.
+
+admin_setting is a base class with lots of subclasses like **admin_setting_configtext**, **admin_setting_configcheckbox**, and so on. If you need to, you can create new subclasses.
+
+External pages are for things that do not fit into the normal settings structure. For example the global assign roles page, or the page for managing activity modules.
+
+## How the tree is built
+
+When Moodle needs the admin tree, it calls admin_get_root in `lib/adminlib.php`, which
+
+1. Creates a global $ADMIN object which is an instance of admin_root.
+2. Does `require_once admin/settings/top.php`, which adds the top level categories to $ADMIN.
+3. Does `require_once` on all the other files in `admin/settings` to add more specific settings pages and the settings themselves. Some of these settings files additionally make calls out to various types of plugins. For example:
+ - `admin/settings/plugins.php` gives activity modules, blocks, question types, ... a chance to add admin settings.
+4. Adds the admin reports to the tree.
+
+As an optimisation, before building each bit of the tree, some capability checks are performed, and bits of the tree are skipped if the current user does not have permission to access them.
+
+## Settings file example
+
+This is an example of a settings.php file provided by a local_helloworld plugin. The file declares a single checkbox configuration variable called "showinnavigation".
+
+```php
+.
+
+/**
+ * Adds admin settings for the plugin.
+ *
+ * @package local_helloworld
+ * @category admin
+ * @copyright 2020 Your Name
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+ $ADMIN->add('localplugins', new admin_category('local_helloworld_settings', new lang_string('pluginname', 'local_helloworld')));
+ $settingspage = new admin_settingpage('managelocalhelloworld', new lang_string('manage', 'local_helloworld'));
+
+ if ($ADMIN->fulltree) {
+ $settingspage->add(new admin_setting_configcheckbox(
+ 'local_helloworld/showinnavigation',
+ new lang_string('showinnavigation', 'local_helloworld'),
+ new lang_string('showinnavigation_desc', 'local_helloworld'),
+ 1
+ ));
+ }
+
+ $ADMIN->add('localplugins', $settingspage);
+}
+```
+
+Few things to highlight:
+
+- Note the variable $hassiteconfig which can be used as a quick way to check for the moodle/site:config permission. This variable is set by the top-level admin tree population scripts.
+- We always add the custom admin_settingpage to the tree, but the actual settings are added to that page only when $ADMIN->fulltree is set. This is to improve performance when the caller does not need the actual settings but only the administration pages structure.
+- The lang_string class instances are used as proxy objects to represent strings. This has performance benefits as there is no need to evaluate all strings in the admin settings tree unless they are actually displayed.
+
+## Individual settings
+
+Let us look at a simple example: [mod/lesson/settings.php](https://github.com/moodle/moodle/blob/main/mod/lesson/settings.php). This is included by admin/settings/plugins.php, which has already created $settings, which is an admin_settingpage that we can add to. The file contains lots of lines that look a bit like:
+
+```php
+$settings->add(new admin_setting_configtext('mod_lesson/mediawidth', get_string('mediawidth', 'lesson'),
+ get_string('configmediawidth', 'lesson'), 640, PARAM_INT));
+```
+
+What this means is that to our settings page, we are adding a text setting. To understand this in more detail, we need to know what arguments the constructor for this admin_setting_configtext takes. The signature of the constructor is:
+
+```php
+public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null)
+```
+
+- $name here is 'mod_lesson/mediawidth'. The slash is important here. It indicates that this setting is owned by the mod_lesson plugin and should be stored in the config_plugins table. Settings that do not have this slash would be stored in the global config table and also exposes them via $CFG properties. Plugin-scope settings can be obtained only via get_config() call. You may notice some older plugins such as the Forum module or many blocks, still store their settings in the global config table. That is strongly discouraged for new plugins.
+- $visiblename is set to a string get_string('mediawidth', 'lesson'), this is the label that is put in front of setting on the admin screen.
+- $description is set to another string, this is a short bit of text displayed underneath the setting to explain it further. Older plugins used strings prefixed with "config" for this purpose. The current best practice is to use "_desc" prefix for them - see [String API#Help strings](https://docs.moodle.org/dev/String_API#Help_strings)
+- $defaultsetting is the default value for this setting. This value is used when Moodle is installed. For simple settings like checkboxes and text fields, this is a simple value. For some more complicated settings, this is an array.
+- $paramtype is one of the constants describing the type of the input text. The inserted value will be sanitised according to the declared type.
+- $size allows to specify custom size of the field in the user interface
+
+Let us now look at a more complicated example, from mod/quiz/settingstree.php:
+
+```php
+ $quizsettings->add(new admin_setting_text_with_advanced('quiz/timelimit', get_string('timelimit', 'quiz'), get_string('timelimit_desc', 'quiz'),
+ ['value' => '0', 'fix' => false], PARAM_INT));
+```
+
+*Note:* the naming convention used here is slightly outdated; new activity modules should use 'mod_mymodule/setting' instead of 'mymodule/setting' as identifier.
+
+This example shows a $defaultsetting that is an array.
+
+Normally, if you want a particular sort of setting, the easiest way is to look around the admin screens of your Moodle site, and find a setting like the one you want. Then go and copy the code and edit it. Therefore, we do not include a complete list of setting types here.
+
+## Locked and advanced settings for activity modules
+
+Admin settings are often used to define the defaults for activity settings. There is a simple way to enable admins make activity settings as "locked" (cannot be changed from the default) or "advanced" (deprecated, used before the change to "short forms").
+
+When creating the admin setting (e.g. in mod/assign/settings.php), create the setting like this:
+
+```php
+$name = new lang_string('teamsubmission', 'mod_assign');
+$description = new lang_string('teamsubmission_help', 'mod_assign');
+$setting = new admin_setting_configcheckbox('assign/teamsubmission',
+ $name,
+ $description,
+ 1);
+$setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
+$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
+$settings->add($setting);
+```
+
+To add "locked" and "advanced" check boxes to the admin setting.
+
+And finally, when creating the activity form (e.g. in mod/assign/mod_form.php), call this:
+
+```php
+$this->apply_admin_defaults();
+```
+
+at the end of the constructor to automatically set the default and lock the form element if required.
+
+Note: Locking an admin setting **will not force the value on existing settings*. Activity settings that are locked will need to be manually updated if they differ from the locked default value.
+
+## Callbacks after a setting has been updated
+
+A typical example is purging a cache after a setting has changed:
+
+```php
+$setting = new admin_setting_configcheckbox(......);
+$setting->set_updatedcallback('theme_reset_all_caches');
+```
+
+## External pages
+
+admin_externalpages represent screens of settings that do not fall into the standard pattern of admin_settings. The admin_externalpage object in the settings tree holds the URL of a PHP page that controls various settings.
+
+In that PHP page, near the start you need to call the function admin_externalpage_setup($pagename). Although earlier versions of Moodle required you to then use admin_externalpage_print_header() and admin_externalpage_print_footer() functions instead of the usual print_header and print_footer functions, this is no longer necessary as of Moodle 2.0 - just use $OUTPUT->header() and $OUTPUT->footer() as per usual (don't forget to echo them). This ensures that your page appears with the administration blocks and appropriate navigation.
+
+Note that if your external page relies on additional optional or required params, you may need to use the optional arguments to admin_externalpage_print_header to ensure that the Blocks editing on/off button works.
+
+Note that there are some subclasses of admin_externalpage, for example admin_page_managemods. In a lot of cases, these subclasses only exist to override the search method so this page can be found by appropriate searches.
+
+Once again, to understand this in more depth, your best approach is to look at how some of the external pages in Moodle work.
+
+### When to use an admin_settings vs admin_externalpages
+
+The short answer is wherever possible always try to use admin settings rather than a custom page which uses formslib for anything related to admin settings. If you need something custom investigate a custom admin_setting class before a custom external page. There are a number of reasons, some around usability but mostly related to security:
+
+- **Searching** - Admin settings can be searched for using the admin search, while only the page title of the external page is searchable. In particular it is very useful to search directly for the key name of a config item, but also the settings current value and it's help text. Also the admin settings tree is a public API and there are 3rd party plugins which leverage this for various purposes, so your settings will be invisible.
+- **Manage from CLI** - All admin settings in core and plugins can be managed via admin/cli/cfg.php, you don't get this for free if you store settings manually in a custom table.
+- **Caching** - All admin settings in core and plugins are cached in the MUC and you don't get this for free if you store settings manually in a custom table.
+- **Forced settings** - Normal admin settings can be forced in config.php and this is shown as forced in the GUI. Unless you re-implement this forced logic admins will get the false impression they are saving a setting when they are won't and it will cause a lot of confusion.
+- **Forced passwords** - Password admin settings can be forced in config.php and these are not only locked but hidden from the admin (since Moodle 3.9). You would have to, and should, re-implement this logic on top of the 'forced' logic above.
+- **Executable paths** - A special case of forced settings is paths to executables which should ideally be set in config.php with $CFG->preventexecpath = true; so they cannot be set in the GUI, even if they aren't in config.php. The admin_setting_configexecutable class handles all this logic for you.
+- **Config log** - When an admin setting is changed, the before and after values are appended to the config log and visible in the config changes report. If you ever called set_config on directly on behalf of a human you should call add_to_config_log as well. It is really important to have a strong audit trail of who did what. For normal actions by anyone this should be in the moodle log, for admin settings it should be in the config log.
+
+An OK rule of thumb is: You can use an external page, which might use formslib, when the settings you are changing are in a custom table and not in the config tables via set_config. But you should seriously consider if and why you need a custom table first. If you are writing custom formslib elements it is usually just as easy to write a custom admin_setting instead.
+
+## See also
+
+- [adding settings for activity modules](https://docs.moodle.org/dev/Modules)
+- [adding admin reports to the tree](https://docs.moodle.org/dev/Admin_reports#How_your_report_gets_included_in_the_admin_tree)
+- configuration of repository plugins
+- [configuration for filter](../../plugintypes/filter/index.md)
+- [Other developer documentation](https://docs.moodle.org/dev/Developer_documentation)
diff --git a/versioned_docs/version-4.5/apis/subsystems/ai/index.md b/versioned_docs/version-4.5/apis/subsystems/ai/index.md
new file mode 100644
index 0000000000..2450e02843
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/ai/index.md
@@ -0,0 +1,267 @@
+---
+title: AI Subsystem
+tags:
+ - AI
+ - LLM
+ - Provider
+ - Placement
+---
+
+
+
+The AI Subsystem in Moodle LMS provides a consistent and user-friendly way for users to interact with AI
+in Moodle's user interface, as they do their teaching and learning activities.
+As well as providing a straightforward way to integrate with various AI providers on the backend.
+
+## What is AI in this context?
+
+When we talk about AI in the context of this subsystem, AI is: anything that's plugged in via a Provider
+Plugin and provides one or more Actions.
+
+Artificial Intelligence (AI), Machine Learning (ML), Large Language Models (LLMs), Generative AI, etc.
+are all individual terms with discrete meanings. However, to keep things simple and in the context of
+the subsystem everything is just called AI.
+
+## Design Overview
+
+The AI subsystem consists of the following (main) components:
+
+- **Placements**
+ - Are the UI components and associated workflows that allow users to interact with AI.
+ - They implement one or more AI actions.
+ - They provide a consistent experience for users regardless of which AI is providing the action
+ - They are LMS plugins
+- **Actions**
+ - These are the "things" users can do with AI.
+ - Examples of an action include: generating text, generating an image, providing a chat interface.
+ - Actions don't have a UI, they are classes that provide a predictable interface.
+- **Providers**
+ - Providers are the interface between AI systems and the Moodle LMS.
+ - They implement support for one or more actions.
+ - Providers should not have an UI, apart from those required for settings,
+ to configure and manage the provider. For example an Administration UI to allow setting of API
+ keys; and/or to enable or disable the enabled actions.
+ - The aim of Providers is to make it "easy" to integrate AI systems with LMS,
+ without the need to implement a UI to use them.
+ - They are LMS plugins
+- **Subsystem Manager**
+ - This is implemented at code level and is a core part of the subsystem design.
+ - It is the "controller" that sits between Placements and Providers.
+ - It allows Placements to pass user requests to Providers and handles all the calls to
+ the providers including prioritisation and logging.
+ - It allows Providers to respond to action requests.
+
+### Placements
+
+The aim of Placements is to provide a consistent UX and UI for users when they use AI backed functionality.
+
+Placement plugins leverage the functionality of the other components of the AI subsystem.
+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 LMS plugins in their own right and are not "other" types of LMS plugins,
+it gives great flexibility in how AI functionality is presented to users.
+
+See the [Placements](/apis/plugintypes/ai/placement.md) documentation for more information
+on developing Placement plugins.
+
+### Providers
+
+Provider plugins are the interface between the LMS AI subsystem and external AI systems.
+Their focus is on 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.
+
+Because of this design the Providers that provide the AI Actions can be swapped out, mix and matched
+or upgraded; both without the need to update the Placement code and without the need to change the
+way users interact with the functionality.
+
+See the [Providers](/apis/plugintypes/ai/provider.md) documentation for more information
+on developing Provider plugins.
+
+### Subsystem Manager
+
+The Subsystem Manager is the "controller" that sits between Placements and Providers.
+
+:::warning The Golden Rule:
+
+Placements DO NOT know about Providers, and Providers DO NOT know about Placements.
+Everything should go via the Manager.
+
+:::
+
+The manager class `\core_ai\manager()` is the "controller" for the subsystem.
+In general it will be how most processes will interact with the AI subsystem.
+
+The main method in the class is `process_action(base $action): responses\response_base`
+this is the entry point for Action processing. Every Placement that wants to process an AI action
+calls this method.
+The manager will determine what Providers support this action and then hand off the action object to
+a Provider. The Provider **MUST** return an action response object. This method will also store the
+result of each action call.
+
+The manager class also has various utility methods that can be accessed.
+Such as getting the list of providers for specific actions, which is used to help render
+administration settings.
+
+### AI User Policy
+
+Inline with Moodle's AI Principles and as guided by emerging legislation; users must accept an
+AI policy before using AI in LMS. As the requirements are different to a site policy
+(legislation, seems to indicate it acknowledgement of AI must be made at point of use),
+separate policy functionality has been implemented for the subsystem.
+
+All Placements must implement a check to see if a user has accepted the AI Policy.
+Placements must also provide a way for users to accept the policy.
+If a user has not previously accepted the AI Policy, the Placement must display the policy to the
+user, and the user is not able to use the placement until they accept the policy.
+Users only need to accept the policy once.
+
+To assist Placements with policy display and acceptance the Manager provides the following functionality:
+
+- The Manager makes the following methods available for Placements to call directly:
+ - `\core_ai\manger::get_user_policy(int $userid): bool` -
+ Given a user ID (record id in user table), returns true if the user has accepted the policy,
+ false otherwise. It handles looking up the status and caching the response.
+ - `\core_ai\manager::set_user_policy(int $userid, int $contextid): bool` -
+ Given a user ID and a Context ID (of the context the policy was displayed in) set the policy
+ acceptance.
+- The manager class also makes available webservices to be used for policy setting and getting.
+ This helps Placements set policy acceptance via ajax as part of a UI workflow:
+ - `core_ai_get_policy_status` -
+ Gets the policy status for a user. Calls: `core_ai\external\get_policy_status`
+ - `core_ai_set_policy_status` -
+ Sets the policy status for a user. Calls: `core_ai\external\set_policy_status`
+
+### Actions
+
+Actions provide a structured way to work with AI. Placements create an Action object when they want
+to interact with AI and Providers, and it is an Action that Providers consume.
+
+Actions are defined as classes in the `\core_ai\aiactions` namespace.
+The naming convention for Action classes is `_`,
+for example: `generate_image`, `translate_text`.
+
+Each action **MUST** inherit from the `\core_ai\aiactions\base()` abstract class.
+They must also implement two methods:
+
+- `__construct(...args): void`
+ - The constructor method is allowed to have a variable signature, so that each action can define its own
+ configuration requirements.
+ - The method **MUST** take a `contextid` as one of the variables.
+ - An Action **MUST** be correctly instantiated before it can be used and passed onto the AI manager.
+ For example the constructor method for the generate_image Action is:
+
+```php
+public function __construct(
+ int $contextid,
+ /** @var int The user id requesting the action. */
+ protected int $userid,
+ /** @var string The prompt text used to generate the image */
+ protected string $prompttext,
+ /** @var string The quality of the generated image */
+ protected string $quality,
+ /** @var string The aspect ratio of the generated image */
+ protected string $aspectratio,
+ /** @var int The number of images to generate */
+ protected int $numimages,
+ /** @var string The visual style of the generated image */
+ protected string $style,
+) {
+ parent::__construct($contextid);
+}
+```
+
+- `store(response_base $response): int`
+ - This method is responsible for storing any action specific data related to the action in the
+ LMS database.
+ - Each Action must store its own data can that can be referenced later.
+ - It takes a matching response class, that contains the result of the action call.
+ - For example the store() call form the generate_image Action is:
+
+```php
+#[\Override]
+public function store(response_base $response): int {
+ global $DB;
+
+ $responsearr = $response->get_response_data();
+
+ $record = new \stdClass();
+ $record->prompt = $this->prompttext;
+ $record->numberimages = $this->numimages;
+ $record->quality = $this->quality;
+ $record->aspectratio = $this->aspectratio;
+ $record->style = $this->style;
+ $record->sourceurl = $responsearr['sourceurl']; // Can be null.
+ $record->revisedprompt = $responsearr['revisedprompt']; // Can be null.
+
+ return $DB->insert_record($this->get_tablename(), $record);
+}
+```
+
+It is up to the action to define its own database schema and stored data, that is relevant to
+what the action does. For example the database table definition for the generate_image Action is:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+The naming convention for Action database tables is `ai_action_`,
+for example: `ai_action_generate_image`, `ai_action_translate_text`.
+
+#### Responses
+
+When a Provider processes an Action it **MUST** return a response object.
+This allows Placements to receive an expected response for any Action call.
+Each Action has a matching response class. The provider that processes the Action will instantiate
+an instance of this response class and populate it with the data required for this type of response.
+
+Each Action response MUST inherit from the `\core_ai\aiactions\responses\response_base` abstract
+class. They must also implement two methods:
+
+- `set_response(array $response): void`
+ - Taking an array of response variables (which must be defined as class variables),
+ it sets these against class variables so they can be retrieved by the Manager and calling Placement.
+- `get_response(): array`
+ - Returns the set response data.
+
+For example the `set_response()` for the generate_image Action response is:
+
+```php
+ #[\Override]
+ public function set_response_data(array $response): void {
+ $this->draftfile = $response['draftfile'] ?? null;
+ $this->revisedprompt = $response['revisedprompt'] ?? null;
+ $this->sourceurl = $response['sourceurl'] ?? null;
+ }
+```
+
+And the `get_response()` for the generate_image Action response is:
+
+```php
+ #[\Override]
+ public function get_response_data(): array {
+ return [
+ 'draftfile' => $this->draftfile,
+ 'revisedprompt' => $this->revisedprompt,
+ 'sourceurl' => $this->sourceurl,
+ ];
+ }
+```
+
+The naming convention for Action Response classes is `response_`,
+for example: `response_generate_image`, `response_translate_text`.
diff --git a/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Analytics_API_classes_diagram_(summary).svg b/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Analytics_API_classes_diagram_(summary).svg
new file mode 100644
index 0000000000..e6bbbfdd35
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Analytics_API_classes_diagram_(summary).svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Inspire_API_components.png b/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Inspire_API_components.png
new file mode 100644
index 0000000000..976d1d72cb
Binary files /dev/null and b/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Inspire_API_components.png differ
diff --git a/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Inspire_data_flow.png b/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Inspire_data_flow.png
new file mode 100644
index 0000000000..915a745aee
Binary files /dev/null and b/versioned_docs/version-4.5/apis/subsystems/analytics/_index/Inspire_data_flow.png differ
diff --git a/versioned_docs/version-4.5/apis/subsystems/analytics/index.md b/versioned_docs/version-4.5/apis/subsystems/analytics/index.md
new file mode 100644
index 0000000000..820f4bdf5f
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/analytics/index.md
@@ -0,0 +1,812 @@
+---
+title: Analytics API
+description: The Analytics API allows managers to use predictions to detect trends and predict student behaviour
+tags:
+ - Analytics
+ - API
+---
+
+
+
+The Moodle Analytics API allows Moodle site managers to define _prediction models_ that combine _indicators_ and a _target_.
+
+The _target_ is the event we want to predict. The _indicators_ are what we think will lead to an accurate prediction of the target.
+
+Moodle is able to evaluate these models and, if the prediction accuracy is high enough, Moodle internally trains a machine learning algorithm by using calculations based on the defined indicators within the site data. Once new data that matches the criteria defined by the model is available, Moodle starts predicting the probability that the target event will occur. Targets are free to define what actions will be performed for each prediction, from sending messages or feeding reports to building new adaptive learning activities.
+
+:::note Example
+
+An example of a model you may be interested in is the detection of [students who are at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out).
+
+Possible _indicators_ for this include:
+
+- a lack of participation in previous activities
+- poor grades in previous activities
+
+The _target_ would be whether the student is able to complete the course or not.
+
+Moodle uses these indicators and the target for each student in a finished course to predict which students are at risk of dropping out in ongoing courses.
+
+:::
+
+## Summary
+
+### API components
+
+This diagram shows the main components of the analytics API and the interactions between them.
+
+![Analytics API components and their interactions](./_index/Inspire_API_components.png)
+
+### Data flow
+
+The diagram below shows the different stages data goes through, from the data a Moodle site contains to actionable insights.
+
+![Data flow at different stages](./_index/Inspire_data_flow.png)
+
+### API classes diagram
+
+This is a summary of the API classes and their relationships. It groups the different parts of the framework that can be extended by 3rd parties to create your own prediction models.
+
+![API Class Structure](./_index/Analytics_API_classes_diagram_(summary).svg)
+
+## Built-in models
+
+People use Moodle in very different ways and even courses on the same site can vary significantly. Moodle core only includes models that have been proven to be good at predicting in a wide range of sites and courses. Moodle provides two built-in models:
+
+- [Students at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out)
+- [No teaching](https://docs.moodle.org/en/Analytics#No_teaching)
+
+To diversify the samples and to cover a wider range of cases, the Moodle HQ research team is collecting anonymised Moodle site datasets from collaborating institutions and partners to train the machine learning algorithms with them. The models that Moodle is shipped with will be likely better at predicting on the sites of participating institutions, although some other datasets are used as test data for the machine learning algorithm to ensure that the models are good enough to predict accurately in any Moodle site.
+
+Even if the models included in Moodle core are already trained by Moodle HQ, each different site will continue training that site machine learning algorithms with its own data, which will lead to better prediction accuracy over time.
+
+## Concepts
+
+The following definitions are included for people not familiar with machine learning concepts:
+
+### Training
+
+This is the process to be run on a Moodle site before being able to predict anything. This process records the relationships found in site data from the past so the analytics system can predict what is likely to happen under the same circumstances in the future. What we train are machine learning algorithms.
+
+### Samples
+
+The machine learning backends we use to make predictions need to know what sort of patterns to look for, and where in the Moodle data to look. A sample is a set of calculations we make using a collection of Moodle site data. These samples are unrelated to testing data or phpunit data, and they are identified by an id matching the data element on which the calculations are based. The id of a sample can be any Moodle entity id: a course, a user, an enrolment, a quiz attempt, etc. and the calculations the sample contains depend on that element. Each type of Moodle entity used as a sample helps develop the predictions that involve that kind of entity. For example, samples based on Quiz attempts will help develop the potential insights that the analytics might offer that are related to the Quiz attempts by a particular group of students. See the [Analyser](#analyser) documentation for more information on how to use analyser classes to define what is a sample.
+
+### Prediction model
+
+As explained above, a prediction model is a combination of indicators and a target. System models can be viewed in **Site administration > Analytics > Analytics models**.
+
+The relationship between indicators and targets is stored in *analytics_models* database table.
+
+The class `\core_analytics\model` manages all of a model's related actions. *evaluate()*, *train()* and *predict()* forward the calculated indicators to the machine learning backends. `\core_analytics\model` delegates all heavy processing to analysers and machine learning backends. It also manages prediction models evaluation logs.
+
+`\core_analytics\model` class is not expected to be extended.
+
+#### Static models
+
+Some prediction models do not need a powerful machine learning algorithm behind them processing large quantities of data to make accurate predictions. There are obvious events that different stakeholders may be interested in knowing that we can easily calculate. These *Static model* predictions are directly calculated based on indicator values. They are based on the assumptions defined in the target, but they should still be based on indicators so all these indicators can still be reused across different prediction models. For this reason, static models are not editable through **Site administration > Analytics > Analytics models** user interface.
+
+Some examples of possible static models:
+
+- [Courses without teaching activity](https://docs.moodle.org/en/Analytics#No_teaching)
+- Courses with students submissions requiring attention and no teachers accessing the course
+- Courses that started 1 month ago and never accessed by anyone
+- Students that have never logged into the system
+
+Moodle can already generate notifications for the examples above, but there are some benefits on doing it using the Moodle analytics API:
+
+- Everything is easier and faster to code from a development point of view as the analytics subsystem provides APIs for everything
+- New Indicators will be part of the core indicators pool that researchers (and 3rd party developers in general) can reuse in their own models
+- Existing core indicators can be reused as well (the same indicators used for insights that depend on machine learning backends)
+- Notifications are displayed using the core insights system, which is also responsible of sending the notifications and all related actions.
+- The Analytics API tracks user actions after viewing the predictions, so we can know if insights result in actions, which insights are not useful, etc. User responses to insights could themselves be defined as an indicator.
+
+### Analyser
+
+Analysers are responsible for creating the dataset files that will be sent to the machine learning processors. They are coded as PHP classes. Moodle core includes some analysers that you can use in your models.
+
+The base class `\core_analytics\local\analyser\base` does most of the work. It contains a key abstract method, *get_all_samples()*. This method is what defines the sample unique identifier across the site. Analyser classes are also responsible of including all site data related to that sample id; this data will be used when indicators are calculated. e.g. A sample id *user enrolment* would include data about the *course*, the course *context* and the *user*. Samples are nothing by themselves, just a list of ids with related data. They are used in calculations once they are combined with the target and the indicator classes.
+
+Other analyser class responsibilities:
+
+- Define the context of the predictions
+- Discard invalid data
+- Filter out already trained samples
+- Include the time factor (time range processors, explained below)
+- Forward calculations to indicators and target classes
+- Record all calculations in a file
+- Record all analysed sample ids in the database
+
+If you are introducing a new analyser, there is an important non-obvious fact you should know about: for scalability reasons, all calculations at course level are executed in per-course basis and the resulting datasets are merged together once all site courses analysis is complete. This is for performance reasons: depending on the sites' size it could take hours to complete the analysis of the entire site. This is a good way to break the process up into pieces. When coding a new analyser you need to decide if you want to extend `\core_analytics\local\analyser\by_course` (your analyser will process a list of courses), `\core_analytics\local\analyser\site-wide` (your analyser will receive just one analysable element, the site) or create your own analyser for activities, categories or any other Moodle entity.
+
+### Target
+
+Targets are the key element that defines the model. As a PHP class, targets represent the event the model is attempting to predict (the [dependent variable in supervised learning](https://en.wikipedia.org/wiki/Dependent_and_independent_variables)). They also define the actions to perform depending on the received predictions.
+
+Targets depend on analysers, because analysers provide them with the samples they need. Analysers are separate entities from targets because analysers can be reused across different targets. Each target needs to specify which analyser it is using. Here are a few examples to clarify the difference between analysers, samples and targets:
+
+- **Target**: 'students at risk of dropping out'. **Analyser provides sample**: 'course enrolments'
+- **Target**: 'spammer'. **Analyser provides sample**: 'site users'
+- **Target**: 'ineffective course'. **Analyser provides sample**: 'courses'
+- **Target**: 'difficulties to pass a specific quiz'. **Analyser provides sample**: 'quiz attempts in a specific quiz'
+
+A callback defined by the target will be executed once new predictions start coming so each target have control over the prediction results.
+
+The API supports binary classification, multi-class classification and regression, but the machine learning backends included in core do not yet support multi-class classification or regression, so only binary classifications will be initially fully supported. See [MDL-59044](https://tracker.moodle.org/browse/MDL-59044) and [MDL-60523](https://tracker.moodle.org/browse/MDL-60523) for more information.
+
+Although there is no technical restriction against using core targets in your own models, in most cases each model will implement a new target. One possible case in which targets might be reused would be to create a new model using the same target and a different sets of indicators, for A/B testing
+
+#### Insights
+
+Another aspect controlled by targets is insight generation. Insights represent predictions made about a specific element of the sample within the context of the analyser model. This context will be used to notify users with the `moodle/analytics:listinsights` capability (the teacher role by default) about new insights being available. These users will receive a notification with a link to the predictions page where all predictions of that context are listed.
+
+A set of suggested actions will be available for each prediction. In cases like *[Students at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out)* the actions can be things like sending a message to the student, viewing the student's course activity report, etc.
+
+### Indicator
+
+Indicator PHP classes are responsible for calculating indicators (predictor value or [independent variable in supervised learning](https://en.wikipedia.org/wiki/Dependent_and_independent_variables)) using the provided sample. Moodle core includes a set of indicators that can be used in your models without additional PHP coding (unless you want to extend their functionality).
+
+Indicators are not limited to a single analyser like targets are. This makes indicators easier to reuse in different models. Indicators specify a minimum set of data they need to perform the calculation. The indicator developer should also make an effort to imagine how the indicator will work when different analysers are used. For example an indicator named *Posts in any forum* could be initially coded for a *Shy students in a course* target; this target would use *course enrolments* analyser, so the indicator developer knows that a *course* and an *enrolment* will be provided by that analyser, but this indicator can be easily coded so the indicator can be reused by other analysers like *courses* or *users'. In this case the developer can chose to require *course* **or** *user*, and the name of the indicator would change according to that. For example, *User posts in any forum* could be used in a user-based model like *Inactive users* and in any other model where the analyser provides *user* data; *Posts in any of the course forums* could be used in a course-based model like *Low participation courses.''
+
+The calculated value can go from -1 (minimum) to 1 (maximum). This requirement prevents the creation of "raw number" indicators like *absolute number of write actions,* because we must limit the calculation to a range, e.g. -1 = 0 actions, -0.33 = some basic activity, 0.33 = activity, 1 = plenty of activity. Raw counts of an event like "posts to a forum" must be calculated in a proportion of an expected number of posts. There are several ways of doing this. One is to define a minimum desired number of events, e.g. 3 posts in a forum represents "some" activity, 6 posts represents adequate activity, and 10 or more posts represents the maximum expected activity. Another way is to compare the number of events per individual user to the mean or median value of events by all users in the same context, using statistical values. For example, a value of 0 would represent that the student posted the same number of posts as the mean of all student posts in that context; a value of -1 would indicate that the student is 2 or 3 standard deviations below the mean, and a +1 would indicate that the student is 2 or 3 standard deviations above the mean.
+
+:::danger Comparative rankings
+
+This kind of comparative calculation has implications to pedagogy: it suggests that there is a ranking of students from best to worst, rather than a defined standard all students can reach. Please be aware of this when considering how indicators are presented to users.
+
+:::
+
+### Analysis intervals
+
+Analysis intervals define when the system will calculate predictions and the portion of activity logs that will be considered for those predictions. They are coded as PHP classes and Moodle core includes some analysis intervals you can use in your models.
+
+:::info Time-splitting methods
+
+Analysis intervals were previously known as Time-splitting methods.
+
+:::
+
+In some cases the time factor is not important and we just want to classify a sample. This is relatively simple. Things get more complicated when we want to predict what will happen in future. For example, predictions about [Students at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out) are not useful once the course is over or when it is too late for any intervention.
+
+Calculations involving time ranges can be a challenging aspect of some prediction models. Indicators need to be designed with this in mind and we need to include time-dependent indicators within the calculated indicators so machine learning algorithms are smart enough to avoid mixing calculations belonging to the beginning of the course with calculations belonging to the end of the course.
+
+There are many different ways to split up a course into time ranges: in weeks, quarters, 8 parts, ten parts (tenths), ranges with longer periods at the beginning and shorter periods at the end... And the ranges can be accumulative (each one inclusive from the beginning of the course) or only from the start of the time range.
+
+Many of the analysis intervals included in Moodle assume that there is a fixed start and end date for each course, so the course can be divided into segments of equal length. This allows courses of different lengths to be included in the same prediction model, but makes these analysis intervals useless for courses without fixed start or end dates, e.g. self-paced courses. These courses might instead use fixed time lengths such as weeks to define the boundaries of prediction calculations.
+
+### Machine learning backends
+
+Documentation available in [Machine learning backends](../../plugintypes/mlbackend/index.md).
+
+## Design
+
+The system is designed as a Moodle subsystem and API. It lives in `analytics/`. All analytics base classes are located here.
+
+[Machine learning backends](../../plugintypes/mlbackend/index.md) is a new Moodle plugin type. They are stored in `lib/mlbackend`.
+
+Uses of the analytics API are located in different Moodle components, being core (`lib/classes/analytics`) the component that hosts general purpose uses of the API.
+
+### Interfaces
+
+This API aims to be as extendable as possible. Any moodle component, including third party plugins, is able to define indicators, targets, analysers and time splitting methods. Analytics API will be able to find them as long as they follow the namespace conventions described below.
+
+An example of a possible extension would be a plugin with indicators that fetch student academic records from the Universities' student information system; the site admin could build a new model on top of the built-in 'students at risk of drop out detection' adding the SIS indicators to improve the model accuracy or for research purposes.
+
+:::note Machine learning backends
+
+This section does not include Machine learning backend interfaces. See [Machine learning backends](../../plugintypes/mlbackend/index.md#interfaces) for more information on these.
+
+:::
+
+#### Analysable (core_analytics\analysable)
+
+Analysables are those elements in Moodle that contain samples. In most of the cases an analysable will be a course, although it can also be the site or any other Moodle element, e.g. an activity. Moodle core includes two analysers `\core_analytics\course` and `\core_analytics\site`.
+
+They list of methods that need to be implemented is quite simple and does not require much explanation.
+
+:::danger
+
+Analysable elements should be lazily loaded, otherwise you may encounter PHP memory issues. The reason is that analysers load all analysable elements in the site to calculate which ones are going to be calculated next (skipping the ones processed recently and stuff like that).
+
+See `core_analytics\course` as an example of how this can be achieved.
+
+:::
+
+Methods to implement:
+
+```php
+/**
+ * The analysable unique identifier in the site.
+ *
+ * @return int.
+ */
+public function get_id();
+
+/**
+ * The analysable human readable name
+ *
+ * @return string
+ */
+public function get_name();
+
+/**
+ * The analysable context.
+ *
+ * @return \context
+ */
+public function get_context();
+```
+
+`get_start` and `get_end` define the start and end times that indicators will use for their calculations.
+
+```php
+/**
+ * The start of the analysable if there is one.
+ *
+ * @return int|false
+ */
+public function get_start();
+
+/**
+ * The end of the analysable if there is one.
+ *
+ * @return int|false
+ */
+public function get_end();
+```
+
+#### Analyser (core_analytics\local\analyser\base)
+
+The get_analysables() method has been deprecated in favour of a new `get_analysables_iterator()` for performance reasons. This method returns the whole list of analysable elements in the site. Each model will later be able to discard analysables that do not match their expectations. *e.g. if your model is only interested in quizzes with a time close the analyser will return all quizzes, your model will exclude the ones without a time close. This approach is supposed to make analysers more reusable.*
+
+```php
+/**
+ * Returns the list of analysable elements available on the site.
+ *
+ * A relatively complex SQL query should be set so that we take into account which analysable elements
+ * have already been processed and the order in which they have been processed. Helper methods are available
+ * to ease to implementation of get_analysables_iterator: get_iterator_sql and order_sql.
+ *
+ * @param string|null $action 'prediction', 'training' or null if no specific action needed.
+ * @return \Iterator
+ */
+public function get_analysables_iterator(?string $action = null)
+```
+
+`get_all_samples` and `get_samples` should return data associated with the sample ids they provide. This is important for 2 reasons:
+
+- The data they provide alongside the sample origin is used to filter out indicators that are not related to what this analyser analyses. ''e.g. courses analysers do provide courses and information about courses, but not information about users, a **is user profile complete** indicator will require the user object to be available. A model using a courses analyser will not be able to use the **is user profile complete** indicator.
+- The data included here is cached in PHP static vars; on one hand this reduces the amount of db queries indicators need to perform. On the other hand, if not well balanced, it can lead to PHP memory issues.
+
+```php
+/**
+ * This function returns this analysable list of samples.
+ *
+ * @param \core_analytics\analysable $analysable
+ * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata)
+ */
+abstract public function get_all_samples(\core_analytics\analysable $analysable);
+```
+
+```php
+/**
+ * This function returns the samples data from a list of sample ids.
+ *
+ * @param int[] $sampleids
+ * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata)
+ */
+abstract public function get_samples($sampleids);
+```
+
+`get_sample_analysable` method is executing during prediction:
+
+```php
+/**
+ * Returns the analysable of a sample.
+ *
+ * @param int $sampleid
+ * @return \core_analytics\analysable
+ */
+abstract public function get_sample_analysable($sampleid);
+```
+
+The sample origin is the moodle database table that uses the sample id as primary key.
+
+```php
+/**
+ * Returns the sample's origin in moodle database.
+ *
+ * @return string
+ */
+abstract public function get_samples_origin();
+```
+
+`sample_access_context` associates a context to a `sampleid`. This is important because this sample predictions will only be available for users with `moodle/analytics:listinsights` capability in that context.
+
+```php
+/**
+ * Returns the context of a sample.
+ *
+ * @param int $sampleid
+ * @return \context
+ */
+abstract public function sample_access_context($sampleid);
+```
+
+`sample_description` is used to display samples in *Insights* report:
+
+```php
+/**
+ * Describes a sample with a description summary and a \renderable (an image for example)
+ *
+ * @param int $sampleid
+ * @param int $contextid
+ * @param array $sampledata
+ * @return array array(string, \renderable)
+ */
+abstract public function sample_description($sampleid, $contextid, $sampledata);
+```
+
+`processes_user_data` and `join_sample_user` methods are used by the analytics implementation of the privacy API. You only need to overwrite them if your analyser deals with user data. They are used to export and delete user data that is stored in analytics database tables:
+
+```php
+/**
+ * Whether the plugin needs user data clearing or not.
+ *
+ * @return bool
+ */
+public function processes_user_data();
+```
+
+```php
+/**
+ * SQL JOIN from a sample to users table.
+ *
+ * More info in [https://github.com/moodle/moodle/blob/main/analytics/classes/local/analyser/base.php core_analytics\local\analyser\base]::join_sample_user
+ *
+ * @param string $sampletablealias The alias of the table with a sampleid field that will join with this SQL string
+ * @return string
+ */
+public function join_sample_user($sampletablealias);
+```
+
+You can overwrite a `new one_sample_per_analysable()` method if the analysables your model is using only have one sample. The insights generated by models will then include the suggested actions in the notification.
+
+```php
+/**
+ * Just one sample per analysable.
+ *
+ * @return bool
+ */
+public static function one_sample_per_analysable() {
+ return true;
+}
+```
+
+#### Indicator (core_analytics\local\indicator\base)
+
+Indicators should generally extend one of these 3 classes, depending on the values they can return: *core_analytics\local\indicator\binary* for **yes/no** indicators, *core_analytics\local\indicator\linear* for indicators that return linear values and *core_analytics\local\indicator\discrete* for categorised indicators. In case you want your activity module to implement a [community of inquiry](https://docs.moodle.org/en/Students_at_risk_of_dropping_out#Indicators) indicator you can extend *core_analytics\local\indicator\community_of_inquiry_indicator* look for examples in Moodle core.
+
+You can use `required_sample_data` to specify what your indicator needs to be calculated; you may need a *user* object, a *course*, a *grade item*... The default implementation does not require anything. Models which analysers do not return the required data will not be able to use your indicator so only list here what you really need. e.g. if you need a grade_grades record mark it as required, but there is no need to require the *user* object and the *course* as well because you can obtain them from the grade_grades item. It is very likely that the analyser will provide them as well because the principle they follow is to include as much related data as possible but do not flag related objects as required because an analyser may, for example, chose to not include the *user* object because it is too big and sites can have memory problems.
+
+```php
+/**
+ * Allows indicators to specify data they need.
+ *
+ * e.g. A model using courses as samples will not provide users data, but an indicator like
+ * "user is hungry" needs user data.
+ *
+ * @return null|string[] Name of the required elements (use the database tablename)
+ */
+public static function required_sample_data() {
+ return null;
+}
+```
+
+A single method must be implemented, `calculate_sample`. Most indicators make use of `$starttime` and `$endtime` to restrict the time period they consider for their calculations (for example read actions during `$starttime - $endtime` period) but some indicators may not need to apply any restriction (e.g. does this user have a user picture and profile description?) `self::MIN_VALUE` is `-1` and `self::MAX_VALUE` is `1`. We do not recommend changing these values.
+
+```php
+/**
+ * Calculates the sample.
+ *
+ * Return a value from self::MIN_VALUE to self::MAX_VALUE or null if the indicator can not be calculated for this sample.
+ *
+ * @param int $sampleid
+ * @param string $sampleorigin
+ * @param integer $starttime Limit the calculation to this timestart
+ * @param integer $endtime Limit the calculation to this timeend
+ * @return float|null
+ */
+abstract protected function calculate_sample($sampleid, $sampleorigin, $starttime, $endtime);
+```
+
+:::danger Performance
+
+Performance here is critical as it runs once for each sample and for each range in the time-splitting method; some tips:
+
+- To avoid performance issues or repeated db queries analyser classes provide information about the samples that you can use for your calculations to save some database queries. You can retrieve information about a sample with `$user = $this->retrieve('user', $sampleid)`. *retrieve()* will return false if the requested data is not available.
+- You can also overwrite *fill_per_analysable_caches* method if necessary (keep in mind though that PHP memory is not unlimited).
+- Indicator instances are reset for each analysable and time range that is processed. This helps keeping the memory usage acceptably low and prevents hard-to-trace caching bugs.
+
+:::
+
+#### Target (core_analytics\local\target\base)
+
+Targets must extend `\core_analytics\local\target\base` or its main child class `\core_analytics\local\target\binary`. Even if Moodle core includes `\core_analytics\local\target\discrete` and `\core_analytics\local\target\linear` Moodle 3.4 machine learning backends only support binary classifications. So unless you are using your own machine learning backend you need to extend `\core_analytics\local\target\binary`. Technically targets could be reused between models although it is not very recommendable and you should focus instead in having a single model with a single set of indicators that work together towards predicting accurately. The only valid use case I can think of for models in production is using different time-splitting methods for it although, again, the proper way to solve this is by using a single time-splitting method specific for your needs.
+
+The first thing a target must define is the analyser class that it will use. The analyser class is specified in `get_analyser_class`.
+
+```php
+/**
+ * Returns the analyser class that should be used along with this target.
+ *
+ * @return string The full class name as a string
+ */
+abstract public function get_analyser_class();
+```
+
+`is_valid_analysable` and `is_valid_sample` are used to discard elements that are not valid for your target.
+
+```php
+/**
+ * Allows the target to verify that the analysable is a good candidate.
+ *
+ * This method can be used as a quick way to discard invalid analysables.
+ * e.g. Imagine that your analysable don't have students and you need them.
+ *
+ * @param \core_analytics\analysable $analysable
+ * @param bool $fortraining
+ * @return true|string
+ */
+public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true);
+```
+
+```php
+/**
+ * Is this sample from the $analysable valid?
+ *
+ * @param int $sampleid
+ * @param \core_analytics\analysable $analysable
+ * @param bool $fortraining
+ * @return bool
+ */
+public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true);
+```
+
+`calculate_sample` is the method that calculates the target value.
+
+```php
+/**
+ * Calculates this target for the provided samples.
+ *
+ * In case there are no values to return or the provided sample is not applicable just return null.
+ *
+ * @param int $sampleid
+ * @param \core_analytics\analysable $analysable
+ * @param int|false $starttime Limit calculations to start time
+ * @param int|false $endtime Limit calculations to end time
+ * @return float|null
+ */
+protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false);
+```
+
+You may wish to add to the list of **actions** that will be offered to the recipient of an insight:
+
+```php
+/**
+ * Suggested actions for a user.
+ *
+ * @param \core_analytics\prediction $prediction
+ * @param bool $includedetailsaction
+ * @param bool $isinsightuser
+ * @return \core_analytics\prediction_action[]
+ */
+public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false,
+ $isinsightuser = false)
+```
+
+You may override the users who will receive **insights notifications**:
+
+```php
+/**
+ * Returns the list of users that will receive insights notifications.
+ *
+ * Feel free to overwrite if you need to but keep in mind that moodle/analytics:listinsights
+ * or moodle/analytics:listowninsights capability is required to access the list of insights.
+ *
+ * @param \context $context
+ * @return array
+ */
+public function get_insights_users(\context $context)
+```
+
+You can implement a `always_update_analysis_time()` method so analysable elements' `timeanalysed` is only updated when analysable elements have been successfully evaluated. It is useful for lightweight targets.
+
+```php
+/**
+ * Update the last analysis time on analysable processed or always.
+ *
+ * If you overwrite this method to return false the last analysis time
+ * will only be recorded in DB when the element successfully analysed. You can
+ * safely return false for lightweight targets.
+ *
+ * @return bool
+ */
+public function always_update_analysis_time()
+```
+
+If you choose to implement `ignored_predicted_classes`, it must be public:
+
+```php
+
+/**
+ * Returns the predicted classes that will be ignored.
+ *
+ * @return array
+ */
+public function ignored_predicted_classes()
+```
+
+You can override the default message provided in the insight with `get_insight_subject()`:
+
+```php
+/**
+ * The insight notification subject.
+ *
+ * This is just a default message, you should overwrite it for a custom insight message.
+ *
+ * @param int $modelid
+ * @param \context $context
+ * @return string
+ */
+public function get_insight_subject(int $modelid, \context $context)
+```
+
+You can also override the URL to the insight with get_insight_context_url():
+
+```php
+/**
+ * URL to the insight.
+ *
+ * @param int $modelid
+ * @param \context $context
+ * @return \moodle_url
+ */
+public function get_insight_context_url($modelid, $context)
+```
+
+#### Analysis interval (core_analytics\local\time_splitting\base)
+
+Analysis intervals are used to define when the analytics API will train the predictions processor and when it will generate predictions. As explained above in the [Analysis intervals](./index.md#analysis-intervals) documentation, they define time ranges based on analysable elements start and end timestamps.
+
+The base class is `\core_analytics\local\time_splitting\base`; if what you are after is to split the analysable duration in equal parts or in cumulative parts you can extend `\core_analytics\local\time_splitting\equal_parts` or `\core_analytics\local\time_splitting\accumulative_parts` instead.
+
+`define_ranges` is the main method to implement and its values mostly depend on the current analysable element (available in `$this->analysable`). An array of time ranges should be returned, each of these ranges should contain 3 attributes: A start time ('start') and an end time ('end') that will be passed to indicators so they can limit the amount of activity logs they read; the 3rd attribute is 'time', which value will determine when the range will be executed.
+
+```php
+/**
+ * Define the time splitting methods ranges.
+ *
+ * 'time' value defines when predictions are executed, their values will be compared with
+ * the current time in ready_to_predict
+ *
+ * @return array('start' => time(), 'end' => time(), 'time' => time())
+ */
+protected function define_ranges();
+```
+
+A name and description should also be specified:
+
+```php
+/**
+ * Returns a lang_string object representing the name for the time splitting method.
+ *
+ * Used as column identificator.
+ *
+ * If there is a corresponding '_help' string this will be shown as well.
+ *
+ * @return \lang_string
+ */
+public static function get_name() : \lang_string;
+```
+
+
+
+Analysis intervals can now override the following:
+
+`valid_for_evaluation()`.
+You can return false if the time-splitting method can not be used to evaluate prediction models or if it does not make sense to evaluate prediction models with it, as for example upcoming_periodic children classes.
+
+`include_range_info_in_training_data()` and `get_training_ranges()`.
+They can be used to create time splitting methods with a pre-defined number of ranges.
+
+The following cannot be overwritten:
+`\core_analytics\local\time_splitting\base::append_rangeindex` and `\core_analytics\local\time_splitting\base::infer_sample_info` `\core_analytics\local\analyser\base::get_most_recent_prediction_range` have been moved to `\core_analytics\local\time_splitting\base::get_most_recent_prediction_range` and it is not overridable by time splitting methods.
+
+#### Calculable (core_analytics\calculable)
+
+Leaving this interface for the end because it is already implemented by `\core_analytics\local\indicator\base` and `\core_analytics\local\target\base` but you can still code targets or indicators from the `\core_analytics\calculable` base if you need more control.
+
+Both indicators and targets must implement this interface. It defines the data element to be used in calculations, whether as independent (indicator) or dependent (target) variables.
+
+## How to create a model
+
+New models can be created and implemented in php, and can be packaged as a part of a Moodle plugin for distribution. An example of model components and models are provided at https://github.com/dmonllao/moodle-local_testanalytics.
+
+:::info Third-party plugin developers
+
+Third party plugin developers can add their new elements (for example [targets](https://docs.moodle.org/en/Learning_analytics_targets), [indicators](https://docs.moodle.org/en/Learning_analytics_indicators), and so on) to the documentation pages provided for those components. These documentation pages are linked from the web UI for editing models.
+
+:::
+
+### Define the problem
+
+Start by defining what you want to predict (the target) and the subjects of these predictions (the samples). You can find the descriptions of these concepts above. The API can be used for all kinds of models, though if you want to predict something like "student success," this definition should probably have some basis in pedagogy. For example, the included model [Students at risk of dropping out](https://docs.moodle.org/34/en/Students_at_risk_of_dropping_out) is based on the Community of Inquiry theoretical framework, and attempts to predict that students will complete a course based on indicators designed to represent the three components of the CoI framework (teaching presence, social presence, and cognitive presence). Start by being clear about how the target will be defined. It must be trained using known examples. This means that if, for example, you want to predict the final grade of a course per student, the courses being used to train the model must include accurate final grades.
+
+### How many predictions for each sample?
+
+The next decision should be how many predictions you want to get for each sample (e.g. just one prediction before the course starts or a prediction every week). A single prediction for each sample is simpler than multiple predictions at different points in time in terms of how deep into the API you will need to go to code it.
+
+These are not absolute statements, but in general:
+
+- If you want a single prediction for each sample at a specific point in time you can reuse a site-wide analyser or define your own and control samples validity through your target's *is_valid_sample()* method.
+- If you want multiple predictions at different points in time for each sample reuse an analysable element or define your own (extending `\core_analytics\analysable`) and reuse or define your own analyser to retrieve these analysable elements. You can control analysers validity through your target's *is_valid_analysable()* method.
+- If you want predictions at activity level use a "by_course" analyser as otherwise you may have scalability problems (imagine storing in memory calculations for each grade_grades record in your site, though processing elements by courses help as we clean memory after each course is processed)
+
+This decision is important because:
+
+- Analysis intervals are applied to analysable time start and time end (e.g. **Quarters** can split the duration of a course in 4 parts).
+- Prediction results are grouped by analysable in the admin interface to list predictions.
+- By default, insights are notified to users with `moodle/analytics:listinsights` capability at analysable level (though this is only a default behaviour you can overwrite in your target).
+
+:::note
+
+Several of the existing analysis intervals are proportional to the length of the course (for example quarters, tenths, and so on). This allows courses with different lengths to be included in the same sample, but requires courses to have defined start and end dates.
+
+Other analysis intervals such as "Upcoming 3 days" are also available, which do not depend on the defined length of the course. These would be more appropriate for self-paced courses without fixed start and end dates.
+
+:::
+
+You do not need to require a single analysis interval at this stage, and they can be changed whenever the model is trained. You do need to define whether the model will make a single prediction or multiple predictions per analysable.
+
+### Create the target
+
+As specified in the [Target](./index.md#target-core_analyticslocaltargetbase) section.
+
+### Create the model
+
+To add a new model to the system, it must be defined in a PHP file. Plugins and core subsystems declare default prediction models by describing them in their db/analytics.php file. Models should not be created manually via the db/install.php file any more. (It is also possible to execute the necessary commands in a standalone PHP file that references the Moodle config.php.)
+
+To create the model, specify at least its target and, optionally, a set of indicators and a time splitting method:
+
+```php
+// Instantiate the target: classify users as spammers
+$target = \core_analytics\manager::get_target('\mod_yours\analytics\target\spammer_users');
+
+// Instantiate indicators: two different indicators that predict that the user is a spammer
+$indicator1 = \core_analytics\manager::get_indicator(
+ '\mod_yours\analytics\indicator\posts_straight_after_new_account_created'
+);
+$indicator2 = \core_analytics\manager::get_indicator(
+ '\mod_yours\analytics\indicator\posts_contain_important_viagra'
+);
+$indicators = [
+ $indicator1->get_id() => $indicator1,
+ $indicator2->get_id() => $indicator2,
+];
+
+// Create the model.
+// Note that the 3rd and 4rd arguments (the time splitting method and the predictions processor)
+// are optional.
+// The 4th argument is available from Moodle 3.6 onwards.
+$model = \core_analytics\model::create(
+ $target,
+ $indicators,
+ '\core\analytics\time_splitting\single_range',
+ '\mlbackend_python\processor'
+);
+```
+
+Models are disabled by default because you may be interested in evaluating how good the model is at predicting before enabling them. You can enable models using Moodle UI or the analytics API:
+
+```php
+$model->enable();
+```
+
+### Indicators
+
+You already know the analyser your target needs (the analyser is what provides samples) and, more or less, what time splitting method may be better for you. You can now select a list of indicators that you think will lead to accurate predictions. You may need to create your own indicators specific to the target you want to predict.
+
+You can use "'Site administration > Analytics > Analytics models"' to see the list of available indicators and add some of them to your model.
+
+## API usage examples
+
+This is a list of prediction models and how they could be coded using the Analytics API:
+
+[Students at risk of dropping out](https://docs.moodle.org/34/en/Students_at_risk_of_dropping_out) (based on student's activity, included in [Moodle 3.4](https://docs.moodle.org/34/en/Analytics))
+
+- **Time splitting:** quarters, quarters accumulative, deciles, deciles accumulative...
+- **Analyser samples:** student enrolments (analysable elements are courses)
+- `target::is_valid_analysable`
+ - For prediction = ongoing courses
+ - For training = finished courses with activity
+- `target::is_valid_sample` = true
+- **Based on assumptions (static)**: no, predictions should be based on finished courses data
+
+Not engaging course contents (based on the course contents)
+
+- **Time splitting:** single range
+- **Analyser samples:** courses (the analysable elements is the site)
+- `target::is_valid_analysable` = true
+- `target::is_valid_sample`
+ - For prediction = the course is close to the start date
+ - For training = no training
+- **Based on assumptions (static)**: yes, a simple look at the course activities should be enough (e.g. are the course activities engaging?)
+
+No teaching (courses close to the start date without a teacher assigned, included in Moodle 3.4)
+
+- **Time splitting:** single range
+- **Analyser samples:** courses (the analysable elements is the site) it would also work using course as analysable
+- `target::is_valid_analysable` = true
+- `target::is_valid_sample`
+ - For prediction = course close to the start date
+ - For training = no training
+- **Based on assumptions (static)**: yes, just check if there are teachers
+
+Late assignment submissions (based on student's activity)
+
+- **Time splitting:** close to the analysable end date (1 week before, X days before...)
+- **Analyser samples:** assignment submissions (analysable elements are activities)
+- `target::is_valid_analysable`
+ - For prediction = the assignment is open for submissions
+ - For training = past assignment due date
+- `target::is_valid_sample` = true
+- **Based on assumptions (static)**: no, predictions should be based on previous students activity
+
+Spam users (based on suspicious activity)
+
+- **Time splitting:** 2 parts, one after 4h since user creation and another one after 2 days (just an example)
+- **Analyser samples:** users (analysable elements are users)
+- `target::is_valid_analysable` = true
+- `target::is_valid_sample`
+ - For prediction = 4h or 2 days passed since the user was created
+ - For training = 2 days passed since the user was created (spammer flag somewhere recorded to calculate target = 1, otherwise no spammer)
+- **Based on assumptions (static)**: no, predictions should be based on users activity logs, although this could also be done as a static model
+
+Students having a bad time
+
+- **Time splitting:** quarters accumulative or deciles accumulative
+- **Analyser samples:** student enrolments (analysable elements are courses)
+- `target::is_valid_analysable`
+ - For prediction = ongoing and course activity
+ - For training = finished courses
+- `target::is_valid_sample` = true
+- **Based on assumptions (static)**: no, ideally it should be based on previous cases
+
+Course not engaging for students (checking student's activity)
+
+- **Time splitting:** quarters, quarters accumulative, deciles...
+- **Analyser samples:** course (analysable elements are courses)
+- `target::is_valid_analysable` = true
+- `target::is_valid_sample`
+ - For prediction = ongoing course
+ - For training = finished courses
+- **Based on assumptions (static)**: no, it should be based on activity logs
+
+Student will fail a quiz (based on things like other students quiz attempts, the student in other activities, number of attempts to pass the quiz...)
+
+- **Time splitting:** single range
+- **Analyser samples:** student grade on an activity, aka grade_grades id (analysable elements are courses)
+- `target::is_valid_analysable`
+ - For prediction = ongoing course with quizzes
+ - For training = ongoing course with quizzes
+- `target::is_valid_sample`
+ - For prediction = more than the X% of the course students attempted the quiz and the sample (a student) has not attempted it yet
+ - For training = the student has attempted the quiz at least X times (X because if after X attempts has not passed counts as failed) or the student passed the quiz
+- **Based on assumptions (static)**: no, it is based on previous students records
+
+## Clustered environments
+
+The `analytics/outputdir` setting can be configured by Moodle sites with multiple frontend nodes to specify a directory shared across nodes. This directory can be used by machine learning backends to store trained algorithms (for example, its internal variables weights) to use them later to get predictions.
+
+The Moodle cron lock will prevent multiple executions of the analytics tasks that train machine learning algorithms and get predictions from them.
diff --git a/versioned_docs/version-4.5/apis/subsystems/availability/index.md b/versioned_docs/version-4.5/apis/subsystems/availability/index.md
new file mode 100644
index 0000000000..edd9d191ea
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/availability/index.md
@@ -0,0 +1,164 @@
+---
+title: Availability API
+tags:
+ - Availability
+ - core_availability
+---
+
+The availability API controls access to activities and sections. For example, a teacher could restrict access so that an activity cannot be accessed until a certain date, or so that a section cannot be accessed unless users have a certain grade in a quiz.
+
+:::note
+
+In older versions of Moodle, the conditional availability system defaulted to off; and users could enable it from the advanced features page in site administration. It is enabled by default for new installs of Moodle since Moodle 3.1, but sites which have been upgraded may still have to manually enable it.
+
+You can still call the API functions even if the system is turned off.
+
+:::
+
+## Using the API
+
+In most cases you do not need to use the API directly because the course and activity API already handles it for you. For example, if you are writing a module:
+
+- Moodle will automatically prevent users from accessing your module if they do not meet the availability conditions (unless they have the ability to access hidden activities). This is calculated when you call `require_login` and pass your activity's information.
+- Your activity's form will automatically include controls for setting availability restriction, as part of the standard form controls.
+
+There are two special cases in which you may need to use the API directly.
+
+- Checking whether the current user can access an activity
+- Displaying a list of users who may be able to access the current activity
+
+### Check access for a user
+
+#### Activities
+
+Some availability information can be accessed from an instance of the `cm_info` class, specifically in the `uservisible` property.
+
+This property considers a range of factors to indicate whether the activity should be visible to that user, including:
+
+- whether the activity was hidden by a teacher
+- whether the activity is available based on the availability API
+
+The `cm_info` object also includes an `availableinfo` property which provides HTML-formatted information to explain to the user why they cannot access the activity.
+
+```php title="Checking and displaying availability information"
+$modinfo = get_fast_modinfo($course);
+$cm = $modinfo->get_cm($cmid);
+if ($cm->uservisible) {
+ // User can access the activity.
+} else if ($cm->availableinfo) {
+ // User cannot access the activity, but on the course page they will
+ // see a link to it, greyed-out, with information (HTML format) from
+ // $cm->availableinfo about why they can't access it.
+} else {
+ // User cannot access the activity and they will not see it at all.
+}
+```
+
+#### Course sections
+
+Some availability information can be accessed from an instance of the `section_info` class, specifically in the `uservisible` property.
+
+This property considers a range of factors to indicate whether the activity should be visible to that user, including:
+
+- whether the activity was hidden by a teacher
+- whether the activity is available based on the availability API
+
+The `section_info` object also includes an `availableinfo` property which provides HTML-formatted information to explain to the user why they cannot access the activity.
+
+:::warning Checking sections and activities
+
+The `uservisible` check for an _activity_ automatically includes all relevant checks for the section that the activity is placed in.
+
+You **do not** need to check visibility and availability for _both_ the section and the activity.
+
+:::
+
+#### Accessing information for a different user
+
+The availability information in both the `cm_info` and `section_info` classes is calculated for the current user. You can also obtain them for a different user by passing a user ID to `get_fast_modinfo`, although be aware that doing this repeatedly for different users will be slow.
+
+### Display a list of users who may be able to access the current activity
+
+Sometimes you need to display a list of users who may be able to access the current activity.
+
+While you could use the above approach for each user, this would be slow and also is generally not what you require. For example, if you have an activity such as an assignment which is set to be available to students until a certain date, and if you want to display a list of potential users within that activity, you probably don't want to make the list blank immediately the date occurs.
+
+The system divides availability conditions into two types:
+
+- Applied to user lists, including:
+ - User group
+ - User grouping
+ - User profile conditions
+- Not applied to user lists, including:
+ - The current date
+ - completion
+ - grade
+
+In general, the conditions which we expect are likely to change over time (such as dates) or as a result of user actions (such as grades) are not applied to user lists.
+
+If you have a list of users (for example you could obtain this using one of the 'get enrolled users' functions), you can filter it to include only those users who are allowed to see an activity with this code:
+
+```php
+$info = new \core_availability\info_module($cm);
+$filtered = $info->filter_user_list($users);
+```
+
+:::note
+
+The above example does not include the `$cm->visible` setting, nor does it take into account the `viewhiddenactivities` setting.
+
+:::
+
+## Using availability conditions in other areas
+
+The availability API is provided for activities (course-modules) and sections. It is also possible to use it in other areas such as _within_ a module. See [Availability API for items within a module](https://docs.moodle.org/dev/Availability_API_for_items_within_a_module).
+
+## Programmatically setting availability conditions
+
+In some situations you may need to _programmatically_ configure the availability conditions for an activity - for example you may have a custom enrolment plugin which creates assessable activities according to a student information system.
+
+To configure the availability, you can generate a JSON structure using an instance of the `core_availability\tree` class, and setting it against the activity or section record in the database, for example:
+
+```php
+$restriction = \core_availability\tree::get_root_json([
+ \availability_group\condition::get_json($group->id),
+]);
+$DB->set_field(
+ 'course_modules',
+ 'availability',
+ json_encode($restriction),
+ [
+ 'id' => $cmid,
+ ]
+);
+rebuild_course_cache($course->id, true);
+```
+
+The following code can be used to programmatically set start and end date restrictions.
+
+```php
+use \core_availability\tree;
+
+$dates = [];
+$dates[] = \availability_date\condition::get_json(">=", $availability['start']);
+$dates[] = \availability_date\condition::get_json("<", $availability['end']);
+
+$showc = [true, true];
+$restrictions = tree::get_root_json($dates, tree::OP_AND, $showc);
+
+$DB->set_field(
+ 'course_modules',
+ 'availability',
+ json_encode($restrictions),
+ [
+ 'id' => $cmid,
+ ]
+);
+rebuild_course_cache($course->id, true);
+```
+
+The ```$showc``` array determines if the course modules will be shown or invisible when not available.
+
+## See also
+
+- Writing [Availability condition](../../plugintypes/availability/index.md) plugins
diff --git a/versioned_docs/version-4.5/apis/subsystems/backup/index.md b/versioned_docs/version-4.5/apis/subsystems/backup/index.md
new file mode 100644
index 0000000000..dfdddc3a47
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/backup/index.md
@@ -0,0 +1,119 @@
+---
+title: Backup API
+tags:
+ - Subsystem
+ - API
+ - Backup
+---
+The Backup API provides a way to include your plugin's in the course backup. See [Restore API](./restore.md) for the part that takes care of restoring data.
+
+## Overview
+
+This page provides just a quick reference to the backup machinery in Moodle 2.0 and higher. There is a detailed documentation available at [Backup 2.0](https://docs.moodle.org/dev/Backup_2.0) page - see especially the tutorial for plugin authors at [Backup 2.0 for developers](https://docs.moodle.org/dev/Backup_2.0_for_developers) page.
+
+Moodle creates backups of courses or their parts by executing so called *backup plan*. The backup plan consists of a set of *backup tasks* and finally each backup task consists of one or more *backup steps*. You as the developer of a plugin will have to implement one backup task that deals with your plugin data. Most plugins have their backup tasks consisting of a single backup step - the one that creates the XML file with data from your plugin's tables. Some activities may need two or more steps to finish their backup task though (for example the backup task of the Quiz module consists of three steps as it needs to write not just the Quiz setting itself but also the questions used by that particular quiz).
+
+There are subtle differences in how the backup code is organised in various supported plugin types (activity modules, blocks, themes, course reports).
+
+## API for activity modules
+
+### Files
+
+The files that implement the backup support in your activity module must be located in the subdirectory backup/moodle2/ in your plugin's folder. So, if you are developing the activity module called *foobar* then the backup files will be located in mod/foobar/backup/moodle2/ folder.
+
+The following two files are supposed to exist in that location (replace *foobar* with the name of your module):
+
+- **backup_foobar_activity_task.class.php**
+Provides the activity task class
+- **backup_foobar_stepslib.php**
+Provides all the backup steps classes
+
+If your module declares its own backup setting (apart from the ones common for all activity modules provided by the core), you will also want to create the backup_foobar_settingslib.php file to provide the setting classes. However most modules do not need this feature.
+
+### Backup task class
+
+The file backup_foobar_activity_task.class.php must provide a single class called **backup_foobar_activity_task**. All activity tasks extend the backup_activity_task class.
+
+There are three methods that your class must define.
+
+- **protected function define_my_settings()**
+If your module declares own backup settings defined in the file backup_foobar_settingslib.php, add them here. Most modules just leave the method body empty.
+- **protected function define_my_steps()**
+This method typically consists of one or more `$this->add_step()` calls. This is the place where you define the task as a sequence of steps to execute.
+- **static public function encode_content_links($content)**
+The current instance of the activity may be referenced from other places in the course by URLs like `http://my.moodle.site/mod/foobar/view.php?id=42` Obviously, such URLs are not valid any more once the course is restored elsewhere. For this reason the backup file does not store the original URLs but encodes them into a transportable form. During the restore, the reverse process is applied and the encoded URLs are replaced with the new ones valid for the target site.
+
+### Backup structure step class
+
+The classes that represent the backup steps added in define_my_steps() are implemented in the file backup_foobar_stepslib.php. Most plugins define just a single step in the class called **backup_foobar_activity_structure_step** that extends the backup_activity_structure_step class. This class defines the structure step - that is the step where the structure of your plugin's instance data is described and linked with the data sources.
+
+You have to implement a single method `protected function define_structure()` in this class class. There are three main things that the method must do.
+
+- Create a set of backup_nested_element instances that describe the required data of your plugin
+- Connect these instances into a hierarchy using their `add_child()` method
+- Set data sources for the elements, using their methods like `set_source_table()` or `set_source_sql()`
+
+The method must return the root backup_nested_element instance processed by the `prepare_activity_structure()` method (which just wraps your structures with a common envelope).
+
+## API for blocks
+
+### Files
+
+The files that implement the backup support in your block must be located in the subdirectory backup/moodle2/ in your plugin's folder. So, if you are developing the block called *foobar* then the backup files will be located in blocks/foobar/backup/moodle2/ folder.
+
+At least the file backup_foobar_block_task.class.php is supposed to exist in that location (replace *foobar* with the name of your block).
+
+If your block defines its own database tables, data from which must be included in the backup, you will want to create a file backup_foobar_stepslib.php, too. Additionally, if your block declares its own backup setting, you will also want to create the backup_foobar_settingslib.php file to provide the setting classes. However most blocks do not need this feature.
+
+### Backup task class
+
+The file backup_foobar_block_task.class.php must provide a single class called **backup_foobar_block_task**. All block tasks extend the backup_block_task class.
+
+There are five methods that your class must define.
+
+- **protected function define_my_settings()**
+If your block declares own backup settings defined in the file backup_foobar_settingslib.php, add them here. Most blocks just leave the method body empty.
+- **protected function define_my_steps()**
+Blocks that do not have their own database tables usually leave this method empty. Otherwise this method consists of one or more `$this->add_step()` calls where you define the task as a sequence of steps to execute.
+- **public function get_fileareas()**
+Returns the array of file area names within the block context.
+- **public function get_configdata_encoded_attributes()**
+Instead of using their own tables, blocks usually use the configuration tables to hold their data (see the instance_config_save() of the block class). This method returns the array of all config elements that must be processed before they are stored in the backup. This is typically used when the stored config elements holds links to embedded media. Most blocks just return empty array here.
+- **static public function encode_content_links($content)**
+If the current instance of the block may be referenced from other places in the course by URLs, it must be encoded into a transportable form. Most blocks just return unmodified $content parameter.
+
+## API for admin tools
+
+The files that implement the backup support in your plugin must be located in the subdirectory *backup/moodle2/* in your plugin's folder. So, if you are developing *tool_foobar* then the backup files will be located in *admin/tool/foobar/backup/moodle2/*.
+
+### Task class
+
+The file backup_tool_foobar_plugin.class.php must provide a single class called *backup_tool_foobar_task* extending *backup_tool_plugin*.
+
+Here is a minimalistic task:
+
+```php
+require_once($CFG->dirroot . '/backup/moodle2/backup_tool_plugin.class.php');
+
+class backup_tool_foobar_plugin extends backup_tool_plugin {
+
+ protected function define_course_plugin_structure() {
+ $this->step->log('Yay, backup!', backup::LOG_DEBUG);
+ return $plugin;
+ }
+
+}
+```
+
+## API for themes
+
+See [Backup 2.0 theme data](https://docs.moodle.org/dev/Backup_2.0_theme_data)
+
+## API for reports
+
+See [Backup 2.0 course report data](https://docs.moodle.org/dev/Backup_2.0_course_report_data)
+
+## See also
+
+- [Restore API](./restore.md)
+- [Core APIs](../../../apis.md)
diff --git a/versioned_docs/version-4.5/apis/subsystems/backup/restore.md b/versioned_docs/version-4.5/apis/subsystems/backup/restore.md
new file mode 100644
index 0000000000..10539c31dc
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/backup/restore.md
@@ -0,0 +1,58 @@
+---
+title: Restore API
+tags:
+ - Subsystem
+ - API
+ - Backup
+---
+The Restore API provides a way to restore your plugin's data from a backup file created in Moodle 2.0 or later. For the information on how backup files are created, see [Backup API](./index.md). For the information on how to support restoring data from backup files created in Moodle 1.x, see [Backup conversion API](https://docs.moodle.org/dev/Backup_conversion_API).
+
+## Overview
+
+This page provides just a quick reference to the restore machinery in Moodle 2.0 and higher. There is a detailed documentation available at [Backup 2.0](https://docs.moodle.org/dev/Backup_2.0) page - see especially the tutorial for plugin authors at [Restore 2.0 for developers](https://docs.moodle.org/dev/Restore_2.0_for_developers) page.
+
+Moodle restores data from course backups by executing so called *restore plan*. The restore plan consists of a set of *restore tasks* and finally each restore task consists of one or more *restore steps*. You as the developer of a plugin will have to implement one restore task that deals with your plugin data. Most plugins have their restore tasks consisting of a single restore step - the one that parses the plugin XML file and puts the data into its tables.
+
+## API for activity modules
+
+### Files
+
+The files that implement the restore support in your activity module must be located in the subdirectory `backup/moodle2/` in your plugin's folder (yes, it's the same folder where the backup related files are located). So, if you are developing the activity module called *foobar* then the restore files will be located in `mod/foobar/backup/moodle2/` folder.
+
+The following two files are supposed to exist in that location (replace *foobar* with the name of your module):
+
+- **restore_foobar_activity_task.class.php**
+Provides the activity task class
+- **restore_foobar_stepslib.php**
+Provides all the restore steps classes
+
+(to be continued)
+
+## API for admin tools
+
+The files that implement the backup support in your plugin must be located in the subdirectory `backup/moodle2/` in your plugin's folder. So, if you are developing `tool_foobar` then the backup files will be located in `admin/tool/foobar/backup/moodle2/`.
+
+### Task class
+
+The file `backup_tool_foobar_plugin.class.php` must provide a single class called `restore_tool_foobar_task` extending `restore_tool_plugin`.
+
+Here is a minimalistic task:
+
+```php
+require_once($CFG->dirroot . '/backup/moodle2/restore_tool_plugin.class.php');
+
+class restore_tool_foobar_plugin extends restore_tool_plugin {
+
+ protected function define_course_plugin_structure() {
+ $paths = array();
+ $this->step->log('Yay, restore!', backup::LOG_DEBUG);
+ return $paths;
+ }
+
+}
+```
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [Backup API](../backup)
diff --git a/versioned_docs/version-4.5/apis/subsystems/check/index.md b/versioned_docs/version-4.5/apis/subsystems/check/index.md
new file mode 100644
index 0000000000..e0852d0854
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/check/index.md
@@ -0,0 +1,247 @@
+---
+title: Check API
+tags:
+ - API
+---
+
+A _Check_ is a runtime test to make sure that something is working well. You can think of Checks as similar and complimentary to the [PHPUnit](/general/development/tools/phpunit) and [Acceptance testing](/general/development/tools/behat) but the next layer around them, and performed at run time rather than development, or build time.
+
+Like other forms of testing the tests themselves should be easy to read, to reason about, and to confirm as valid.
+
+:::note
+
+Many types of runtime checks cannot be unit tested, and often the checks **are** the test.
+
+:::
+
+Checks can be used for a variety of purposes including:
+
+- configuration checks
+- security checks
+- status checks
+- performance checks
+- health checks
+
+Moodle has had various types of checks and reports for a long time but they were inconsistent and not machine readable. In Moodle 3.9 they were unified under a single Check API which also enabled plugins to cleanly define their own additional checks. Some examples include:
+
+- a password policy plugin could add a security check
+- a custom authentication plugin can add a check that the upstream identity system can be connected to
+- a MUC store plugin could add a performance check
+Having these centralized and with a consistent contract makes it much easier to ensure the whole system is running smoothly. This makes it possible for an external integration with monitoring systems such as Nagios / Icinga. The Check API is exposed as an NPRE compliance cli script:
+
+```console
+php admin/cli/checks.php
+```
+
+## Result states of a check
+
+| Status | Meaning | Example |
+| --- | --- | --- |
+| N/A | This check doesn't apply - but we may still want to expose the check | secure cookies setting is disabled because site is not https |
+| Ok | A component is configured, working and fast. | ldap can bind and return value with low latency |
+| Info | A component is OK, and we may want to alert the admin to something non urgent such as a deprecation, or something which needs to be checked manually. | |
+| Unknown | We don't yet know the state. eg it may be very expensive so it is run using the Task API and we are waiting for the answer. NOTE: unknown is generally a bad thing and is semantically treated as an error. It is better to have a result of Unknown until the first result happens, and from then on it is Ok, or perhaps Warning or Error if the last known result is getting stale. If you are caching or showing a stale result you should expose the time of this in the result summary text. | A complex user security report is still running for the first time. |
+| Warning | Something is not ideal and should be addressed, eg usability or the speed of the site may be affected, but it may self heal (eg a load spike) | auth_ldap could bind but was slower than normal |
+| Error | Something is wrong with a component and a feature is not working | auth_ldap could not connect, so users cannot start new sessions |
+| Critical | An error which is affecting everyone in a major way | Cannot read site data or the database, the whole site is down |
+
+How the various states are then leveraged is a local decision. A typical policy might be that health checks with a status of 'Error' or 'Critical' will page a system administrator 24/7, while 'Warning' only pages during business hours.
+
+## Check types and reports
+
+Checks are broken down into types, which roughly map to a step in the life cycle of your Moodle System.
+
+### Environmental checks
+
+Available from _/admin/environment.php_, environmental checks make sure that a Moodle instance is fully configured.
+
+This page is a potential candidate to move to the new Check API but it slightly more complex than the other checks so it hasn't been tackled yet. It would be a deeper change and this is intrinsically part of the install and upgrade system. It is not as critical to refactor as it is already possible for a plugin to declare its own checks, via either declarative [Environment checking](https://docs.moodle.org/dev/Environment_checking) or programmatically with a custom check:
+
+### Configuration checks
+
+Available from _/admin/index.php?cache=1_, the Admin notifications page performs a mixture of checks, including security, status, and performance checks.
+
+None of these checks are as exhaustive as the checks in the reports below. It also does additional checks including whether the web services for the Moodle Mobile App are enabled, and whether the site has been registered.
+
+### Security checks (security)
+
+Available from _/report/security/index.php_, these checks make sure that a Moodle instance is hardened correctly for your needs.
+
+For more information see [MDL-67776](https://tracker.moodle.org/browse/MDL-67776).
+
+### Status checks (status)
+
+Available from _/report/status/index.php_, a status check is an 'in the moment' test and covers operational tests such as 'can moodle connect to ldap'. The main core status checks are that cron is running regularly and there has been no failed tasks.
+
+:::danger Important
+
+It is critical to understand that Status checks are conceptually defined at the level of the application and not at a lower host level such as a docker container or node in a cluster. Checks should be defined so that whichever instance you ask you should get a consistent answer. DO NOT use the Status Checks to detect containers which need to be reaped or restarted. If you do, any status errors may mean all containers will simultaneously be marked for reaping.
+
+An additional status check is likely the most common type of check a plugin would define. Especially a plugin that connects to a 3rd party service. If the concept of 'OK' requires some sort of threshold, eg network response within 500ms, then that threshold should be managed by the plugin and optionally exposed as a admin setting. The plugin may choose to have different thresholds for Warning / Error / Critical. When designing a new Status Check be mindful that it needs to be actionable, for instance if you are asserting that a remote domain is available and it goes down, which then alerts your infrastructure team, there isn't much they can do about it if it isn't their domain. If it is borderline then make things like this configurable so that each site has to option of tune their own policies of what should be considered an issue or not.
+
+:::
+
+For more information see [MDL-47271](https://tracker.moodle.org/browse/MDL-47271).
+
+### Performance checks (performance)
+
+Available from _/report/performance/index.php_, each check might simply check for certain settings which are known to slow things down, or it might actually do some sort of test like multiple reads and writes to the db or filesystem to get a performance metric.
+
+## Implementing a new check
+
+### A check class
+
+And make a new check class in `mod/myplugin/classes/check/foobar.php` and the only mandatory method is `get_result()`. By default it will use a set language string but you can override the `get_name()` method to reuse an existing string.
+
+```php title="mod/myplugin/lang/en/myplugin.php"
+$string['checkfoobar'] = 'Check the foos to make sure they are barred';
+```
+
+```php title="mod/myplugin/classes/check/foobar"
+id = "foobar{$id}";
+ }
+
+ public function get_id(): string {
+ return $this->id;
+ }
+ ...
+}
+```
+
+### Make checks as fast as practical
+
+As many checks will be run and compiled into a report we want the checks themselves to be simple and as fast as possible. For instance an auth_ldap check while authenticating an end user could have a timeout of 60 seconds, and the check could warn if it takes more than 2 seconds. But the check could have a hard timeout of say 5 seconds and have a result status of ERROR for 5 or more seconds.
+
+### Lazy loading expensive result details
+
+Checks can provide details on a check, such as the complete list of bad records. Generally this type of information might be expensive to produce so you can defer this lookup until get_details() is called specifically rather than setting this in the constructor. It will only be loaded on demand and shown when you drill down into the check details page.
+
+```php
+add(new admin_setting_check(
+ 'antivirus/checkantivirus',
+ new \core\check\environment\antivirus(),
+));
+```
+
+:::info
+
+These checks are performed asynchronously, and only if the check is visible, so will not slow down the whole admin tree which is often a downside of implementing this manually.
+
+:::
+
+You can also use checks which are not linked into one of the reports. This means you can check the connection while configuring the plugin, but before it is enabled, and only add the check to the status report page once the plugin is enabled.
+
+## See also
+
+- [Performance overview](https://docs.moodle.org/en/Performance_overview) user docs
diff --git a/versioned_docs/version-4.5/apis/subsystems/communication/index.md b/versioned_docs/version-4.5/apis/subsystems/communication/index.md
new file mode 100644
index 0000000000..de1de2579f
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/communication/index.md
@@ -0,0 +1,318 @@
+---
+title: Communication API
+tags:
+ - communication
+ - provider
+ - Communication provider
+ - Communication room
+ - Communication service
+ - Chat
+documentationDraft: true
+---
+
+Communication API provides access to the messaging system and other communication providers (such as Matrix).
+This API the management of room and members to that room.
+
+Communication API actions its tasks happens asynchronously using ad-hoc tasks. This means that the API functions will return immediately and the action will be
+executed in the background. Communication API has multiple interfaces which acts like a feature of the API. Each of these interface/features have one or more associated
+ad-hoc tasks. These tasks are responsible to action the requested feature from the provider plugin (such as Matrix) informed by the Communication API instance. For developers
+to interact with the API, they must use the public API (`communication\api`).
+
+Communication API allows to add ad-hoc tasks to the queue to perform actions on the communication providers. This API will not allow any immediate actions to be performed on the
+communication providers. It will only add the tasks to the queue. The exception has been made for deletion of members in case of deleting the user. This is because the user will
+not be available for any checks (capability, role etc.) after deleted.
+
+:::info
+
+This is an experimental feature. To use this feature, you must enable the experimental feature flag for Communication in the site administration.
+Please follow the steps to enable the feature.
+
+1. Navigate to `Site administration > Development > Experimental` settings.
+2. Tick the checkbox to enable the following feature: Enable communication providers (`enablecommunicationsubsystem`).
+
+:::
+
+## Hooks
+
+The Communication API takes advantage of the [Hooks API](../../plugintypes/communication/index.md) with actions performed in the following way:
+
+1. Actions in core have their own hook dispatched (for example enrol, change role, add to group).
+2. Hooks are then registered to a particular callback (`lib/db/hooks.php`).
+3. Callbacks are then listened for inside the Communication API's hook listener (`communication\hook_listener`) where all the logic is performed.
+4. To identify the ones Communication API is using, the callbacks will be methods from Communication API's hook listener (`communication\hook_listener`).
+
+## Important features of the API
+
+The below are the important methods/features of the API. These methods/features are the ones which can be used to interact with the communication API.
+
+### Loading a communication instance
+
+To load a communication instance, you must use the `communication\api::load_by_instance()` method. This method will return a communication instance which can be
+used to interact with the communication API and its related methods. The below example shows how to load a communication instance for a course, where the
+context is the actual course context object, component is the component name (`core_course`), instance type is the custom string which can change according to
+the usage and instance id is the course id. The constructor of the public api is private and hence the only way to load an instance is through
+the `load_by_instance()` method. The provider is the name of the provider plugin which is used to load the instance. It's optional, if not provided, it will load
+the enabled provider for that instance.
+
+```php
+$communication = \core_communication\api::load_by_instance(
+ context: $context,
+ component: 'core_course',
+ instancetype: 'coursecommunication',
+ instanceid: $courseid,
+ provider: 'communication_matrix',
+);
+```
+
+### Create and configure a room
+
+`$communication->create_and_configure_room()` method is used to add an ad-hoc to create a room in the communication provider and configure it with the required settings.
+
+```php
+public function create_and_configure_room(
+ string $communicationroomname,
+ ?\stored_file $avatar = null,
+ ?\stdClass $instance = null,
+);
+```
+
+This method takes the following parameters:
+
+- The name of the room as a string to be created.
+- The avatar of the room to be created, this is a stored file object.
+- The instance object of the communication API.
+
+For example, we want to create a room for a course with the course name as the room name and the course image as the avatar, we can use the below code.
+There can be other cases where information from course instance will be used by a plugin, for example, the dynamic field from matrix plugin has a form
+field named topic which sets the topic of the room, the instance object can be used to get the topic from the course instance and set it in the form field.
+Please refer to [Communication plugin](../../plugintypes/communication/index.md) documentation for more details.
+
+```php
+// First initialize the communication instance,
+// provided the context is already initialized
+// and the selected provider is available
+// though a form or other means.
+$communication = \core_communication\api::load_by_instance(
+ context: $context,
+ component: 'core_course',
+ instancetype: 'coursecommunication',
+ instanceid: $course->id,
+ provider: 'communication_matrix',
+);
+
+// Now call the create_and_configure_room() method
+// to add an ad-hoc to create a room in the
+// communication provider and configure it with the
+// required settings.
+$communication->create_and_configure_room(
+ communicationroomname: $course->fullname,
+ avatar: $courseimage ?: null,
+ instance: $course,
+);
+```
+
+### Update a room and its associated information
+
+`$communication->update_room()` method is used to add an ad-hoc to update a room in the communication provider and configure it with the required settings.
+
+```php
+public function update_room(
+ ?int $active = null,
+ ?string $communicationroomname = null,
+ ?\stored_file $avatar = null,
+ ?\stdClass $instance = null,
+);
+```
+
+This method takes the following parameters:
+
+- The active status of the room to be updated. Can be either 0 or 1.
+- The name of the room as a string to be updated.
+- The avatar of the room to be updated, this is a stored file object.
+- The instance object of the communication API.
+
+```php
+// First initialize the instance.
+$communication = \core_communication\api::load_by_instance(
+ context: $context,
+ component: 'core_course',
+ instancetype: 'coursecommunication',
+ instanceid: $course->id,
+ provider: 'communication_matrix',
+);
+
+// Now update the room with the required settings.
+// The instance will be used by dynamic fields feature of the plugin
+// to allow the plugin to get the required information from the instance.
+$communication->update_room(
+ active: $active,
+ communicationroomname: $communicationroomname,
+ avatar: $courseimage ?: null,
+ instance: $course,
+);
+```
+
+### Delete a room
+
+`$communication->delete_room()` method is used to add an ad-hoc to delete a room in the communication provider. The associated task for this also removes all the members from
+the room before deleting the room.
+
+:::danger
+
+This is destructive and might remove all the messages and other data associated with the room. Usage of this should be done with caution.
+
+:::
+
+### Create and configure a room according to the provider
+
+There are cases where the provider is changed for a communication instance.
+For example, previously the provider was set to _Provider B_ and now it has changed to _Provider A_.
+In this case, the room and its members need to be configured according to the new provider. Without any extra logic needed, the method `$communication->configure_room_and_membership_by_provider()` takes care of this in the following way:
+
+1. Communication provider is changed from _Provider B_ to _Provider A_.
+2. New room is created in _Provider A_.
+3. Members are added to the new room at _Provider A_.
+4. Members are removed from the old room at _Provider B_.
+
+```php
+// First initialize the instance.
+$communication = \core_communication\api::load_by_instance(
+ context: $context,
+ component: 'core_course',
+ instancetype: 'coursecommunication',
+ instanceid: $course->id,
+ provider: 'communication_matrix',
+ );
+
+// Now call this with the new provider to take care of the room and its members for the new provider.
+$communication->configure_room_and_membership_by_provider(
+ provider: 'communication_slack',
+ instance: $course,
+ communicationroomname: $coursecommunicationroomname,
+ users: $enrolledusers,
+ instanceimage: $courseimage,
+ );
+```
+
+### Add members to a room
+
+`$communication->add_members_to_room()` method is used to add an ad-hoc to add members to a room in the communication provider. The user id of each user to be added to
+the provider room is also stored in the communication_user table for user mapping with the provider.
+
+```php
+public function add_members_to_room(
+ array $userids,
+ bool $queue = true,
+);
+```
+
+This method accepts the following parameters:
+
+- An array of user ids to be added to the provider room.
+- A boolean value to indicate if the task should be added to the queue or run immediately. This is false by default. This is added to ensure the cases where the mapping should be created but the task might not be needed at this point.
+
+```php
+// First initialize the instance, provider is not required here, it will initialize the enabled instance.
+$communication = \core_communication\api::load_by_instance(
+ context: $context,
+ component: 'core_course',
+ instancetype: 'coursecommunication',
+ instanceid: $course->id,
+);
+
+// Now add the members.
+$communication->add_members_to_room(
+ userids: $enrolledusers,
+);
+```
+
+### Update the membership of a room
+
+`$communication->update_room_membership()` method is used to add an ad-hoc to update the membership of a room in the communication provider.
+The user mapping for each of the users are also reset to allow task to update the users from the task.
+
+```php
+public function update_room_membership(
+ array $userids,
+ bool $queue = true,
+);
+```
+
+This method accepts the same parameters as the `add_members_to_room()` method and the usage is also the same.
+
+### Remove members from a room
+
+`$communication->remove_members_from_room()` method is used to add an ad-hoc to remove members from a room in the communication provider.
+The users are flagged as deleted in the communication_user table to allow the task to remove the users from the provider room.
+
+```php
+public function remove_members_from_room(
+ array $userids,
+ bool $queue = true
+);
+```
+
+This method accepts the same parameters as the `add_members_to_room()` method and the usage is also the same.
+
+It's also possible to remove all members from a room by using `$communication->remove_all_members_from_room()`. This method does not take any parameters and will remove all users from the room and delete the user mapping found in the `communication_user` table.
+
+:::caution
+
+Both `$communication->remove_all_members_from_room()` and `$communication->remove_members_from_room()` will remove users and may delete communication history from the provider itself.
+
+:::
+
+### Show the communication room creation status notification
+
+`$communication->show_communication_room_status_notification()` is a special method to have a UI element to show the status of the room creation. If the room is ready, it will
+return the notification as a string. If the room is not ready, it will return pending status. Course have this implemented and shows the status after the communication instance
+is configured. Please note, the status notification relies on the creation of room, not the memberships of the room.
+
+### Communication instance setup using form/configuration page
+
+The communication API allows to have settings to configure the basic information like room name, selection of provider etc. `$communication->form_definition()` method
+is used to get the form definition for the settings form. If the form elements are used in another form to include the communication form elements, `form_definition()` method
+will be useful. There is a configuration page (`communication/configure.php`) which can be used to configure the communication instance. Course currently uses this page to set up
+communication and its associated information.
+It will also be required to use `set_data()` method in order to set the data to the form elements while saving the data.
+
+```php
+public function form_definition(
+ \MoodleQuickForm $mform,
+ string $selectdefaultcommunication = processor::PROVIDER_NONE,
+);
+```
+
+This method accepts the following parameters:
+
+- The moodle quick form object to add the form elements to.
+- The default provider to be selected in the form. This is set to none by default.
+
+For example, we have a form where we want to have the communication settings, we can use the below code to add the form elements to the form.
+
+```php
+class configure_form extends \moodleform {
+ public function definition() {
+ $mform = $this->_form;
+ $communication = \core_communication\api::load_by_instance(
+ context: $context,
+ component: 'core_course',
+ instancetype: 'coursecommunication',
+ instanceid: $course->id,
+ );
+ $communication->form_definition(mform: $mform);
+ $this->communication->set_data(instance: $course);
+ }
+}
+```
+
+## Building a communication plugin
+
+Communication API also provides a bunch of interfaces for a communication plugin to consume. Every plugin should implement these interfaces according to the features they
+support.
+
+:::info
+
+Please refer to [Communication plugin](../../plugintypes/communication/index.md) documentation for more details about building a plugin.
+
+:::
diff --git a/versioned_docs/version-4.5/apis/subsystems/editor/index.md b/versioned_docs/version-4.5/apis/subsystems/editor/index.md
new file mode 100644
index 0000000000..bdb2afb1f1
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/editor/index.md
@@ -0,0 +1,54 @@
+---
+title: Editor API
+tags: []
+documentationDraft: true
+---
+
+The editor API lets you control Moodle text editors. It can be found in `lib/editorlib.php`.
+
+:::important
+
+Normally you do not need to use this API directly because you can include editors as part of a Moodle form, which will automatically set up the editor for you.
+
+:::
+
+## How to set up a text editor
+
+To set up a text editor on an existing HTML text area field:
+
+- Call function `editors_get_preferred_editor()`, which will return an object of the texteditor class.
+- Call function `use_editor()` to enable the editor for the text area.
+
+For example, assuming there is an HTML text area with id `mytextareaid`:
+
+```php
+$editor = editors_get_preferred_editor(FORMAT_HTML);
+$editor->use_editor('mytextareaid');
+```
+
+## Editor options
+
+The use_editor function allows an options array to be supplied.
+
+### General options
+
+- `context`: set to the current context object
+- `enable_filemanagement`: set false to disable the file management plugin
+- `autosave`: set false to disable autosave
+
+### Atto-specific options
+
+- `toolbar`: set to override which icons appear on the toolbar (normally it uses the admin setting - this is for special cases for example if you want a minimal editor in a particular plugin).
+
+The following example will cause atto to show the four buttons indicated.
+
+```php
+$attobuttons = 'style1 = bold, italic' . PHP_EOL . 'list = unorderedlist, orderedlist';
+$editor->use_editor($id, [
+ 'context' => $context,
+ 'autosave' => false,
+ 'atto:toolbar' => $attobuttons
+], [
+ 'return_types' => FILE_EXTERNAL,
+]);
+```
diff --git a/versioned_docs/version-4.5/apis/subsystems/enrol.md b/versioned_docs/version-4.5/apis/subsystems/enrol.md
new file mode 100644
index 0000000000..c1044c66a8
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/enrol.md
@@ -0,0 +1,258 @@
+---
+title: Enrolment API
+tags:
+ - Enrolment
+ - Library
+---
+
+
+
+The enrolment API gives access to the enrolment methods and also to [enrolment plugins](../plugintypes/enrol/index.md) instances.
+
+## Difference between user enrolment and role assignment
+
+Users enrolled in a course have at least one record in `user_enrolments` table. This table has the relation between courses and users **through an enrolment plugin instance**. However, `user_enrolments` does not contain information about the user role in the course, only information about:
+
+- **Enrolment plugin instance**
+- **Enrolment status** (active or suspended).
+- **Enrolment Start and end dates**.
+
+The specific role assignments are related to the context, not only to course (as activities and other pages can use its own). The specific roles of a user are stored in the `role_assignments` table. This table stores:
+
+- The **user role id** in the **context**
+- The **component item** that assigned the role. In the case of a regular course, the *component* is the name of the enrolment plugin and the *item id* is the specific plugin instance.
+
+### What is enrolment?
+
+Enrolled users may fully participate in a course. Active user enrolment allows user to enter course. Only enrolled users may be group members. Grades are stored only for enrolled users.
+
+### Unenrolment
+
+Unenrolment is irreversible operation that purges user participation information. Full unenrolment is suitable only if you do not need to preserve all course participation information including user grades.
+
+### Enrolment status
+
+Instead of full unenrolment it is usually better to only *suspend* user enrolment. If there are other ways to enter the course (such guest access) it is recommended to remove user roles at the same time.
+
+Enrolments have two states defined by two constants:
+
+- `ENROL_USER_ACTIVE` the enrolment is active
+- `ENROL_USER_SUSPENDED` the enrolment is suspended
+
+### Activity participation
+
+Activity developers decide the enrolment related behaviour of module.
+
+There are some general guidelines:
+
+- Only users with active enrolments should receive notifications.
+- Activities should display enrolled users with some capability as participants.
+- By default only users with active enrolments should be displayed in reports.
+- There should be option to display all enrolled users including suspended enrolments.
+- For performance reasons invisible participation data should be purged on unenrolment.
+- Contributions visible by other participants should be kept after unenrolment (such as forum posts).
+
+## API functions
+
+### is_enrolled()
+
+Use this method to determine if a user is enrolled into a course. This method returns true for students and teachers, but false for administrators and other managers.
+
+:::caution
+
+User enrolments can be either active or suspended, suspended users can not enter the course (unless there is some kind of guest access allowed or have `moodle/course:view` capability) and are usually hidden in the UI.
+
+:::
+
+```php
+function is_enrolled(
+ context $context,
+ stdClass $user = null,
+ string $withcapability = '',
+ bool $onlyactive = false
+)
+```
+
+Good example is choice module where we have one slot for each participant, people that are not enrolled are not allowed to vote `is_enrolled($context, $USER, 'mod/choice:choose')`. Another example is assignment where users need to be enrolled and have capability to submit assignments `is_enrolled($this->context, $USER, 'mod/assignment:submit')`.
+
+### get_enrolled_users()
+
+Returns the list of enrolled users. This method allows to filter the result by capability, pagination or state.
+
+```php
+function get_enrolled_users(
+ context $context,
+ string $withcapability = '',
+ int $groupid = 0,
+ string $userfields = 'u.*',
+ ?string $orderby = null,
+ int $limitfrom = 0,
+ int $limitnum = 0,
+ bool $onlyactive = false
+)
+```
+
+
+ View example
+
+
+Get all users who are able to submit an assignment:
+
+```php
+$submissioncandidates = get_enrolled_users($modcontext, 'mod/assign:submit');
+```
+
+
+
+
+### count_enrolled_users()
+
+This method is used to get the total count of enrolments of a context. As `get_enrolled_users` this methods allow several filters like capability, group id or counting only active enrollments.
+
+```php
+function count_enrolled_users(
+ context $context,
+ string $withcapability = '',
+ int $groupid = 0,
+ bool $onlyactive = false
+)
+```
+
+### get_enrolled_sql()
+
+SQL `select from get_enrolled_sql()` is often used for performance reasons as it can be used in joins to get specific information for enrolled users only.
+
+```php
+function get_enrolled_sql(
+ context $context,
+ string $withcapability = '',
+ int $groupid = 0,
+ bool $onlyactive = false,
+ bool $onlysuspended,
+ int $enrolid = 0
+)
+```
+
+### enrol_get_plugin(): enrol_plugin
+
+Returns the enrolment plugin base class with the given name.
+
+
+ View example
+
+
+```php
+$instance = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'manual'])
+$enrolplugin = enrol_get_plugin($instance->enrol);
+$enrolplugin->enrol_user($instance, $user->id, $role->id, $timestart, $timeend);
+```
+
+:::note
+
+As can be seen in the example, to use the plugin enrol_user and unenrol_user methods you need to get the instance record of the plugin first.
+
+:::
+
+
+
+
+## Enrolment plugin methods
+
+Once you use `enrol_get_plugin` function to get the enrolment plugin instance, you can use that class to modify the enrolments.
+
+### $enrol_plugin->enrol_user()
+
+Using this method you can enrol a user into a course.
+
+The method takes the following parameters:
+
+- Enrol plugin instance record
+- User id
+- Role id
+- Optional enrolment start and end timestamps
+- Optional enrolment status (ENROL_USER_ACTIVE or ENROL_USER_SUSPENDED)
+- If the enrol must try to recover the previous user enrolment grades (if any)
+
+```php
+$enrolplugin->enrol_user($instance, $user->id, $role->id, $timestart, $timeend, ENROL_USER_ACTIVE);
+```
+
+### $enrol_plugin->unenrol_user()
+
+Unenrol a user from a course enrolment method.
+
+:::note
+
+Other enrolment methods can define other roles to the same user.
+
+:::
+
+The method takes the following parameters:
+
+- Enrol plugin instance record
+- User id
+
+```php
+$enrolplugin->unenrol_user($instance, $user->id);
+```
+
+### $enrol_plugin->update_user_enrol()
+
+Updates a user enrolment **status** and the **start or end dates**.
+
+The method takes the following parameters:
+
+- Enrol plugin instance record
+- User id
+- Optional enrolment start and end timestamps
+- Optional enrolment status (ENROL_USER_ACTIVE or ENROL_USER_SUSPENDED)
+
+```php
+$enrolplugin->update_user_enrol($instance, $user->id, $timestart, $timeend, ENROL_USER_SUSPENDED);
+```
+
+### $enrol_plugin->add_default_instance()
+
+Add a new enrolment instance to a specific course an returns the instance id. This method will create a new instance record in the `enrol` table with the default values.
+
+The method takes the following parameters:
+
+- Course id
+
+```php
+$enrolplugin->add_default_instance($course->id);
+```
+
+### $enrol_plugin->add_custom_instance()
+
+Add a new enrolment instance to a specific course with custom settings an returns the instance id. This method will create a new instance record in the `enrol` table with the specified settings.
+
+The method takes the following parameters:
+
+- Course object
+- Array of instance settings
+
+```php
+$enrolplugin->add_custom_instance($course, $settings);
+```
+
+### $enrol_plugin->delete_instance()
+
+Remove an enrolment instance form a course and invalidate all related user enrolments.
+
+The method takes the following parameters:
+
+- Enrol plugin instance record
+
+```php
+$enrolplugin->delete_instance($instance);
+```
+
+### $enrol_plugin->is_csv_upload_supported()
+
+Checks whether enrolment plugin is supported in CSV course upload. Defaults to false. Override this function in your enrolment plugin if you want it to
+be supported in CSV course upload.
+
+## See also
+
+- [Enrolment plugins](../plugintypes/enrol/index.md)
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/advanced/_category_.json b/versioned_docs/version-4.5/apis/subsystems/external/advanced/_category_.json
new file mode 100644
index 0000000000..626044609c
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/advanced/_category_.json
@@ -0,0 +1,6 @@
+{
+ "position": 6,
+ "label": "Advanced topics",
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/advanced/custom-services.md b/versioned_docs/version-4.5/apis/subsystems/external/advanced/custom-services.md
new file mode 100644
index 0000000000..ac5ba42363
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/advanced/custom-services.md
@@ -0,0 +1,71 @@
+---
+title: Service creation
+tags:
+ - Web Services
+ - core_external
+ - external
+ - API
+---
+
+Moodle comes with two built-in services that your functions can be attached to.
+
+In rare situations, you may need to create a create a _custom_ service declaration.
+
+The recommended way of creating a new _service_ declaration is by placing it into the `db/services.php` file as a new service declaration.
+
+Moodle Administrators can also manually create a service declaration using the web interface.
+
+This is an advanced feature and, in most cases, _you will not need to use this feature_.
+
+:::note
+
+Whilst writing a service declaration is _optional_, if you do not create a service declaration, then the Moodle administrator will have to create one manually through the Web UI.
+
+If you define a web service here, then the administrator cannot add or remove any function from it.
+
+:::
+
+## Declaring a custom service declaration
+
+Service declarations should be placed in the `db/services.php` file of your plugin (after function declaration), for example `local/groupmanager/db/services.php`.
+
+```php title="local/groupmanager/db/services.php"
+$services = [
+ // The name of the service.
+ // This does not need to include the component name.
+ 'myintegration' => [
+
+ // A list of external functions available in this service.
+ 'functions' => [
+ 'local_groupmanager_create_groups',
+ ],
+
+ // If set, the external service user will need this capability to access
+ // any function of this service.
+ // For example: 'local_groupmanager/integration:access'
+ 'requiredcapability' => 'local_groupmanager/integration:access',
+
+ // If enabled, the Moodle administrator must link a user to this service from the Web UI.
+ 'restrictedusers' => 0,
+
+ // Whether the service is enabled by default or not.
+ 'enabled' => 1,
+
+ // This field os optional, but requried if the `restrictedusers` value is
+ // set, so as to allow configuration via the Web UI.
+ 'shortname' => '',
+
+ // Whether to allow file downloads.
+ 'downloadfiles' => 0,
+
+ // Whether to allow file uploads.
+ 'uploadfiles' => 0,
+ ]
+];
+```
+
+:::note
+
+It is not possible for an administrator to add/remove any function from a pre-built service.
+
+:::
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/description.md b/versioned_docs/version-4.5/apis/subsystems/external/description.md
new file mode 100644
index 0000000000..32d31453ea
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/description.md
@@ -0,0 +1,85 @@
+---
+title: Function Declarations
+tags:
+ - Web Services
+ - core_external
+ - external
+ - API
+sidebar_position: 1
+---
+
+Before they can be used, all functions must be _declared_ to Moodle, and their inputs and outputs must be _defined_.
+
+- Functions are _declared_ by noting them in the `db/services.php` file for a plugin.
+- Functions are _defined_ within their own class located within the `\component\external` namespace of a component.
+
+Note that there is a strict [naming convention for external service functions](https://moodledev.io/general/development/policies/naming#web-services).
+
+Function implementation classes consist of one class containing a number of functions, some of which are mandatory.
+
+During a Moodle installation or upgrade, the service and function _declarations_ are parsed by a service discovery process and stored within the database. An administrative UI may be used to change _some_ configuration details of these declarations.
+
+## Service declarations
+
+Each component wishing to create an external service function must declare that the function exists by describing it in the `db/services.php` file for that component.
+
+This information is stored internally within Moodle, and collected as part of the service discovery during installation and upgrade.
+
+```php title="local/groupmanager/db/services.php"
+$functions = [
+ // The name of your web service function, as discussed above.
+ 'local_myplugin_create_groups' => [
+ // The name of the namespaced class that the function is located in.
+ 'classname' => 'local_groupmanager\external\create_groups',
+
+ // A brief, human-readable, description of the web service function.
+ 'description' => 'Creates new groups.',
+
+ // Options include read, and write.
+ 'type' => 'write',
+
+ // Whether the service is available for use in AJAX calls from the web.
+ 'ajax' => true,
+
+ // An optional list of services where the function will be included.
+ 'services' => [
+ // A standard Moodle install includes one default service:
+ // - MOODLE_OFFICIAL_MOBILE_SERVICE.
+ // Specifying this service means that your function will be available for
+ // use in the Moodle Mobile App.
+ MOODLE_OFFICIAL_MOBILE_SERVICE,
+ ],
+ ),
+);
+```
+
+
+Advanced options
+
+A number of advanced options are also available, as described below:
+
+```php title="local/groupmanager/db/services.php"
+$functions = [
+ // The name of your web service function, as discussed above.
+ 'local_myplugin_create_groups' => [
+ // A comma-separated list of capabilities used by the function.
+ // This is advisory only and used to indicate to the administrator
+ // configuring a custom service definition.
+ 'capabilities' => 'moodle/course:creategroups,moodle/course:managegroups',
+
+ // The following parameters are also available, but are no longer recommended.
+
+ // The name of the external function name.
+ // If not specified, this will default to 'execute'.
+ 'methodname' => 'execute',
+
+ // The file containing the class/external function.
+ // Do not use if using namespaced auto-loading classes.
+ 'classpath' => 'local/groupmanager/externallib.php',
+ ),
+);
+```
+
+
+
+The function name is arbitrary, but must follow [the naming convention](https://docs.moodle.org/dev/Web_service_API_functions#Naming_convention). This helps ensure that it is globally unique.
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/files.md b/versioned_docs/version-4.5/apis/subsystems/external/files.md
new file mode 100644
index 0000000000..13efbe516a
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/files.md
@@ -0,0 +1,137 @@
+---
+title: File handling
+tags:
+ - Web Services
+ - core_external
+ - external
+ - core_files
+sidebar_position: 4
+---
+
+Moodle provides two ways to fetch and upload files:
+
+1. A set of web service functions; and
+2. A pair of dedicated endpoints.
+
+## Web service functions
+
+You can use the following functions to upload, and fetch, file content:
+
+1. `core_files_get_files()`; and
+1. `core_files_upload()`.
+
+When using these functions, the file content is base64-encoded.
+
+:::note
+
+Many devices do not have enough memory to encode and decode requests containing large files. As such we recommend using the dedicated endpoints instead.
+
+:::
+
+## Dedicated endpoints
+
+Moodle provides two dedicated endpoints which can be used, alongside the authentication token, to upload and fetch content. These are:
+
+- to upload a file: `/webservice/upload.php`; and
+- to fetch a file: `/webservice/pluginfile.php`.
+
+### File upload
+
+The recommended way to upload file content from an external service is by issue a `POST` request to the `/webservice/upload.php` endpoint, passing in a valid web service token for authentication.
+
+Upon successful upload, any files passed will be saved in the user's draft file area.
+
+The endpoint takes two optional arguments:
+
+- An `itemid` to upload the files to, defaulting to `0`. If none is specified then a new id is generated for the current user's draft file area
+- A `filepath` to store the file in, defaulting to `/`.
+
+The endpoint will return a JSON-encoded summary of the uploaded file, including the `itemid` that it was stored in.
+
+:::tip
+
+It is typical that the `itemid` parameter will be used when the files are uploaded singularly in separate HTTP calls and the files are required to be in the same draft file area.
+
+The client retrieves the `itemid` from the first uploaded file and uses it in subsequent uploads.
+
+This allows multiple files to be uploaded to the same draft file area.
+
+:::
+
+On every successful upload, the file/s information are returned in JSON format. If an error occurs, an error message will be sent back in JSON format too.
+
+:::note Example
+
+To upload a file, `users.csv`, you could use curl as follows:
+
+```bash
+$ curl -X POST -F "file_1=@users.csv" https://SITENAME/webservice/upload.php?token=TOKEN \
+| jq
+
+[
+ {
+ "component": "user",
+ "contextid": 567,
+ "userid": "123",
+ "filearea": "draft",
+ "filename": "users.csv",
+ "filepath": "/",
+ "itemid": 880413555,
+ "license": "allrightsreserved",
+ "author": "User User",
+ "source": "O:8:\"stdClass\":1:{s:6:\"source\";s:13:\"users.csv\";}"
+ }
+]
+```
+
+The returned JSON response includes the key parts of the file record, including the `itemid`.
+
+:::
+
+Once all the files are uploaded, you can call a webserivce function to process the files from the user drafts area, passing in the `itemid` of the draft area containing the list of files for the request. The service can identify the uploads and manipulate them as necessary.
+
+An example of a webservice that accepts files is: `mod_assign_save_submission`.
+
+To accept file uploads, the service must allow "files download" (*Administration > Plugins > Web services > Manage services > Edit service > Advanced button*)
+
+## File download
+
+We serve the files through `/webservice/pluginfile.php`. This script requires a web service token for authentication.
+
+To support file downloads, the service must allow "files download".
+
+:::note
+
+The `/webservice/pluginfile.php` endpoint has the exact same structure as `/pluginfile.php` and `/tokenpluginfile.php`.
+
+We don't serve the files through `/pluginfile.php` for web service clients because it requires the user's login session to work, however it is possible to use the `/tokenpluginfile.php` endpoint with an appropriate token.
+
+:::
+
+## Returning files in Web Services
+
+Since Moodle 3.2, you can return a complete file area list via Web Services using the static `get_area_files` method, defined in `external_util`.
+
+```php
+$forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
+```
+
+You can also use the `external_files` structure definition in combination with the method to return the most common file fields required by WS clients.
+
+```php
+public static function execute_returns(): external_multiple_structure {
+ return new external_multiple_structure(
+ new external_single_structure([
+ 'id' => new external_value(PARAM_INT, 'Forum id'),
+ // ...
+ 'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
+ // ...
+ ])
+ );
+}
+```
+
+## See also
+
+- [Web services developer documentation](./index.md)
+- [Web services user documentation](https://docs.moodle.org/en/Web_services)
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/functions.md b/versioned_docs/version-4.5/apis/subsystems/external/functions.md
new file mode 100644
index 0000000000..63853fcb10
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/functions.md
@@ -0,0 +1,137 @@
+---
+title: Function Definitions
+tags:
+ - Web Services
+ - API
+ - core_external
+ - external
+sidebar_position: 2
+---
+
+An External function _definition_ is the class, and collection of functions, used to define:
+
+- any parameters that the function expects to take, including any types
+- what the function will return, including any types
+- whether the function has been deprecated, or not
+
+It also includes the code that will actually be executed by the function.
+
+The function definition should be located within the `external` namespace of a component.
+
+:::info Example
+
+For a component named `local_groupmanager` located in `local/groupmanager` which is responsible for creating groups on request, you may have:
+
+- a Web service function named: `local_groupmanager_create_groups`
+- defined in a class named `local_groupmanager\external\create_groups`
+- which is located `local/groupmanager/classes/external/create_groups.php`
+
+:::
+
+A service definition:
+
+- _must_ extend the `\core_external\external_api` class
+- _must_ declare an `execute_parameters` function to describe the expected parameters of the function
+- _must_ declare an `execute` function which is called with the functions and performs the expected actions
+- _must_ declare an `execute_returns` function to describe the values returned by the function
+- _may_ declare an `execute_is_deprecated` function to declare a function as deprecated
+
+
+
+:::caution Writing plugins supporting Multiple Moodle versions
+
+The External API subsystem was restructured in Moodle 4.2 and moved from classes within a manually-required file, to autoloaded and namespaced classes.
+
+If you are developing a plugin whose codebase is used or tested in multiple Moodle versions, including older versions of Moodle, then you:
+
+- _must_ `require_once` the `lib/externallib.php` file
+- _must_ extend the `external_api` class instead of `\core_external\external_api`
+
+This will allow your plugin to continue working without deprecation notices or failures.
+
+Please note that deprecation notices will be added to this pathway from Moodle 4.6 onwards.
+
+:::
+
+### An example definition
+
+```php title="local/groupmanager/classes/external/create_groups.php"
+ new external_multiple_structure(
+ new external_single_structure([
+ 'courseid' => new external_value(PARAM_INT, 'The course to create the group for'),
+ 'idnumber' => new external_value(
+ PARAM_RAW,
+ 'An arbitrary ID code number perhaps from the institution',
+ VALUE_DEFAULT,
+ null
+ ),
+ 'name' => new external_value(
+ PARAM_RAW,
+ 'The name of the group'
+ ),
+ 'description' => new external_value(
+ PARAM_TEXT,
+ 'A description',
+ VALUE_OPTIONAL
+ ),
+ ]),
+ 'A list of groups to create'
+ ),
+ ]);
+ }
+
+ public static function execute(array $groups): array {
+ // Validate all of the parameters.
+ [
+ 'groups' => $groups,
+ ] = self::validate_parameters(self::execute_parameters(), [
+ 'groups' => $groups,
+ ]);
+
+ // Perform security checks, for example:
+ $coursecontext = \context_course::instance($courseid);
+ self::validate_context($coursecontext);
+ require_capability('moodle/course:creategroups', $coursecontext);
+
+ // Create the group using existing Moodle APIs.
+ $createdgroups = \local_groupmanager\util::create_groups($groups);
+
+ // Return a value as described in the returns function.
+ return [
+ 'groups' => $createdgroups,
+ ];
+ }
+
+ public static function execute_returns(): external_single_structure {
+ return new external_single_structure([
+ 'groups' => new external_multiple_structure([
+ 'id' => new external_value(PARAM_INT, 'Id of the created user'),
+ 'name' => new external_value(PARAM_RAW, 'The name of the group'),
+ ])
+ ]);
+ }
+}
+```
+
+:::note
+
+Available parameter types are defined in `lib/moodlelib.php` and are used by the `validate_parameters()` function and during return value checking to ensure that the service is called and working as defined.
+
+:::
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [Web services API](./writing-a-service.md)
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/index.md b/versioned_docs/version-4.5/apis/subsystems/external/index.md
new file mode 100644
index 0000000000..f0a099de0e
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/index.md
@@ -0,0 +1,62 @@
+---
+title: External Services
+tags:
+ - external
+ - core_external
+ - API
+---
+
+Moodle has a full-featured Web Service framework, allowing you to use and create web services for use in external systems.
+The Web Service framework and the External API work closely together providing a number of _Endpoints_, and self-describing classes to support a wide range of uses.
+
+Moodle uses these web services internally for:
+
+- AJAX interactions in the Moodle Web Interface; and
+- The official Moodle Mobile App.
+
+The following example shows a typical authentication and protocol workflow.
+
+```mermaid
+sequenceDiagram
+ Client ->> Login Endpoint: Login attempted
+ Note right of Client: Authentication using Username and Password
+ Login Endpoint ->> Client: Session token
+ Client ->> Protocol Server: Function and authentication token
+ Protocol Server -->> External API: Access Control Check
+ Note right of Protocol Server: The Session token is used to confirm user permission against the specified API.
+ External API -->> Protocol Server: Authentication granted
+ Protocol Server ->> Plugin API: Function called against relevant API
+ Plugin API ->> Protocol Server: Result passed back to Protocol server
+ Protocol Server -->> External API: Return value validated
+ Protocol Server ->> Client: Validated data returned to Client
+```
+
+## Developer documentation
+
+The External Service API has two categories of documentation:
+
+1. this documentation details how to _write_ a web service and use the External API; and
+2. API documentation for a live Moodle site, which can be found under ** Site administration > Server > Web services > API Documentation **.
+
+In addition to the standard API endpoints, several additional API endpoints are available for the purpose of uploading, and downloading, files. For more information on these endpoints, see the [file handling](./files.md) documentation.
+
+- [How to contribute a web service function to core](https://docs.moodle.org/dev/How_to_contribute_a_web_service_function_to_core)
+- [Adding a web service to your plugin](./writing-a-service.md)
+- Code example: [Adding a web service, using APIs](https://gist.github.com/timhunt/51987ad386faca61fe013904c242e9b4) by (Tim Hunt)
+- [Implement a web service client](https://docs.moodle.org/dev/Creating_a_web_service_client)
+- [Web services files handling](./files.md)
+- [Web service Listing & Roadmap](https://docs.moodle.org/dev/Web_services_Roadmap)
+
+## Specification and brainstorming
+
+- [External services security](./security.md)
+- [External services description](./description.md)
+
+## See also
+
+- [Web service API functions](https://docs.moodle.org/dev/Web_service_API_functions)
+- [Web services FAQ](https://docs.moodle.org/en/Web_services_FAQ)
+- [How to create and enable a web service](https://docs.moodle.org/en/How_to_create_and_enable_a_web_service)
+- [How to enable the mobile web service](https://docs.moodle.org/en/Enable_mobile_web_services)
+- [Web services user documentation](https://docs.moodle.org/en/Web_services)
+- [Mastering Moodle Web Services development](http://www.slideshare.net/juanleyva/mastering-moodle-web-services-development) - Last session of the Hackfest in the MoodleMoot UK 2016
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/security.md b/versioned_docs/version-4.5/apis/subsystems/external/security.md
new file mode 100644
index 0000000000..84c00c356d
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/security.md
@@ -0,0 +1,85 @@
+---
+title: Security
+tags:
+ - Web Services
+ - core_external
+ - external
+ - API
+sidebar_position: 3
+---
+
+Before operating on any data in an external function, you must ensure that the user:
+
+- has access to context that the data is located in
+- has permission to perform the relevant action
+
+## Validating function parameters
+
+Before working with any data provided by a user you **must** validate the parameters against the definitions you have defined.
+
+To do so you should call the `validate_parameters()` function, passing in the reference to your `execute_parameters()` function, and the complete list of parameters for the function. The function will return the validated and cleaned parameters.
+
+The `validate_parameters()` function is defined on the `\core_external\external_api` class, and can be called as follows:
+
+```php title="local/groupmanager/classes/external/create_groups.php"
+public static function execute(array $groups): array {
+ [
+ 'groups' => $groups,
+ ] = self::validate_parameters(self::execute_parameters(), [
+ 'groups' => $groups,
+ ]);
+ // ...
+}
+```
+
+## Validating access to the Moodle context
+
+Whenever fetching or updating any data within Moodle using an External function definition, you **must** validate the context that the data exists within.
+
+To do so you should call the `validate_context()` function, passing the _most specific_ context for the data.
+
+For example, if you are working with data belonging to a specific activity, you should use the _activity_ context. If you are working with data belonging to a course, you should use the _course_ context.
+
+If your function operates on multiple contexts (like a list of courses), you must validate each context right before generating any response data related to that context.
+
+The `validate_context()` function is defined on the `\core_external\external_api` class, and can be called as follows:
+
+```php title="local/groupmanager/classes/external/create_groups.php"
+public static function execute(array $groups): array {
+ // ...
+ foreach ($groups as $group) {
+ $coursecontext = \context_course::instance($group['courseid']);
+ self::validate_context($coursecontext);
+ // ...
+ }
+}
+```
+
+:::tip
+
+The `validate_context()` function will also configure the correct theme, language, and filters required to render content for the current user.
+
+:::
+
+:::caution
+
+You should not:
+
+- use the `require_login` function from an external function - this function is reserved for php scripts returning a web page.
+- call `$PAGE->set_context()` manually - this will generate warning notices.
+
+The `validate_context()` function is the only correct way to write external functions.
+
+:::
+
+## Ensuring that a user has the appropriate rights
+
+Once you have confirmed that the provided data is of the correct type, and configured Moodle for the specific context, you should also ensure that all capabilities are checked correctly.
+
+You can use the standard capability functions, including:
+
+- `has_capability()` - to check that a user has a single capability
+- `has_any_capability()` - to check that a user has any capability in a set
+- `has_all_capability()` - to check that a user has all capabilities in a set
+- `require_capability()` - to require a single capability
+- `require_all_capabilities()` - to require that a user has all capabilities in a set
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/testing.md b/versioned_docs/version-4.5/apis/subsystems/external/testing.md
new file mode 100644
index 0000000000..02390fbc7c
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/testing.md
@@ -0,0 +1,126 @@
+---
+title: Unit Testing
+tags:
+ - Unit testing
+ - Web Services
+ - external
+ - core_external
+ - API
+---
+
+Unit tests are the best way of checking the behaviour of your external services and can help you to:
+
+- discover use cases you didn't think about
+- understand the feelings and the needs of the web service client developer
+- end up with a function usable by everybody, not only by your own client
+- reach integration way faster as you joined a proof of validity
+- make the QA process a breeze
+
+Writing unit tests for an external service function is no different to writing unit tests for any other part of Moodle, which is documented in under [PHPUnit](/general/development/tools/phpunit).
+
+## How to write an external function PHPUnit test
+
+You should create one unit test testcase for each external service file, and it should be named after the file that it tests.
+
+For example, if you have written a service function in `[componentfolder]/classes/external/get_fruit.php`, you should write a unit test in `[componentfolder]/tests/external/get_fruit_test.php`.
+
+```php title="mod/kitchen/tests/external/get_fruit_test.php"
+.
+
+/**
+ * Unit tests for the get_fruit function of the kitchen.
+ *
+ * @package mod_kitchen
+ * @category external
+ * @copyright 20XX Your Name
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_kitchen\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+class get_fruit_test extends externallib_advanced_testcase {
+
+ /**
+ * Test the execute function when capabilities are present.
+ *
+ * @covers \mod_fruit\external\get_fruit::execute
+ */
+ public function test_capabilities(): void {
+ $this->resetAfterTest(true);
+
+ $course = $this->getDataGenerator()->create_course();
+ $cm = $this->getDataGenerator()->create_module('mod_kitchen', [
+ 'course' => $course->id,
+ ]);
+
+ // Set the required capabilities by the external function
+ $contextid = context_module::instance($cm->cmid)->id;
+ $roleid = $this->assignUserCapability('moodle/CAPABILITYNAME', $contextid);
+
+ // Call the external service function.
+ $returnvalue = get_fruit::execute([
+ 'course' => $course->id,
+ 'cmid' => $cm->id,
+ ]);
+
+ // We need to execute the return values cleaning process to simulate
+ // the web service server.
+ $returnvalue = \core_external\external_api::clean_returnvalue(
+ get_fruit::execute_returns(),
+ $returnvalue
+ );
+
+ // Assert that there was a response.
+ // The actual response is tested in other tests.
+ $this->assertNotNull($returnvalue);
+ }
+
+ /**
+ * Test the execute function when capabilities are missing.
+ *
+ * @covers \mod_fruit\external\get_fruit::execute
+ */
+ public function test_capabilities_missing(): void {
+ global $USER;
+
+ $this->resetAfterTest(true);
+
+ $course = $this->getDataGenerator()->create_course();
+ $cm = $this->getDataGenerator()->create_module('mod_kitchen', [
+ 'course' => $course->id,
+ ]);
+
+ // Set the required capabilities by the external function
+ $contextid = context_module::instance($cm->cmid)->id;
+ $this->unassignUserCapability('moodle/CAPABILITYNAME', $contextid, $roleid);
+
+ $params = [PARAM1, PARAM2, ...];
+
+ // Call without required capability
+ $this->expectException(required_capability_exception::class);
+ get_fruit::execute([
+ 'course' => $course->id,
+ 'cmid' => $cm->id,
+ ]);
+ }
+}
+```
diff --git a/versioned_docs/version-4.5/apis/subsystems/external/writing-a-service.md b/versioned_docs/version-4.5/apis/subsystems/external/writing-a-service.md
new file mode 100644
index 0000000000..e4a5cee4ad
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/external/writing-a-service.md
@@ -0,0 +1,488 @@
+---
+title: Writing a new service
+tags:
+ - Web Services
+ - Plugins
+ - core_external
+ - external
+sidebar_position: 5
+---
+
+This documentation covers the creation of a new external service for use in a web service of a fictional local plugin, `local_groupmanager`.
+
+## Functional specification
+
+The `local_groupmanager` plugin has a need to create groups within a course and would like to do so using its own web service.
+
+:::important
+
+When defining a new service definition, Moodle requires that the name of the definition be in the form:
+
+```sh
+[frankenstyle_component]_[methodname]
+```
+
+The [naming convention](https://moodledev.io/general/development/policies/naming#web-services) further dictates that the `methodname` component be in the form:
+
+```sh
+[methodname] - The name of the method in the form of [verb]_[noun]
+[verb] - Usually one of get, create, delete, update
+ A similar verb that well describes the action may also be used
+[noun] - The object being modified
+ Usually in Plural form
+```
+
+:::
+
+Per the Moodle naming convention for web services the name of the function should be:
+
+```
+local_groupmanager_create_groups
+```
+
+### Inputs
+
+The `local_groupmanager_create_groups` external service definition will take a list of _groups_ as its only parameters.
+
+### Outputs
+
+The service will return a list of the created groups, including the `id` element of those groups.
+
+### Exceptions and failures
+
+If _any_ group creation fails, the function will throw an exception, and no groups will be created.
+
+## Technical specification
+
+- **the core function the external function will call**: `groups_create_group()` from [/group/lib.php](http://github.com/moodle/moodle/tree/main/moodle/group/lib.php).
+- **the parameter types**: a list of object. This object are groups, with `id`/`name`/`courseid`.
+- **the returned value types**: a list of objects (groups) with their id.
+- **the user capabilities to check**: `moodle/course:managegroups`
+
+## Declare the web service function
+
+An external function must be declared before it can be used in your plugin.
+Function declarations should be placed in the `db/services.php` file of your plugin. For example in our fictitious plugin this would be located in `local/groupmanager/db/services.php`.
+
+```php
+$functions = [
+ // The name of your web service function, as discussed above.
+ 'local_groupmanager_create_groups' => [
+ // The name of the namespaced class that the function is located in.
+ 'classname' => 'local_groupmanager\external\create_groups',
+
+ // A brief, human-readable, description of the web service function.
+ 'description' => 'Creates new groups.',
+
+ // Options include read, and write.
+ 'type' => 'write',
+
+ // Whether the service is available for use in AJAX calls from the web.
+ 'ajax' => true,
+
+ // An optional list of services where the function will be included.
+ 'services' => [
+ // A standard Moodle install includes one default service:
+ // - MOODLE_OFFICIAL_MOBILE_SERVICE.
+ // Specifying this service means that your function will be available for
+ // use in the Moodle Mobile App.
+ MOODLE_OFFICIAL_MOBILE_SERVICE,
+ ]
+ ],
+];
+```
+
+
+Advanced options
+
+A number of advanced options are also available, as described below:
+
+```php
+$functions = [
+ // The name of your web service function, as discussed above.
+ 'local_groupmanager_create_groups' => [
+ // A comma-separated list of capabilities used by the function.
+ // This is advisory only and used to indicate to the administrator configuring a custom service definition.
+ 'capabilities' => 'moodle/course:creategroups,moodle/course:managegroups',
+
+ // The following parameters are also available, but are no longer recommended.
+
+ // The name of the external function name.
+ // If not specified, this will default to 'execute'.
+ // 'methodname' => 'execute',
+
+ // The file containing the class/external function.
+ // Do not use if using namespaced auto-loading classes.
+ // 'classpath' => 'local/groupmanager/externallib.php',
+ ),
+);
+```
+
+
+
+## Write the external function descriptions
+
+Every web service function is mapped to an external function. External function are described in the [External functions API](./functions.md).
+Each external function is written with two other functions describing the parameters and the return values. These description functions are used by web service servers to:
+
+- validate the web service function parameters
+- validate the web service function returned values
+- build WSDL files or other protocol documents
+
+These two description functions are located in the class declared in `local/groupmanager/db/services.php`.
+
+Thus for the web service function `local_groupmanager_create_groups()`, we should write a class named `create_groups` in the `local_groupmanager\external` namespace.
+
+This will be located in the file `local/groupmanager/classes/external/create_groups.php`. The class will contain:
+
+- `execute(...)`
+- `execute_parameters()`
+- `execute_return()`
+
+### Defining parameters
+
+```php
+ new external_multiple_structure(
+ new external_single_structure([
+ 'courseid' => new external_value(PARAM_INT, 'id of course'),
+ 'name' => new external_value(
+ PARAM_TEXT,
+ 'multilang compatible name, course unique'
+ ),
+ 'description' => new external_value(
+ PARAM_RAW,
+ 'group description text'
+ ),
+ 'enrolmentkey' => new external_value(
+ PARAM_RAW,
+ 'group enrol secret phrase'
+ ),
+ ])
+ )
+ ]);
+ }
+}
+```
+
+A web service function without parameters will have a parameter description function like that:
+
+```php
+/**
+ * Returns description of method parameters
+ * @return external_function_parameters
+ */
+public static function execute_parameters(): external_function_parameters {
+ return new external_function_parameters([
+ // If this function had any parameters, they would be described here.
+ // This example has no parameters, so the array is empty.
+ ]);
+}
+```
+
+A parameter can be described as:
+
+- a list => `external_multiple_structure`
+- an object => `external_single_structure`
+- a primary type => `external_value`
+
+Our `create_groups()` function expects one parameter named `groups`, so we will first write:
+
+```php
+/**
+ * Returns description of method parameters
+ * @return external_function_parameters
+ */
+public static function execute_parameters(): external_function_parameters {
+ return new external_function_parameters([
+ 'groups' => ...
+ ]);
+}
+```
+
+This *groups* parameter is a list of group. So we will write :
+
+```php
+'groups' => new external_multiple_structure(
+ ...
+)
+```
+
+An external_multiple_structure object (list) can be constructed with:
+
+- `external_multiple_structure` (list)
+- `external_single_structure` (object)
+- `external_value` (primary type).
+
+For our function it will be a `external_single_structure`:
+
+```php
+new external_single_structure([
+ 'courseid' => ...,
+ 'name' => ...,
+ 'description' => ...,
+ 'enrolmentkey' => ...,
+])
+```
+
+Thus we obtain :
+
+```php
+'groups' => new external_multiple_structure(
+ new external_single_structure([
+ 'courseid' => ...,
+ 'name' => ...,
+ 'description' => ...,
+ 'enrolmentkey' => ...,
+ ])
+)
+```
+
+Each group values is a *external_value* (primary type):
+
+- `courseid` is an integer
+- `name` is a string (text only, not tag)
+- `description` is a string (can be anything)
+- `enrolmentkey` is also a string (can be anything)
+
+We add them to the description :
+
+```php
+'groups' => new external_multiple_structure(
+ new external_single_structure([
+ // The second argument is a human readable description text.
+ // This text is displayed in the automatically generated documentation.
+ 'courseid' => new external_value(PARAM_INT, 'id of course'),
+ 'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
+ 'description' => new external_value(PARAM_RAW, 'group description text'),
+ 'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
+ ])
+)
+```
+
+### execute_returns()
+
+It's similar to execute_parameters(), but instead of describing the parameters, it describes the return values.
+
+```php
+public static function execute_returns() {
+ return new external_multiple_structure(
+ new external_single_structure([
+ 'id' => new external_value(PARAM_INT, 'group record id'),
+ 'courseid' => new external_value(PARAM_INT, 'id of course'),
+ 'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
+ 'description' => new external_value(PARAM_RAW, 'group description text'),
+ 'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
+ ])
+ );
+}
+```
+
+### Required, Optional or Default value
+
+A value can be `VALUE_REQUIRED`, `VALUE_OPTIONAL`, or `VALUE_DEFAULT`. If not mentioned, a value is `VALUE_REQUIRED` by default.
+
+```php
+'yearofstudy' => new external_value(PARAM_INT, 'year of study', VALUE_DEFAULT, 1979),
+```
+
+- `VALUE_REQUIRED` - if the value is not supplied => the server throws an error message
+- `VALUE_OPTIONAL` - if the value is not supplied => the value is ignored. Note that VALUE_OPTIONAL can't be used in top level parameters, it must be used only within array/objects key definition. If you need top level Optional parameters you should use VALUE_DEFAULT instead.
+- `VALUE_DEFAULT` - if the value is not supplied => the default value is used
+
+:::caution
+
+Because some web service protocols are strict about the number and types of arguments - it is not possible to specify an optional parameter as one of the top-most parameters for a function.
+
+
+
+```php
+public static function get_biscuit_parameters() {
+ return new external_function_parameters([
+ 'chocolatechips' => new external_value(
+ PARAM_BOOL,
+ 'if biscuit contains chocolate chips',
+ VALUE_REQUIRED
+ ),
+ 'glutenfree' => new external_value(
+ type: PARAM_BOOL,
+ required: VALUE_DEFAULT,
+ default: false,
+ allownull: false
+ ),
+ // ERROR! top level optional parameter!!!
+ 'icingsugar' => new external_value(
+ PARAM_BOOL,
+ 'if biscuit has icing sugar on top',
+ VALUE_OPTIONAL
+ ),
+ ]);
+}
+```
+
+
+
+
+
+```php
+public static function get_biscuit_parameters() {
+ return new external_function_parameters([
+ 'ifeellike' => new external_single_structure([
+ 'chocolatechips' => new external_value(
+ PARAM_BOOL,
+ 'if biscuit contains chocolate chips',
+ VALUE_REQUIRED
+ ),
+ 'glutenfree' => new external_value(
+ type: PARAM_BOOL,
+ required: VALUE_DEFAULT,
+ default: false,
+ allownull: false
+ ),
+ // ALL GOOD!! We have nested the params in an external_single_structure.
+ 'icingsugar' => new external_value(
+ PARAM_BOOL,
+ 'if biscuit has icing sugar on top',
+ VALUE_OPTIONAL
+ ),
+ ]),
+ ]);
+}
+```
+
+
+
+:::
+
+## Implement the external function
+
+We declared our web service function and we defined the external function parameters and return values. We will now implement the external function:
+
+```php
+ /**
+ * Create groups
+ * @param array $groups array of group description arrays (with keys groupname and courseid)
+ * @return array of newly created groups
+ */
+ public static function execute($groups) {
+ global $CFG, $DB;
+ require_once("$CFG->dirroot/group/lib.php");
+
+ $params = self::validate_parameters(self::execute_parameters(), ['groups' => $groups]);
+
+ $transaction = $DB->start_delegated_transaction(); //If an exception is thrown in the below code, all DB queries in this code will be rollback.
+
+ $groups = array();
+
+ foreach ($params['groups'] as $group) {
+ $group = (object)$group;
+
+ if (trim($group->name) == '') {
+ throw new invalid_parameter_exception('Invalid group name');
+ }
+ if ($DB->get_record('groups', ['courseid' => $group->courseid, 'name' => $group->name])) {
+ throw new invalid_parameter_exception('Group with the same name already exists in the course');
+ }
+
+ // now security checks
+ $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
+ self::validate_context($context);
+ require_capability('moodle/course:managegroups', $context);
+
+ // finally create the group
+ $group->id = groups_create_group($group, false);
+ $groups[] = (array) $group;
+ }
+
+ $transaction->allow_commit();
+
+ return $groups;
+ }
+```
+
+### Parameter validation
+
+```php
+$params = self::validate_parameters(self::execute_parameters(), [
+ 'groups' => $groups,
+]);
+```
+
+This *validate_parameters* function validates the external function parameters against the description. It will return an exception if some required parameters are missing, if parameters are not well-formed, and check the parameters validity. It is essential that you do this call to avoid potential hack.
+
+**Important:** the parameters of the external function and their declaration in the description **must be the same order**. In this example we have only one parameter named $groups, so we don't need to worry about the order.
+
+### Context and Capability checks
+
+```php
+// Perform security checks.
+$context = context_course::instance($group->courseid);
+self::validate_context($context);
+require_capability('moodle/course:managegroups', $context);
+```
+
+Note: validate_context() is required in all external functions before operating on any data belonging to a context. This function does sanity and security checks on the context that was passed to the external function - and sets up the global $PAGE and $OUTPUT for rendering return values. Do NOT use require_login(), or $PAGE->set_context() in an external function.
+
+### Exceptions
+
+You can throw exceptions. These are automatically handled by Moodle web service servers.
+
+```php
+// Note: It is good practice to add detailled information in $debuginfo,
+// and only send back a generic exception message when Moodle DEBUG mode < NORMAL.
+// It's what we do here throwing the invalid_parameter_exception($debug) exception
+throw new invalid_parameter_exception('Group with the same name already exists in the course');
+```
+
+### Correct return values
+
+The return values will be validated by the Moodle web service servers:
+
+- return values contain some values not described => these values will be skipped.
+- return values miss some required values (VALUE_REQUIRED) => the server will return an error.
+- return values types don't match the description (int != PARAM_ALPHA) => the server will return an error
+**Note:** cast all your returned objects into arrays.
+
+## Bump the plugin version
+
+Edit your `local/groupmanager/version.php` and increase the plugin version. This should trigger a Moodle upgrade and the new web service should be available in the administration (*Administration > Plugins > Web Services > Manage services*)
+
+## Deprecation
+
+External functions deprecation process is slightly different from the standard deprecation. If you are interested in deprecating any of your external functions you should **also** (apart from the applicable points detailed in the [standard deprecation docs](/general/development/policies/deprecation)) create a `FUNCTIONNAME_is_deprecated()` method in your external function class. Return true if the external function is deprecated. This is an example:
+
+```php
+ /**
+ * Mark the function as deprecated.
+ * @return bool
+ */
+ public static function execute_is_deprecated() {
+ return true;
+ }
+```
+
+## See also
+
+- [Web services developer documentation](./index.md)
+- [Web services user documentation](https://docs.moodle.org/en/Web_services)
+- [Implement a web service client](https://docs.moodle.org/dev/Creating_a_web_service_client)
+- Code example: [Adding a web service, using APIs](https://gist.github.com/timhunt/51987ad386faca61fe013904c242e9b4) by (Tim Hunt)
+Specification:
+- [External services security](./security.md)
+- [External services description](./description.md)
+- [Session locks#Read only sessions in web services](https://docs.moodle.org/dev/Session_locks#Read_only_sessions_in_web_services)
diff --git a/versioned_docs/version-4.5/apis/subsystems/favourites/index.md b/versioned_docs/version-4.5/apis/subsystems/favourites/index.md
new file mode 100644
index 0000000000..a8b333a5b9
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/favourites/index.md
@@ -0,0 +1,132 @@
+---
+title: Favourites API
+tags:
+ - API
+ - core_favourites
+---
+
+## Overview
+
+### What is a favourite?
+
+The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit.
+
+### What can be marked as a favourite?
+
+Almost any 'item' can be marked as a favourite, provided it is something which can be identified by a unique integer id.
+
+### Identifying items
+
+In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: **component**, **itemtype**, **itemid** and **contextid**. You will see these in a range of API calls.
+
+The **itemid** is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents.
+
+The two fields **component** and **itemtype** make up a pairing representing the *type* of each favourite. Within this pair, the **component** must be a valid [frankenstyle](/general/development/policies/codingstyle/frankenstyle) component name and is the name of the component wishing to set/unset the item as a favourite. The **itemtype** can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values.
+
+The **contextid** is the id of the context in which the item is being marked as a favourite. For example, a user's course might be marked as a favourite at the course context, whereas a user's conversation with another user might be marked as a favourite at the user context. It's also possible that items of a certain *type* (remember, this is the `{component, itemtype}` pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite.
+
+## Using the API
+
+### Getting a service object
+
+Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods.
+
+Assuming you have a user context, you can get a service scoped to a single user with:
+
+```php
+$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
+```
+
+The returned `$ufservice` is an object of type \core_favourites\local\service\user_favourite_service.
+
+### Creating a favourite
+
+Let's say we want to set a course as a favourite. Note: In core, this is done by using the favourite *type* {'core_course', 'courses'}.
+
+The service provides the method:
+
+```php
+public function create_favourite(
+ string $component,
+ string $itemtype,
+ int $itemid,
+ \context $context,
+ int $ordering = null
+): favourite;
+```
+
+So, assuming we have the course id and course context, we can create our favourite with:
+
+```php
+$favourite = $ufservice->create_favourite('core_course', 'courses', $course->id, $coursecontext);
+```
+
+The returned $favourite is an object of type \core_favourites\local\entity\favourite.
+
+### Reading favourites
+
+There are several read actions supported by the service object.
+
+```php
+public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int;
+public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array;
+public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool;
+public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite;
+```
+
+### Deleting a favourite
+
+The service provides the method:
+
+```php
+public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context);
+```
+
+So, assuming we have the course id and course context, we can remove the favourite with:
+
+```php
+$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
+$ufservice->delete_favourite('core_course', 'courses', $course->id, $coursecontext);
+```
+
+### Including favourites in external queries
+
+Most of the time, you should ask the service to find favourite items for you. Sometimes, however, rather than fetching the favourites from the service, you'll just want to include the relevant information in those records from an existing query. You might want to do this if dealing with performance sensitive code where additional queries are undesirable.
+
+The service lets you do this too, by providing the method:
+
+```php
+public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array;
+```
+
+which can be used in such cases.
+
+For example, and for simplicity, let's say we have a query returning the ids and names of all courses within a given course category:
+
+```php
+$sql = "SELECT c.id, c.name
+ FROM {course} c
+ WHERE c.category = :category";
+$params = ['category' => 3];
+
+$courses = $DB->get_records_sql($sql, $params);
+```
+
+we can then modify this using the get_join_sql_by_type() result to include favourite information.
+
+```php
+$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
+list($favsql, $favparams) = $ufservice->get_join_sql_by_type('core_course', 'courses', 'favalias', 'c.id');
+
+$sql = "SELECT c.id, c.name, favalias.id as favouriteid
+ FROM {course} c
+ $favsql
+ WHERE c.category = :category";
+$params = ['category' => 3] + $favparams;
+
+$courses = $DB->get_records_sql($sql, $params);
+```
+
+We've now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records.
+
+If you wish to select ONLY favourites, adding `"AND favouriteid IS NOT NULL"` to the query will achieve this.
diff --git a/versioned_docs/version-4.5/apis/subsystems/files/browsing.md b/versioned_docs/version-4.5/apis/subsystems/files/browsing.md
new file mode 100644
index 0000000000..b5c1961d2f
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/files/browsing.md
@@ -0,0 +1,45 @@
+---
+title: File Browser API
+tags:
+ - File API
+ - Files
+---
+
+The File Browser API is a supplemental API which can be used to fetch information relating to Files stored in the [Moodle File API](./index.md).
+
+### Fetch a series of breadcrumbs to the requested file
+
+This example demonstrates using the `filebrowser` API to fetch the parent folders of a file.
+
+```php
+public function get_file_breadcrumbs(\stored_file $file): ?array {
+ $browser = get_file_browser();
+ $context = get_system_context();
+
+ $fileinfo = $browser->get_file_info(
+ \context::instance_by_id($file->get_contextid()),
+ $file->get_component(),
+ $file->get_filearea(),
+ $file->get_itemid(),
+ $file->get_filepath(),
+ $file->get_filename()
+ )
+
+ if ($fileinfo) {
+ // Build a Breadcrumb trail
+ $level = $fileinfo->get_parent();
+ while ($level) {
+ $path[] = [
+ 'name' => $level->get_visible_name(),
+ ];
+ $level = $level->get_parent();
+ }
+
+ $path = array_reverse($path);
+
+ return $path;
+ }
+
+ return null;
+}
+```
diff --git a/versioned_docs/version-4.5/apis/subsystems/files/converter.md b/versioned_docs/version-4.5/apis/subsystems/files/converter.md
new file mode 100644
index 0000000000..d94913d117
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/files/converter.md
@@ -0,0 +1,98 @@
+---
+title: File Converters
+tags:
+ - File
+ - core_file
+ - file_converter
+ - API
+ - PDF
+ - Conversion
+ - Document
+---
+
+Users are able to submit a wide range of files, and it is a common requirement to convert these to alternative formats.
+
+The most obvious example of this in Moodle core is in the `assignfeedback_editpdf` plugin which allows for conversion from a variety of document types into PDF to facilitate annotation.
+
+The file converters distributed with Moodle currently are:
+
+- Unoconv
+- Google Drive
+
+The file converter API allows for conversion via multiple plugins and will automatically fallback to another suitable plugin upon failure.
+
+The API is designed to be called asynchronously as many cloud services offering document conversion offer an asynchronous API themselves.
+
+## Using the file converter API
+
+A file conversion is performed by the `core_files\converter` API and a single conversion is represented by the `core\files\conversion` class.
+
+Individual file conversions should always be accessed by the `core_files\converter` API.
+
+A file conversion is fetched or created by calling the `start_conversion` function on the converter API and passing in an existing `stored_file` record, along with the target format.
+
+```php title="Starting a new conversion"
+$converter = new \core_files\converter();
+$conversion = $converter->start_conversion($file, 'pdf');
+```
+
+If an existing file conversion matching the file and target format exists, the conversion record for this file will be returned, otherwise a new conversion is created and returned.
+
+To force a fresh conversion, a third boolean parameter can be passed, though this should not normally be necessary.
+
+```php title="Forcing a conversion to be performed again"
+$converter = new \core_files\converter();
+$conversion = $converter->start_conversion($file, 'pdf', true);
+```
+
+### Polling for updates on an existing conversion
+
+When the `start_conversion` function is called, it automatically polls for any update on the conversion so it should not normally be necessary to poll the status separately.
+
+It is however possible to do so:
+
+```php title="Polling the API for the status of a conversino"
+$converter = new \core_files\converter();
+$conversion = $converter->start_conversion($file, 'pdf');
+$converter->poll_conversion($conversion);
+```
+
+### Checking status of a conversion
+
+File conversions can have one of four states:
+
+- `STATUS_PENDING` - The conversion has not yet started;
+- `STATUS_IN_PROGRESS` - A conversion has been picked up by a file converter and is currently in progress;
+- `STATUS_COMPLETE` - The conversion was successful and the converted file is available; and
+- `STATUS_FAILED` - All attempts to convert the file have failed.
+
+The conversion API provides a way to check the status of the conversion with the `$conversion->get_status()` function:
+
+```php title="Fetching status"
+$converter = new \core_files\converter();
+$conversion = $converter->start_conversion($file, 'pdf');
+
+switch ($conversion->get_status()) {
+ case \core_files\conversion::STATUS_COMPLETE:
+ // The file is ready. Do something with it.
+ case \core_files\conversion::STATUS_PENDING:
+ case \core_files\conversion::STATUS_IN_PROGRESS:
+ // Conversion still ongoing. Display spinner to the user.
+ case \core_files\conversion::STATUS_FAILED:
+ // Permanent failure - handle to the user.
+}
+```
+
+### Fetching the target file
+
+Following a conversion, the target file is stored as a `stored_file` record and can be fetched for consumption elsewhere in your API:
+
+```php title="Fetching the new file"
+if ($conversion->get_status() === \core_files\conversion::STATUS_COMPLETE) {
+ $file = $conversion->get_destfile();
+}
+```
+
+## See also
+
+- Creating a new [file converter plugin](../../plugintypes/fileconverter/index.md)
diff --git a/versioned_docs/version-4.5/apis/subsystems/files/index.md b/versioned_docs/version-4.5/apis/subsystems/files/index.md
new file mode 100644
index 0000000000..83e11d61da
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/files/index.md
@@ -0,0 +1,438 @@
+---
+title: File API
+tags:
+ - File API
+ - Files
+---
+
+
+
+The File API is used to control, manage, and serve all files uploaded and stored within Moodle. This page covers the core File API, which is responsible for storage, retrieval, and serving of files stored in Moodle.
+
+The following documentation is also related:
+
+- The [Repository API](../../plugintypes/repository/index.md) is responsible for the code paths associated with uploading files to Moodle. This includes Repository plugins.
+- [Using the File API in Moodle forms](../form/usage/files.md)
+- Additional detail of how this API works is discussed in the [File API internals](./internals.md)
+
+## File areas
+
+Files are conceptually stored in _file areas_. A file area is uniquely identified by:
+
+- A `contextid`.
+- A full component name (using the [Frankenstyle](/general/development/policies/codingstyle/frankenstyle)/frankenstyle) format), for example `course`, `mod_forum`, `mod_glossary`, `block_html`.
+- A file area type, for example `intro` or `post`.
+- A unique `itemid`. Typically if there is only one of a file area per context, then the `itemid` is `0`, whilst if there can be multiple instances of a file area within a context, then the id of the item it relates to is used. For example in the course introduction text area, there is only one course introduction per course, so the `itemid` is set to `0`, whilst in a forum each forum post is within the same context, and the `itemid` should be the id of the post that it relates to.
+
+:::note
+
+File areas are not listed separately anywhere, they are stored implicitly in the files table.
+
+:::
+
+:::important Accessing files belonging to another component
+
+Please note that each plugin, or subsystem should only ever access its own file areas. Any other access should be made using that components own APIs. For example a file in the `mod_assign` plugin should only access files within the `mod_assign` component, and no other component should access its files.
+
+:::
+
+### Naming file areas
+
+The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas where possible (for example: intro, post, attachment, description).
+
+## Serving files to users
+
+The serving of files to users is separated into two distinct areas:
+
+1. Generating an appropriate URL to the file; and
+2. Parsing the URL to serve the file correctly.
+
+This allows Moodle to have a shared file serving mechanism which is common to all Moodle components.
+
+When serving files you _must_ implement both parts together.
+
+### Generating a URL to your files
+
+You must refer to the file with a URL that includes a file-serving script, often `pluginfile.php`. This is usually generated with the `moodle_url::make_pluginfile_url()` function. For example:
+
+```php title="Generating a pluginfile URL for a known file"
+$url = moodle_url::make_pluginfile_url(
+ $file->get_contextid(),
+ $file->get_component(),
+ $file->get_filearea(),
+ $file->get_itemid(),
+ $file->get_filepath(),
+ $file->get_filename(),
+ false // Do not force download of the file.
+);
+```
+
+:::note
+
+Note: If you do not need the `itemid`, then you _may_ pass a `null` value instead of the `itemid`.
+
+This will remove the `itemid` from the URL entirely - this must be considered when [serving your file to the user](#serving-your-file-to-the-user).
+
+:::
+
+The final parameter (`false` here) is `forcedownload`.
+
+### Serving your file to the user
+
+File serving is performed by a small number of file serving scripts which include:
+
+- `pluginfile.php`; and
+- `tokenpluginfile`.php.
+
+These file serving scripts are responsible for authenticating the user, parsing the URL, and then passing the parameters provided in the URL to the relevant component, via a _callback_, which will then serve the file.
+
+The relevant component is then responsible for finding the file, performing relevant security checks, and finally serving the file to the user.
+
+The component callback _must_ be located in your plugin's `lib.php` file, and _must_ be named `[component]_pluginfile()`. It is passed the following values:
+
+- the context id;
+- the component name;
+- the name of the file area;
+- any item id, if specified in the URL; and
+- the file path and file name.
+
+:::info
+
+The complete function signature for this callback is as follows:
+
+```php
+/**
+ * Serve the requested file for the [component_name] plugin.
+ *
+ * @param stdClass $course the course object
+ * @param stdClass $cm the course module object
+ * @param stdClass $context the context
+ * @param string $filearea the name of the file area
+ * @param array $args extra arguments (itemid, path)
+ * @param bool $forcedownload whether or not force download
+ * @param array $options additional options affecting the file serving
+ * @return bool false if the file not found, just send the file otherwise and do not return anything
+ */
+function [component_name]_pluginfile(
+ $course,
+ $cm,
+ $context.
+ string $filearea,
+ array $args,
+ bool $forcedownload,
+ array $options
+): bool;
+```
+
+
+ See an example implementation of this callback for an activity module.
+
+```php title="mod/myplugin/lib.php"
+/**
+ * Serve the files from the myplugin file areas.
+ *
+ * @param stdClass $course the course object
+ * @param stdClass $cm the course module object
+ * @param stdClass $context the context
+ * @param string $filearea the name of the file area
+ * @param array $args extra arguments (itemid, path)
+ * @param bool $forcedownload whether or not force download
+ * @param array $options additional options affecting the file serving
+ * @return bool false if the file not found, just send the file otherwise and do not return anything
+ */
+function mod_myplugin_pluginfile(
+ $course,
+ $cm,
+ $context,
+ string $filearea,
+ array $args,
+ bool $forcedownload,
+ array $options = []
+): bool {
+ global $DB;
+
+ // Check the contextlevel is as expected - if your plugin is a block, this becomes CONTEXT_BLOCK, etc.
+ if ($context->contextlevel != CONTEXT_MODULE) {
+ return false;
+ }
+
+ // Make sure the filearea is one of those used by the plugin.
+ if ($filearea !== 'expectedfilearea' && $filearea !== 'anotherexpectedfilearea') {
+ return false;
+ }
+
+ // Make sure the user is logged in and has access to the module (plugins that are not course modules should leave out the 'cm' part).
+ require_login($course, true, $cm);
+
+ // Check the relevant capabilities - these may vary depending on the filearea being accessed.
+ if (!has_capability('mod/myplugin:view', $context)) {
+ return false;
+ }
+
+ // The args is an array containing [itemid, path].
+ // Fetch the itemid from the path.
+ $itemid = array_shift($args);
+
+ // The itemid can be used to check access to a record, and ensure that the
+ // record belongs to the specifeid context. For example:
+ if ($filearea === 'expectedfilearea') {
+ $post = $DB->get_record('myplugin_posts', ['id' => $itemid]);
+ if ($post->myplugin !== $context->instanceid) {
+ // This post does not belong to the requested context.
+ return false;
+ }
+
+ // You may want to perform additional checks here, for example:
+ // - ensure that if the record relates to a grouped activity, that this
+ // user has access to it
+ // - check whether the record is hidden
+ // - check whether the user is allowed to see the record for some other
+ // reason.
+
+ // If, for any reason, the user does not hve access, you can return
+ // false here.
+ }
+
+ // For a plugin which does not specify the itemid, you may want to use the following to keep your code consistent:
+ // $itemid = null;
+
+ // Extract the filename / filepath from the $args array.
+ $filename = array_pop($args); // The last item in the $args array.
+ if (empty($args)) {
+ // $args is empty => the path is '/'.
+ $filepath = '/';
+ } else {
+ // $args contains the remaining elements of the filepath.
+ $filepath = '/' . implode('/', $args) . '/';
+ }
+
+ // Retrieve the file from the Files API.
+ $fs = get_file_storage();
+ $file = $fs->get_file($context->id, 'mod_myplugin', $filearea, $itemid, $filepath, $filename);
+ if (!$file) {
+ // The file does not exist.
+ return false;
+ }
+
+ // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
+ send_stored_file($file, DAY_SECS, 0, $forcedownload, $options);
+}
+```
+
+
+
+:::
+
+## Getting files from the user
+
+You will typically use the [Forms API](https://docs.moodle.org/dev/Forms_API) to accept files from users. This topic is detailed in more detail in the [Using the File API in Moodle forms](../form/usage/files.md) documentation.
+
+## Common uses of the file API
+
+Although you will usually interact with the File API from other related APIs including the Form, and Repository APIs, you may find that you need to interact with files directly for a range of purposes.
+
+### Create files
+
+There are several ways to created files in the Moodle file store. Each of them
+requires a `fileinfo` record, which is a `stdClass` object containing all of the
+relevant information that cannot be calculated automatically. A file info record
+may look like the following example:
+
+```php title="A sample file info record"
+$fileinfo = [
+ 'contextid' => $context->id, // ID of the context.
+ 'component' => 'mod_mymodule', // Your component name.
+ 'filearea' => 'myarea', // Usually = table name.
+ 'itemid' => 0, // Usually = ID of row in table.
+ 'filepath' => '/', // Any path beginning and ending in /.
+ 'filename' => 'myfile.txt', // Any filename.
+];
+```
+
+#### From a file on disk
+
+If you need to create a file from another file elsewhere on disk, for example a file you have downloaded into a temporary folder, you can use `create_file_from_pathname`.
+
+```php title="Create a file from a file on disk"
+$fs = get_file_storage();
+
+// Create a new file containing the text 'hello world'.
+$fs->create_file_from_pathname($fileinfo, $requestdir . '/helloworld.txt');
+```
+
+#### From a URL
+
+If you need to fetch a file from a downloadable resource and store it straight into the Moodle filestore, you can use the `create_file_from_url()` function:
+
+```php title="Create a file from a file on disk"
+$fs = get_file_storage();
+
+// Create a new file containing the text 'hello world'.
+$fs->create_file_from_url($fileinfo, 'https://example.com/helloworld.txt');
+```
+
+#### From a string
+
+In some cases you may need to create a file from a string that you have generated, or retrieved in some other manner. For example, a string created from a Template, or image data which has been automatically generated.
+
+```php title="Create a file from a string"
+$fs = get_file_storage();
+
+// Create a new file containing the text 'hello world'.
+$fs->create_file_from_string($fileinfo, 'hello world');
+```
+
+#### From another `stored_file`
+
+In some situations you may need to create a new file entry based on an existing file entry. You may need to do this when copying a file between users in a group activity.
+
+```php title="Create a file from an existing file"
+$fs = get_file_storage();
+
+// Create a new file containing the text 'hello world'.
+$fs->create_file_from_storedfile($fileinfo, $existingfile);
+```
+
+### List all files in a particular file area
+
+You may need to fetch a list of all files in a specific file area. You can do this using the `file_storage::get_area_files()` API, which will return array of `stored_file` objects, for example:
+
+```php title="Fetch a list of all files in the file area"
+$fs = get_file_storage();
+
+// Returns an array of `stored_file` instances.
+$files = $fs->get_area_files($contextid, 'mod_myplugin', 'post', $post->id);
+foreach ($files as $file) {
+ // ...
+}
+```
+
+### Access the content of a file
+
+In some rare situations you may need to use the content of a file stored in the file storage API. The easiest way of doing so is using the `get_content(): string` API call:
+
+```php title="Fetch the content of a file"
+// Get file
+$fs = get_file_storage();
+$file = $fs->get_file(...);
+
+// Read contents
+$contents = $file->get_content();
+```
+
+In some situations you may instead need a standard PHP file _handle_ to the file. You can retrieve a file handle resource using the `get_content_file_handle(): resource` API call, for example:
+
+```php title="Fetch a file handle for a file"
+// Get file
+$fs = get_file_storage();
+$file = $fs->get_file(...);
+
+// Read contents
+$fh = $file->get_content_file_handle();
+
+while($line = fgets($fh)) {
+ // ...
+}
+
+fclose($fh);
+```
+
+:::important
+
+You *cannot* write to a file handle fetched using the `get_content_file_handle()` function and you _must_ call `fclose()` on the returned handle when you have finished using it.
+
+:::
+
+### Copy a file in the File Storage API to elsewhere on disk
+
+As the Moodle File Storage API prevents direct access to files on the disk, if you need a local copy of the file on disk, you must copy the file to a different location. You can use the `\stored_file::copy_content_to(string $destination);` function to achieved this, for example:
+
+```php title="Copy a file to disk"
+// Get file
+$fs = get_file_storage();
+$file = $fs->get_file(...);
+
+$requestdir = make_request_directory();
+$file->copy_content_to("{$requestdir}/helloworld.txt");
+```
+
+### Delete file
+
+You can easily remove a file from the File Storage API using the `\stored_file::delete()` function, for example:
+
+```php
+$fs = get_file_storage();
+
+$file = $fs->get_file(...);
+$file->delete();
+```
+
+### Moving and renaming files around
+
+In some instances you may need to move, or copy, files to other parts of the file structure.
+
+If a file is moving within the same context, file area, and item id, then you can use the `rename(string $path, string $filename)` function, for example:
+
+```php title="Move a file within the same file area"
+$file->rename('/newpath/', '/newname.txt');
+```
+
+If a file is to be moved to a different context, file area, or item id then you will need to create a new file from the old record, and then remove the original file, for example:
+
+```php title="Move a file to a different file area"
+$fs = get_file_storage();
+$filerecord = [
+ 'contextid' => $file->get_contextid(),
+ 'component' => $file->get_component(),
+ 'filearea' => 'newfilearea',
+ 'itemid' => 0,
+ 'filepath' => '/newpath/',
+ 'filename' => $file->get_filename(),
+ 'timecreated' => time(),
+ 'timemodified' => time(),
+];
+$fs->create_file_from_storedfile($filerecord, $file);
+
+// Now delete the original file.
+$file->delete();
+```
+
+### Convert between file formats (office documents)
+
+This functionality requires `unoconv` to be installed and configured on the site - so it is not available on all installations.
+
+```php
+$fs = get_file_storage();
+
+// Prepare file record object
+$fileinfo = [
+ 'component' => 'mod_mymodule', // Your component name.
+ 'filearea' => 'myarea', // Usually = table name.
+ 'itemid' => 0, // Usually = ID of row in table.
+ 'contextid' => $context->id, // ID of context.
+ 'filepath' => '/', // Any path beginning and ending in /.
+ 'filename' => 'myfile.txt', // Any filename.
+ ];
+
+// Fetch the file from the database.
+$file = $fs->get_file(
+ $fileinfo['contextid'],
+ $fileinfo['component'],
+ $fileinfo['filearea'],
+ $fileinfo['itemid'],
+ $fileinfo['filepath'],
+ $fileinfo['filename']
+);
+
+// Try and convert it if it exists
+if ($file) {
+ $convertedfile = $fs->get_converted_document($file, 'pdf');
+}
+```
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [File API internals](./internals.md) how the File API works internally.
+- [Using the File API in Moodle forms](../form/usage/files.md)
+- [Repository API](../../plugintypes/repository/index.md)
diff --git a/versioned_docs/version-4.5/apis/subsystems/files/internals.md b/versioned_docs/version-4.5/apis/subsystems/files/internals.md
new file mode 100644
index 0000000000..4dc6dd223a
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/files/internals.md
@@ -0,0 +1,207 @@
+---
+title: File API internals
+tags:
+ - Architecture
+ - File API
+ - Files
+ - Internals
+---
+
+The goals of the File API are to:
+
+- allow files to be stored within Moodle, as part of the content
+- use a consistent and flexible approach for all file handling throughout Moodle
+- give components control over which users can access a file, using capabilities and other local rules
+- make it easy to determine which parts of Moodle use which files, to simplify operations like backup and restore
+- track where files originally came from
+- avoid redundant storage, when the same file is used twice
+- fully support Unicode file names, irrespective of the capabilities of the underlying file system
+- support alternative file systems, including cloud-based APIs
+
+## Overview
+
+The File API is a set of core interfaces to allow the rest of Moodle to store, serve, and manage files. It applies to all files that are part of the Moodle site's content. It is not used for internal files, such as those in the following subdirectories:
+
+- `dataroot`
+- `temp`
+- `lang`
+- `cache`
+- `environment`
+- `filter`
+- `search`
+- `sessions`
+- `upgradelogs`
+
+See the [File API](./index.md) documentation for information on using the File API.
+
+The API can be subdivided into the following parts:
+
+- [*File system*](#file-system) - Low level storage of file content, without access control
+- [*File storage*](#file-storage) - Storage of file metadata
+- [*File serving*](#file-serving) - Handle the retrieval and serving of files from the File storage API, including:
+ - serving the files on request
+ - performing appropriate access checks
+- *File related user interfaces* - Provides the interface for uploading files, including:
+ - Form elements allowing users to select a file using the file picker, and have it stored within Moodle.
+ - UI for users to manage their files, replacing the old course files UI
+- *File browsing API* - Allow code to browse and optionally manipulate the file areas, including:
+ - find information about available files in each area
+ - print links to files
+ - optionally move, rename, copy, delete, and perform other user-facing operations.
+
+## File API internals
+
+### File System
+
+The File System API allows for files to be stored in alternative underlying file systems, for example in an cloud-based API such as [Amazon S3](https://aws.amazon.com/s3). Each file is stored and retrieved using a `contenthash`.
+
+The file content hash is calculated by taking a SHA1 hash of the content of the file. This should be unique enough so as to allow any number of files to be uploaded to the File API without any natural collisions occurring, and allows the File system to store a single copy of a file, no matter how many times that file content is used within user-generated content.
+
+This means Moodle can not store two files with _different_ content and the _same_ SHA1 hash, luckily it is extremely unlikely that this would ever happen. Technically it is also possible to implement reliable collision tests (with some performance cost), though Moodle currently checks for this case using a simple file length check in addition to SHA1 hash.
+
+:::info
+
+The default file system shipped with Moodle stores all files on disk within the `moodledata` sub-directory of `$CFG->dataroot`.
+
+Suppose a file has a content hash of `081371cb102fa559e81993fddc230c79205232ce`, then it will be stored in on disk as `moodledata/filedir/08/13/081371cb102fa559e81993fddc230c79205232ce`.
+
+:::
+
+:::tip Validation of files
+
+As files in the standard disk-based file storage API are named using their SHA1 hash, there is a simple way of validating files have not become corrupted using the 'sha1sum' command available in most GNU/Linux distributions.
+
+Where a file is correct then the filename will match the `sha1sum` of the file. for example:
+
+```console
+ $ cd /moodlepath/moodledata/filedir/1d/df/
+ $ sha1sum 1ddf5b375fcb74929cdd7efda4f47efc61414edf
+ 1ddf5b375fcb74929cdd7efda4f47efc61414edf 1ddf5b375fcb74929cdd7efda4f47efc61414edf
+```
+
+Where a file has become corrupted, these will differ:
+
+```console
+ $ cd /moodlepath/moodledata/filedir/42/32/
+ $ sha1sum 42327aac8ce5741f51f42be298fa63686fe81b7a
+ 9442188152c02f65267103d78167d122c87002cd 42327aac8ce5741f51f42be298fa63686fe81b7a
+```
+
+:::
+
+### File Storage
+
+The File Storage API is provided by the `\file_storage` class, and stores all metadata relating to a file. It interacts with the File System API and the `\stored_file` class to provide all low-level storage functionality.
+
+### Files table
+
+The File system API stores all file records in the `files` database table. This table contains one entry for each usage of a file. Enough information is kept here so that the file can be fully identified and retrieved again if necessary.
+
+If, for example, the same image is used in a user's profile, and a forum post, then there will be two rows in this table, one for each use of the file, and Moodle will treat the two as separate files, even though the file is only stored once on disc.
+
+Entries with a file name of `.`represent directories. Directory entries like this are created automatically when a file is added within them.
+
+:::note
+
+The name `files` is used in the plural form, even though it goes against the [coding guidelines](https://docs.moodle.org/dev/Database) because `file` is a reserved word in some SQL dialects.
+
+:::
+
+## Implementation of basic operations
+
+The low level access API is defined in the `\file_storage` class, which can be obtained using the `get_file_storage()` function, for example:
+
+```php
+$fs = get_file_storage();
+```
+
+Details of common operations are documented in the [File System API](./index.md) documentation
+
+## File serving
+
+The File serving component of the File API deals with serving files to the user. This is typically in the form of browser requests. Moodle has several main files to handle serving of files. These include:
+
+- `draftfile.php` - the script used to serve files in a user's `draft` file area.
+- `pluginfile.php` - the script typically used by a plugin to access content.
+- `tokenpluginfile.php` - the script typically used by a plugin to access content when a user is not logged in. This is usually in situations where a file is referred to in an e-mail or other similar scenario.
+
+It is the plugins responsibility to handle:
+
+- access control
+- optional XSS protection - student submitted files must not be served with normal headers, we have to force download instead; ideally there should be second wwwroot for serving of untrusted files
+- links to these files are constructed on the fly from the relative links stored in database (see [Generating a URL to your files](./index.md#generating-a-url-to-your-files) for further information).
+
+:::important
+
+Each plugin should only ever use the File Storage API to access its __own files__.
+
+:::
+
+## File related user interfaces
+
+Files are typically selected by users and uploaded to Moodle using the File manager, and the file picker.
+
+- The **file manager** is an interface used to view, and delete existing files, and to add new files.
+- The **file picker** is an interface, often accessed using the _file manager_, to select files for upload to Moodle. The file picker makes use of [file repositories](../../plugintypes/repository/index.md).
+
+### Form fields
+
+Moodle defines two form field types as part of the `formslib` integration, these are:
+
+- The `filepicker`; and
+- the `filemanager`.
+
+### Integration with the HTML editor
+
+Each instance of an HTML editor can be told to store related files in a particular file area.
+
+During editing, files are stored in a draft files area in the `user` component. Then when the form is submitted they are moved into the real file area.
+
+## Other issues
+
+### Unicode support in zip format
+
+Zip format is an old standard for compressing files. It was created long before Unicode existed, and Unicode support was only recently added. There are several ways used for encoding of non-ASCII characters in path names, but unfortunately it is not very standardised. Most Windows packers use DOS encoding.
+
+#### Client software
+
+- Windows built-in compression - bundled with Windows, non-standard DOS encoding only
+- WinZip - shareware, Unicode option (since v11.2)
+- TotalCommander - shareware, single byte(DOS) encoding only
+- 7-Zip - free, Unicode or DOS encoding depending on characters used in file name (since v4.58beta)
+- Info-ZIP - free, uses some weird character set conversions
+
+#### PHP extraction
+
+- Info-ZIP binary execution - no Unicode support at all, mangles character sets in file names (depends on OS, see docs), files must be copied to temp directory before compression and after extraction
+- PclZip PHP library - reads single byte encoded names only, problems with random problems and higher memory usage.
+- Zip PHP extension - kind of works in latest PHP versions
+
+#### Large file support
+
+- PHP running under 32bit operating systems does not support files >2GB. This might be a potential problem for larger backups.
+
+#### Tar Alternative
+
+- tar with gzip compression - easy to implement in PHP + zlib extension (PclTar, Tar from PEAR or custom code)
+- no problem with unicode in *nix, Windows again expects DOS encoding :-(
+- seems suitable for backup/restore - yay!
+
+#### Summary
+
+1. added zip processing class that fully hides the underlying library
+1. using single byte encoding "garbage in/garbage out" approach for encoding of files in zip archives; add new `zipencoding` string into lang packs (for example `cp852` DOS charset for Czech locale) and use it during extraction (we might support true unicode later when PHP Zip extension does that)
+
+### Tar packer
+
+In addition to the zip packer, a tar packer is also available. This creates
+archives in a compressed tar format (similar to the file created by `tar -czf example.tar.gz mycontent`).
+
+The packer is currently limited to ASCII filenames and individual files are limited to 8GB each, but unlike zip there is no limit on the total filesize. It uses the old POSIX format and is compatible with GNU tar using default options.
+
+## See also
+
+- [Repository API](../../plugintypes/repository/index.md)
+- [Portfolio API](https://docs.moodle.org/dev/Portfolio_API)
+- [Resource module file API migration](https://docs.moodle.org/dev/Resource_module_file_API_migration)
+- [MDL-14589](https://tracker.moodle.org/browse/MDL-14589) - File API Meta issue
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/advanced/_category_.yml b/versioned_docs/version-4.5/apis/subsystems/form/advanced/_category_.yml
new file mode 100644
index 0000000000..b640140c1b
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/advanced/_category_.yml
@@ -0,0 +1 @@
+label: Advanced features
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/advanced/advanced-elements.md b/versioned_docs/version-4.5/apis/subsystems/form/advanced/advanced-elements.md
new file mode 100644
index 0000000000..423cffea87
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/advanced/advanced-elements.md
@@ -0,0 +1,94 @@
+---
+title: Advanced elements
+tags:
+ - core_form
+ - core
+ - Forms API
+ - Advanced
+---
+
+Form elements can be marked as 'advanced'. This has the effect that they are hidden initially.
+
+To control whether an element is advanced, you can use the `setAdvanced` method on the MoodleQuickForm instance and set a specific element as being advanced, for example:
+
+```php title="Marking an element as advanced"
+$mform->addElement(
+ 'select',
+ 'display',
+ get_string('displaymode', 'choice'),
+ $CHOICE_DISPLAY
+);
+$mform->setAdvanced('display');
+```
+
+It is also possible to unset the advanced status of a field - the `setAdvanced` function takes a second, boolean, parameter which defaults to `true`. By passing a `false` value, the element's advanced flag will be removed, for example:
+
+```php title="Marking an element as advanced"
+// Mark a field as not advanced after it was previously marked as advanced.
+$mform->setAdvanced('display', false);
+```
+
+:::warning
+
+You should be careful about marking too many elements as advanced.
+
+For more information on the risks of this, see the advice in [Designing usable forms](/general/development/policies/designing-usable-forms#use-show-moreless-advanced-settings-sparingly).
+
+:::
+
+:::info Location of Show and hide links
+
+Whenever you mark a form element as advanced, then the _Show / hide advanced_ links are shown automatically at relevant points within the form.
+
+The _Show advanced_ and _Hide advanced_ links are currently displayed at the top right of all fieldsets containing advanced controls.
+
+:::
+
+### Setting a name
+
+When adding a header element, the second parameter to `addElement()` is a name field. You should pass a _unique_ name for each header.
+
+If the name is not specified, or is not unique then this can have a range of unintended impacts, including marking multiple sections of the form as advanced. It is strongly encouraged to _always_ name headers.
+
+
+
+```php title="An empty name is passed to these headings"
+$mform->addElement(
+ 'header',
+ '',
+ get_string('miscellaneoussettings', 'form')
+);
+$mform->addElement(
+ 'select',
+ 'display',
+ get_string('displaymode', 'choice'),
+ $CHOICE_DISPLAY
+);
+$mform->setAdvanced('display');
+
+// Because this section has a non-unique name (an empty string),
+// the advanced flag set for the display element in the previous
+// section will also apply here.
+$mform->addElement(
+ 'header',
+ '',
+ get_string('anothersection', 'form')
+);
+```
+
+
+
+## Marking an entire section as advanced
+
+The `setAdvanced` function can mark an entire section as advanced by specifying the name of the header at the top of the section, for example:
+
+```php title="Marking an entire section as advanced"
+$mform->addElement(
+ 'header',
+ 'miscellaneoussettingshdr',
+ get_string('miscellaneoussettings', 'form')
+);
+$mform->setAdvanced('miscellaneoussettingshdr');
+```
+
+In this example, all fields from this header until the next header will be marked as advanced.
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/advanced/checkbox-controller.md b/versioned_docs/version-4.5/apis/subsystems/form/advanced/checkbox-controller.md
new file mode 100644
index 0000000000..f9890a3e58
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/advanced/checkbox-controller.md
@@ -0,0 +1,74 @@
+---
+title: Checkbox controller
+tags:
+ - core_form
+ - core
+ - Forms API
+ - Advanced
+---
+
+The checkbox controller allows developers to group checkboxes together and add a link or button to check, or uncheck, all of the checkboxes at once.
+
+You can add as many groups of checkboxes as you like, as long as they are uniquely named, where the name is an integer.
+
+## Checkbox groups
+
+When adding checkboxes, you can add them in _groups_. Each group of checkboxes must have a unique integer name, for example:
+
+```php title="classes/form/example_form.php"
+public function definition(): void {
+ // These two elements are part of group 1.
+ $mform->addElement('advcheckbox', 'test1', 'Test 1', null, ['group' => 1]);
+ $mform->addElement('advcheckbox', 'test2', 'Test 2', null, ['group' => 1]);
+
+ // Add a checkbox controller for all checkboxes in `group => 1`:
+ $this->add_checkbox_controller(1);
+
+ // These two elements are part of group 3.
+ $mform->addElement('advcheckbox', 'test3', 'Test 3', null, ['group' => 3]);
+ $mform->addElement('advcheckbox', 'test4', 'Test 4', null, ['group' => 3]);
+
+ // Add a checkbox controller for all checkboxes in `group => 3`.
+ // This example uses a different wording isntead of Select all/none by passing the second parameter:
+ $this->add_checkbox_controller(
+ 3,
+ get_string("checkall", "plugintype_pluginname")
+ );
+}
+```
+
+## API
+
+The checkbox controller is described by the following mform function:
+
+```php
+moodleform::add_checkbox_controller(
+ $groupid = null,
+ string $text = null,
+ mixed $attributes = null,
+ int $originalvalue
+): void;
+```
+
+- int *$groupid* This also serves as the checkbox group name. It must be a unique integer per collection of checkboxes.
+- string *$text* (optional) Link display text. Defaults to `get_string('selectallornone', 'form')`.
+- mixed *$attributes* (optional) Either a typical HTML attribute string or an associative array.
+- int *$originalValue* (optional) Defaults to 0; The general original value of the checkboxes being controlled by this element.
+
+:::info An explanation of `$originalvalue`
+
+Imagine that you have 50 checkboxes in your form, which are all unchecked when the form first loads, except 5 or 6 of them.
+
+The logical choice here would be for the standard behaviour to check all of the checkboxes upon first click, then to uncheck them all upon the next click and so on.
+
+If the situation was reversed, with most of the checkboxes already checked by default, then it would make more sense to have the first action uncheck all the checkboxes.
+
+The `$originalvalue` parameter allows you to configure the initial value and therefore the initial behaviour.
+
+:::
+
+### Description of functionality
+
+The first role of the `add_checkbox_controller` method is to add a form element. Depending on whether JavaScript is supported by the browser or not, it will either output a link with onclick behaviour for instant action, or a `nosubmit` button which will reload the page and change the state of the checkboxes, but retain the rest of the data already filled in the form by the user.
+
+The second role is to change the state of the checkboxes. The JavaScript version simply switches all checkboxes to checked or unchecked. The state applied when the link is first clicked depends on the `$originalvalue` parameter as noted above. The non-JavaScript version behaves in exactly the same way, although a page reload is necessary.
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/advanced/no-submit-button.md b/versioned_docs/version-4.5/apis/subsystems/form/advanced/no-submit-button.md
new file mode 100644
index 0000000000..2744b69b20
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/advanced/no-submit-button.md
@@ -0,0 +1,54 @@
+---
+title: No submit button
+tags:
+ - core_form
+ - core
+ - Forms API
+ - Advanced
+---
+
+The moodleform 'no_submit_button_pressed()' method allows you to detect if a button on your form has been pressed that is a submit button but that has been defined as a button that doesn't result in a processing of all the form data but will result in some form 'sub action' and then having the form redisplayed. This is useful for example to have an 'Add' button to add some option to a select box in the form etc. You define a button as a no submit button as in the example below (in `definition()`). This example adds a text box and a submit button in a group.
+
+## Form definition
+
+When defining your form, you will need to call the `registerNoSubmitButton()` function with the name of the submit button mark as a non-submission button, for example:
+
+```php
+$mform->registerNoSubmitButton('addtags');
+$tags = [
+ $mform->createElement('text', 'tagsadd', get_string('addtags', 'blog')),
+ $mform->createElement('submit', 'addtags', get_string('add')),
+];
+$mform->addGroup($tags, 'tagsgroup', get_string('addtags','blog'), [' '], false);
+$mform->setType('tagsadd', PARAM_NOTAGS);
+```
+
+## Form handling
+
+When handling a no-submit button press you will need to check whether any no-submit button as pressed _before_ checking for submitted data. For example:
+
+```php
+$mform = new \plugintype_pluginname\form\my_form();
+$mform->set_data($toform);
+
+if ($mform->is_cancelled()){
+ // If you have a cancel button on your form then you will need
+ // to handle the cancel action here according to the
+ // requirements of your plugin
+} else if ($mform->no_submit_button_pressed()) {
+ // If you have a no-submit button on your form, then you can handle that action here.
+ $data = $mform->get_submitted_data();
+} else if ($fromform = $mform->data_submitted()){
+ // This branch is where you process validated data.
+} else {
+ // The form has not been submitted, or was unsuccessfully submitted.
+}
+```
+
+:::important Modifying your form in a no-submit action
+
+Because the check for a no-submit button press takes place on the defined form, the form is already defined at the point that it is checked.
+
+If you wish to add or modify elements or options in your form, then may need to use methods on MoodleQuickForm to redefine your form elements.
+
+:::
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/advanced/repeat-elements.md b/versioned_docs/version-4.5/apis/subsystems/form/advanced/repeat-elements.md
new file mode 100644
index 0000000000..116718e145
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/advanced/repeat-elements.md
@@ -0,0 +1,109 @@
+---
+title: Repeat elements
+tags:
+ - core_form
+ - core
+ - Forms API
+ - Advanced
+---
+
+The Form API includes the ability to repeat a group of form elements. This is useful where you need to have an unknown quantity of item data, for example possible answers in a quiz question.
+
+This is achieved by adding a button to the form to handle the creation of the additional buttons using a page reload, and a zero-indexed array added to the `elementname` data returned by `get_data()`.
+
+## Overview
+
+Most of the necessary information is in the phpdoc comment for the repeat_elements() method:
+
+```php
+/**
+ * Method to add a repeating group of elements to a form.
+ *
+ * @param array $elementobjs Array of elements or groups of elements that are to be repeated
+ * @param int $repeats no of times to repeat elements initially
+ * @param array $options a nested array. The first array key is the element name.
+ * the second array key is the type of option to set, and depend on that option,
+ * the value takes different forms.
+ * 'default' - default value to set. Can include '{no}' which is replaced by the repeat number.
+ * 'type' - PARAM_* type.
+ * 'helpbutton' - array containing the helpbutton params.
+ * 'disabledif' - array containing the disabledIf() arguments after the element name.
+ * 'rule' - array containing the addRule arguments after the element name.
+ * 'expanded' - whether this section of the form should be expanded by default. (Name be a header element.)
+ * 'advanced' - whether this element is hidden by 'Show more ...'.
+ * @param string $repeathiddenname name for hidden element storing no of repeats in this form
+ * @param string $addfieldsname name for button to add more fields
+ * @param int $addfieldsno how many fields to add at a time
+ * @param string $addstring name of button, {no} is replaced by no of blanks that will be added.
+ * @param bool $addbuttoninside if true, don't call closeHeaderBefore($addfieldsname). Default false.
+ * @param string $deletebuttonname if specified, treats the no-submit button with this name as a "delete element" button in each of the elements.
+ * @return int no of repeats of element in this page
+ */
+```
+
+## Configuration
+
+- The list of elements you wish to repeat is set in the `$elementobjs` array, with any options passed into the `$options` array.
+A `{no}` placeholder can be placed into strings, such as the element label or default values, to represent the item number.
+
+:::note
+
+While the elements are zero-indexed, the `{no}` label is one-indexed.
+
+:::
+
+- The number of repeats to show initially can be configured using the `$repeats` parameter.
+- The number of elements to add when adding more options is configured using the `$addfieldsno` parameter.
+- The label used for the 'Add more' button can be set using the `$addstring` parameter. A `{no}` placeholder can be used in the string to indicate how many repeats will be added.
+- The number of element repeats currently shown is stored in a hidden element, whose name can be specified using the `$repeathiddenname` parameter.
+
+The following example shows how `repeat_elements()` can be used within a form definition with a delete button for each repeated field :
+
+```php title="definition() function"
+$repeatarray = [
+ $mform->createElement('text', 'option', get_string('optionno', 'choice')),
+ $mform->createElement('text', 'limit', get_string('limitno', 'choice')),
+ $mform->createElement('hidden', 'optionid', 0),
+ $mform->createElement('submit', 'delete', get_string('deletestr', 'choice'), [], false),
+];
+
+
+if ($this->_instance){
+ $repeatno = $DB->count_records('choice_options', ['choiceid' => $this->_instance]);
+ $repeatno += 2;
+} else {
+ $repeatno = 5;
+}
+
+$repeateloptions = [
+ 'limit' => [
+ 'default' => 0,
+ 'disabledif' => array('limitanswers', 'eq', 0),
+ 'rule' => 'numeric',
+ 'type' => PARAM_INT,
+ ],
+ 'option' => [
+ 'helpbutton' => [
+ 'choiceoptions',
+ 'choce',
+ ]
+ ]
+];
+
+$mform->setType('option', PARAM_CLEANHTML);
+$mform->setType('optionid', PARAM_INT);
+
+$this->repeat_elements(
+ $repeatarray,
+ $repeatno,
+ $repeateloptions,
+ 'option_repeats',
+ 'option_add_fields',
+ 3,
+ null,
+ true,
+ 'delete',
+);
+```
+
+For other examples, have a look at the question type editing forms. They make extensive use of repeat_elements().
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/fields/_category_.yml b/versioned_docs/version-4.5/apis/subsystems/form/fields/_category_.yml
new file mode 100644
index 0000000000..5f7a811318
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/fields/_category_.yml
@@ -0,0 +1 @@
+label: Form fields
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/fields/choicedropdown.md b/versioned_docs/version-4.5/apis/subsystems/form/fields/choicedropdown.md
new file mode 100644
index 0000000000..bd23fa0988
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/fields/choicedropdown.md
@@ -0,0 +1,60 @@
+---
+title: Choice Dropdown
+tags:
+- core_form
+- form
+---
+
+The `choicedropdown` form field creates a dropdown list with multiple options. It is different from a standard select dropdown in that each option can have extra icons and descriptions. This field is often used in forms to facilitate the selection of a non-trivial option that demands additional information.
+
+## Using the `choicedropdown` Form Field
+
+While most form API fields use primitive data types, the `choicedropdown` form field uses a particular data definition called `choicelist`. This data definition is an abstraction that represents a user choice list and is used in other UI components like `core\output\local\dropdown\status` or `core\output\local\action_menu\subpanel`.
+
+The `choicelist` class provides a way to define a list of options with additional information such as icons, descriptions, the currently selected option, or the ability to disable specific options.
+
+## Example Usage
+
+To create a `choicedropdown` form field, you need to:
+
+1. Create a new instance of the `choicelist` class.
+1. Add options and any extra information to the `choicelist` instance.
+1. Add the `choicedropdown` form field to the form, passing the `choicelist` instance.
+
+```php
+// Define the options for the dropdown list.
+$options = new core\output\choicelist();
+$options->add_option(
+ 'option1',
+ "Text option 1",
+ [
+ 'description' => 'Option 1 description',
+ 'icon' => new pix_icon('t/hide', 'Eye icon 1'),
+ ]
+);
+$options->add_option(
+ 'option2',
+ "Text option 2",
+ [
+ 'description' => 'Option 2 description',
+ 'icon' => new pix_icon('t/stealth', 'Eye icon 2'),
+ ]
+);
+$options->add_option(
+ 'option3',
+ "Text option 3",
+ [
+ 'description' => 'Option 3 description',
+ 'icon' => new pix_icon('t/show', 'Eye icon 3'),
+ ]
+);
+
+// Add the choicedropdown field to the form.
+$mform->addElement(
+ 'choicedropdown',
+ 'FIELDNAME',
+ get_string('FIELDNAME', 'PLUGINNAME'),
+ $options,
+);
+$mform->setDefault('FIELDNAME', $defaultvalue);
+```
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/index.md b/versioned_docs/version-4.5/apis/subsystems/form/index.md
new file mode 100644
index 0000000000..1df2b9b8e7
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/index.md
@@ -0,0 +1,389 @@
+---
+title: Forms API
+tags:
+ - API
+ - core_form
+ - form
+ - core
+---
+
+Form are created using the Form API. The Form API supports most standard HTML elements, including checkboxes, radio buttons, text boxes, and so on, adding additional accessibility and security features to them.
+
+## Highlights
+
+- Tested and optimised for use on major screen-readers like Dragon and JAWS.
+- Table-less layout.
+- Processes form data securely, with `required_param`, `optional_param`, and session key.
+- Supports client-side validation.
+- Facility to add Moodle help buttons to forms.
+- Support for file repository using the [File API](../files/index.md) .
+- Support for many custom Moodle specific and non-specific form elements.
+- Facility for [repeated elements](./advanced/repeat-elements.md).
+- Facility for form elements in advanced groups
+
+## Usage
+
+The Moodle forms API separates forms into different areas:
+
+1. a form definition, which extends the `moodleform` class; and
+2. uses of that form.
+
+To create a form in Moodle, you create a class that defines the form, including every form element. Your class must extend the `moodleform` class and overrides the [definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#definition.28.29) member function to describe the form elements.
+
+
+An example of a form definition
+
+```php title="[path/to/plugin]/classes/form/myform.php"
+// moodleform is defined in formslib.php
+require_once("$CFG->libdir/formslib.php");
+
+class simplehtml_form extends moodleform {
+ // Add elements to form.
+ public function definition() {
+ // A reference to the form is stored in $this->form.
+ // A common convention is to store it in a variable, such as `$mform`.
+ $mform = $this->_form; // Don't forget the underscore!
+
+ // Add elements to your form.
+ $mform->addElement('text', 'email', get_string('email'));
+
+ // Set type of element.
+ $mform->setType('email', PARAM_NOTAGS);
+
+ // Default value.
+ $mform->setDefault('email', 'Please enter email');
+ }
+
+ // Custom validation should be added here.
+ function validation($data, $files) {
+ return [];
+ }
+}
+```
+
+
+
+Once the form has been defined it can be instantiated elsewhere in Moodle, for example:
+
+```php title="[path/to/plugin]/myform.php
+
+// Instantiate the myform form from within the plugin.
+$mform = new \plugintype_pluginname\form\myform();
+
+// Form processing and displaying is done here.
+if ($mform->is_cancelled()) {
+ // If there is a cancel element on the form, and it was pressed,
+ // then the `is_cancelled()` function will return true.
+ // You can handle the cancel operation here.
+} else if ($fromform = $mform->get_data()) {
+ // When the form is submitted, and the data is successfully validated,
+ // the `get_data()` function will return the data posted in the form.
+} else {
+ // This branch is executed if the form is submitted but the data doesn't
+ // validate and the form should be redisplayed or on the first display of the form.
+
+ // Set anydefault data (if any).
+ $mform->set_data($toform);
+
+ // Display the form.
+ $mform->display();
+}
+```
+
+If you wish to use the form within a block then you should consider using the render method, as demonstrated below:
+
+Note that the render method does the same as the display method, except returning the HTML rather than outputting it to the browser, as with above make sure you've included the file which contains the class for your Moodle form.
+
+```php
+class block_yourblock extends block_base {
+ public function init(){
+ $this->title = 'Your Block';
+ }
+
+ public function get_content(){
+ $this->content = (object) [
+ 'text' => '',
+ ];
+
+ $mform = new \plugintype_pluginname\form\myform();
+
+ if ($mform->is_cancelled()) {
+ // If there is a cancel element on the form, and it was pressed,
+ // then the `is_cancelled()` function will return true.
+ // You can handle the cancel operation here.
+ } else if ($fromform = $mform->get_data()) {
+ // When the form is submitted, and the data is successfully validated,
+ // the `get_data()` function will return the data posted in the form.
+ } else {
+ // This branch is executed if the form is submitted but the data doesn't
+ // validate and the form should be redisplayed or on the first display of the form.
+
+ // Set anydefault data (if any).
+ $mform->set_data($toform);
+
+ // Display the form.
+ $this->content->text = $mform->render();
+ }
+
+ return $this->content;
+ }
+}
+```
+
+## Form elements
+
+Moodle provides a number of basic, and advanced, form elements. These are described in more detail below.
+
+### Basic form elements
+
+1. [button](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#button)
+1. [checkbox](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#checkbox)
+1. [radio](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#radio)
+1. [select](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#select)
+1. [multi-select](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#multi-select)
+1. [password](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#password)
+1. [hidden](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hidden)
+1. [html](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#html) - div element
+1. [static](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#static) - Display a static text.
+1. [text](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text)
+1. [textarea](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#textarea)
+1. [header](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements)
+
+### Advanced form elements
+
+
+
+
+
+
+1. [Autocomplete](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#autocomplete) - A select box that allows you to start typing to narrow the list of options, or search for results.
+1. [advcheckbox](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#advcheckbox) - Advance checkbox
+1. [choicedropdown](./form/fields/choicedropdown) - A dropdown menu where custom information is displayed on each option.
+1. [float](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#float)
+1. [passwordunmask](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#passwordunmask) - A password element with option to show the password in plaintext.
+1. [recaptcha](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#recaptcha)
+1. [selectyesno](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectyesno)
+1. [selectwithlink](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectwithlink)
+1. [date_selector](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_selector)
+1. [date_time_selector](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_time_selector)
+1. [duration](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#duration)
+1. [editor](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#editor)
+1. [filepicker](./usage/files.md#file-picker) - upload single file
+1. [filemanager](./usage/files.md#file-manager) - upload multiple files
+1. [tags](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#tags)
+1. [addGroup](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addGroup)
+1. [modgrade](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modgrade)
+1. [modvisible](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modvisible)
+1. [choosecoursefile](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#choosecoursefile)
+1. [grading](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#grading)
+1. [questioncategory](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#questioncategory)
+
+### Custom form elements
+
+In addition to the standard form elements, you can register your own custom form elements, for example:
+
+```php
+// Register a custom form element.
+MoodleQuickForm::registerElementType(
+ // The custom element is named `course_competency_rule`.
+ // This is the element name used in the `addElement()` function.
+ 'course_competency_rule',
+
+ // This is where it's definition is defined.
+ // This does not currently support class auto-loading.
+ "$CFG->dirroot/$CFG->admin/tool/lp/classes/course_competency_rule_form_element.php",
+
+ // The class name of the element.
+ 'tool_lp_course_competency_rule_form_element'
+);
+
+// Add an instance of the custom form element to your form.
+$mform->addElement(
+ // The name of the custome lement.
+ 'course_competency_rule',
+ 'competency_rule',
+ get_string('uponcoursemodulecompletion', 'tool_lp'),
+ $options
+);
+```
+
+For a real-life example, see:
+
+- [Custom element definition](https://github.com/moodle/moodle/blob/main/admin/tool/lp/classes/course_competency_rule_form_element.php)
+- [Custom element usage](https://github.com/moodle/moodle/blob/main/admin/tool/lp/lib.php#L157-L161)
+
+## Commonly used functions
+
+### add_action_buttons()
+
+Add the standard 'action' buttons to the form - these are the standard Submit, and Cancel buttons on the form.
+
+```php
+public function add_action_buttons(
+ bool $cancel = true,
+ ?string $submitlabel = null
+);
+```
+
+- The `$cancel` parameter can be used to control whether a cancel button is shown.
+- The `$submitlabel` parameter can be used to set the label for the submit button. The default value comes from the `savechanges` string.
+
+:::important
+
+The `add_action_buttons` function is defined on `moodlform` class, and not a part of `$this->_form`, for example:
+
+```php
+ public function definition() {
+ // Add your form elements here.
+ $this->_form->addElement(...);
+
+ // When ready, add your action buttons.
+ $this->add_action_buttons();
+ }
+```
+
+:::
+
+### add_sticky_action_buttons()
+
+
+
+This method calls `add_action_buttons()` internally and makes 'action' buttons sticky.
+
+```php
+public function add_sticky_action_buttons(
+ bool $cancel = true,
+ ?string $submitlabel = null
+);
+```
+
+### setDefault()
+
+The [setDefault()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#setDefault_2) function can be used to set the default value for an element.
+
+### disabledIf()
+
+The [disabledIf()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#disabledIf) function can be used to conditionally _disable_ a group of elements, or and individual element depending on the state of other form elements.
+
+### hideIf()
+
+
+
+The [hideif()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hideIf) function can be used to conditionally _hide_ a group of elements, or and individual element depending on the state of other form elements.
+
+### addRule()
+
+The [addRule()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addRule) function can be used to define both client-side, and server-side validation rules. For example, this can be used to validate that a text-field is required, and has a type of email.
+
+### addHelpButton()
+
+The [addHelpButton()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addHelpButton) function can be used to add a pop-up help button to a form element.
+
+### setType()
+
+The [setType()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#setType) function can be used to specify how submitted values are cleaned. The `PARAM_*` constants are used to specify the type of data that will be submitted.
+
+### disable_form_change_checker()
+
+Normally, if a user navigate away from any form and changes have been made, a popup will be shown to the user asking them to confirm that they wish to leave the page and that they may lose data.
+
+In some cases this is not the desired behaviour, in which case the [disable_form_change_checker()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#disable_form_change_checker) function can be used to disable the form change checker.
+
+For example:
+
+```php
+public function definition() {
+ // Your definition goes here.
+
+ // Disable the form change checker for this form.
+ $this->_form->disable_form_change_checker();
+}
+```
+
+### filter_shown_headers()
+
+
+
+This method adds values to `_shownonlyelements` array to decide whether a header should be shown or hidden.
+Only header names would be accepted and added to `_shownonlyelements` array.
+Headers included in `_shownonlyelements` will be shown expanded in the form. The rest of the headers will be hidden.
+
+```php
+public function filter_shown_headers(array $shownonly): void {
+ $this->_shownonlyelements = [];
+ if (empty($shownonly)) {
+ return;
+ }
+ foreach ($shownonly as $headername) {
+ $element = $this->getElement($headername);
+ if ($element->getType() == 'header') {
+ $this->_shownonlyelements[] = $headername;
+ $this->setExpanded($headername);
+ }
+ }
+}
+```
+
+Empty `_shownonlyelements` array doesn't affect header's status or visibility.
+
+```php title="/course/editsection.php"
+$showonly = optional_param('showonly', 0, PARAM_TAGLIST);
+
+[...]
+
+$mform = $courseformat->editsection_form($PAGE->url, $customdata);
+
+$initialdata = convert_to_array($sectioninfo);
+if (!empty($CFG->enableavailability)) {
+ $initialdata['availabilityconditionsjson'] = $sectioninfo->availability;
+}
+$mform->set_data($initialdata);
+if (!empty($showonly)) {
+ $mform->filter_shown_headers(explode(',', $showonly));
+}
+```
+
+### Other features
+
+In some cases you may want to [group elements](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements) into collections.
+
+## Unit testing
+
+In order to test the processing of submitted form contents in unit tests, the Forms API has a `mock_submit()` function.
+
+This method makes the form behave as if the data supplied to it was submitted by the user via the web interface. The data still passes through all form validation, which means that `get_data() will return all of the parsed values, along with any defaults.
+
+
+Example usage
+
+```php
+// Instantiate a form to submit.
+$form = new qtype_multichoice_edit_form(...);
+
+// Fetch the data and then mock the submission of that data.
+$questiondata = test_question_maker::get_question_data('multichoice', 'single');
+$form->mock_submit($questiondata);
+
+// The `get_data()` function will return the validated data, plus any defaults.
+$actualfromform = $form->get_data();
+
+// The resultant data can now be tested against the expected values.
+$expectedfromform = test_question_maker::get_question_form_data('multichoice', 'single');
+$this->assertEquals($expectedfromform, $actualfromform);
+
+// The data can also be saved and tested in the context of the API.
+save_question($actualfromform);
+$actualquestiondata = question_load_questions(array($actualfromform->id));
+$this->assertEquals($questiondata, $actualquestiondata);
+```
+
+
+
+## See also
+
+- [Core APIs](../../../apis.md)
+- [lib/formslib.php Usage](./usage/index.md)
+- [lib/formslib.php Form Definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition)
+- [Designing usable forms](/general/development/policies/designing-usable-forms)
+- [Fragment](https://docs.moodle.org/dev/Fragment)
+- [MForm Modal](https://docs.moodle.org/dev/MForm_Modal)
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/usage/files.md b/versioned_docs/version-4.5/apis/subsystems/form/usage/files.md
new file mode 100644
index 0000000000..9468d39ed7
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/usage/files.md
@@ -0,0 +1,441 @@
+---
+title: Files in Forms
+tags:
+ - Files
+ - Repositories
+---
+
+Files and their metadata are stored within the [File API](../../files/index.md). Each file within the API is associated with:
+
+- a context id;
+- a component;
+- a _file area_; and
+- an optional item id.
+
+This combination acts as a virtual bucket, within which any desired file structure may be used.
+
+A common use case is to allow users to upload file content within a standard Moodle form.
+
+Normally this works as follows:
+
+1. User starts to create, or re-edit an existing item - this may be a forum post, resource, glossary entry, and so on.
+1. User presses some sort of button to browse for new files to attach or embed
+1. User sees our "Choose file..." dialog, which contains one or more repository instances.
+1. User chooses a file, the corresponding [Repository plugin](../../../plugintypes/repository/index.md) takes care of copying the file into a "draft file area" within Moodle
+1. File appears in the text or as an attachment in the form.
+1. When the user hits save, the [File API](../../files/index.md) is invoked to move the file from the draft file area into a permanent file area associated with that data
+
+This document shows you how to use Moodle forms to interact with users in a standard and secure way.
+
+If you just want to write code to manipulate Moodle files internally (without user input) you can use the [File API](../../files/index.md) without involving form elements.
+
+## Form elements
+
+There are three file-related form elements for interacting with users:
+
+1. `filepicker` - a way to specify one file for the case when you want to process the file and throw it away
+1. `filemanager` - a way to attach one or more files as a collection, using the file picker interface
+1. `editor` - a way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML
+
+### File picker
+
+The _File picker_ may be used directly to allow a user to upload _one_ file so that it can be processed, and then removed.
+
+Example use-cases include allowing a single file to be uploaded for a purpose such as CSV import, or restoration of a backup.
+
+:::note
+
+A filepicker element does not typically support storing data within the File API.
+
+If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).
+
+:::
+
+#### Using the filepicker element
+
+```php
+$mform->addElement(
+ 'filepicker',
+ 'userfile',
+ get_string('file'),
+ null,
+ [
+ 'maxbytes' => $maxbytes,
+ 'accepted_types' => '*',
+ ]
+);
+```
+
+#### Working with an uploaded file
+
+To get the contents of the uploaded file:
+
+```php
+$content = $mform->get_file_content('userfile');
+```
+
+To get the name of the uploaded file:
+
+```php
+$name = $mform->get_new_filename('userfile');
+```
+
+To save the uploaded file to the server filesystem:
+
+```php
+$success = $mform->save_file('userfile', $fullpath, $override);
+```
+
+:::note
+
+Be wary of this approach if you need to use the file in multiple page requests.
+
+A clustered server installation will need the file to be accessible on any server node.
+
+:::
+
+To store the chosen file in the Moodle file API:
+
+```php
+$storedfile = $mform->save_stored_file('userfile', ...);
+```
+
+### File manager
+
+The File Manager element improves on file picker by allowing you to manage more than one file. It is expected that the files will be stored permanently for future use.
+
+Examples of the File manager can be found all over Moodle and include the Forum, and Glossary attachments, Course files, and more.
+
+#### Add a file manager element
+
+Example:
+
+```php
+$mform->addElement(
+ 'filemanager',
+ 'attachments',
+ get_string('attachment', 'moodle'),
+ null,
+ [
+ 'subdirs' => 0,
+ 'maxbytes' => $maxbytes,
+ 'areamaxbytes' => 10485760,
+ 'maxfiles' => 50,
+ 'accepted_types' => ['document'],
+ 'return_types' => FILE_INTERNAL | FILE_EXTERNAL,
+ ]
+);
+```
+
+When a user uploads files, these are stored into a _draft_ file area for that user. It is the developers responsibility to then move those files within the File API.
+
+#### Loading existing files into draft area
+
+If you are presenting a form which has previously had data saved to it, for example when editing an existing piece of content, you will need to copy all of the existing files into the draft file area used in the form. This can be achieved using the `file_prepare_draft_area` function, for example:
+
+```php
+// Fetch the entry being edited, or create a placeholder.
+if (empty($id)) {
+ $entry = (object) [
+ 'id' => null,
+ ];
+} else {
+ $entry = $DB->get_records('glossary_entries', ['id' => $id]);
+}
+
+// Get an unused draft itemid which will be used for this form.
+$draftitemid = file_get_submitted_draft_itemid('attachments');
+
+// Copy the existing files which were previously uploaded
+// into the draft area used by this form.
+file_prepare_draft_area(
+ // The $draftitemid is the target location.
+ $draftitemid,
+
+ // The combination of contextid / component / filearea / itemid
+ // form the virtual bucket that files are currently stored in
+ // and will be copied from.
+ $context->id,
+ 'mod_glossary',
+ 'attachment',
+ $entry->id,
+ [
+ 'subdirs' => 0,
+ 'maxbytes' => $maxbytes,
+ 'maxfiles' => 50,
+ ]
+);
+
+// Set the itemid of draft area that the files have been moved to.
+$entry->attachments = $draftitemid;
+$mform->set_data($entry);
+```
+
+#### Store updated set of files
+
+During the processing of the submitted data, the developer handling the form will need to handle storing the files in an appropriate part of the File API.
+
+This can be accomplished using the `file_save_draft_area_files()` function, for example:
+
+```php
+if ($data = $mform->get_data()) {
+ // ... store or update $entry.
+
+ // Now save the files in correct part of the File API.
+ file_save_draft_area_files(
+ // The $data->attachments property contains the itemid of the draft file area.
+ $data->attachments,
+
+ // The combination of contextid / component / filearea / itemid
+ // form the virtual bucket that file are stored in.
+ $context->id,
+ 'mod_glossary',
+ 'attachment',
+ $entry->id,
+
+ [
+ 'subdirs' => 0,
+ 'maxbytes' => $maxbytes,
+ 'maxfiles' => 50,
+ ]
+ );
+}
+```
+
+### Editors
+
+Another common place to handle files is within an HTML editor, such as TinyMCE.
+
+There are two ways of using the editor element in code, the first one is easier but expects some standardised fields. The second method is more low level.
+
+All of the methods share key behaviours:
+
+- When preparing the editor:
+ - You must create a draft file area to store the files while the user is making changes.
+- When using the editor:
+ - Any files referenced use a full URL, for example `https://example.com/pluginfile.php/123/user/icon/456/filedir/filename.png`.
+ - These files are stored in the draft file area.
+- When processing the form submission:
+ - You must process the content so that part of the URL is replaced with a placeholder - usually `@@PLUGINFILE@@`. For example the URL may become `@@PLUGINFILE@@/filedir/filename.png`.
+ - You must pass it through a function to move the files from the draft file area into the correct file area for your code.
+- When displaying the editor content:
+ - You must pass it through `file_rewrite_pluginfile_urls()` to rewrite it back to a servable URL.
+ - You must provide a `pluginfile` function to perform access control checks and serve the file.
+- When fetching existing content for editing:
+ - You must copy it into a new draft file area so that changes can be made.
+ - You must rewrite the `@@PLUGINFILE@@` URL with the new draft file area.
+
+#### Simple use
+
+By creating a series of fields in the database, it is very easy to have standard functions fetch and store the data for this editor. For example, if you wanted to store data in a field called `textfield`, you would need to create fields in the database for:
+
+- `textfield` - where the content is actually stored
+- `textfieldformat` - to store the _format_ of the field
+- `textfieldtrust` (optional) - to store whether the text is trusted
+
+This is then paired with a set of options which are used when adding the Editor for the form, and processing the file content before displaying, and after saving the form, for example:
+
+```php
+$textfieldoptions = [
+ 'trusttext' => true,
+ 'subdirs' => true,
+ 'maxfiles' => $maxfiles,
+ 'maxbytes' => $maxbytes,
+ 'context' => $context,
+];
+```
+
+To add the editor to the form, and process the form data:
+
+1. Add editor `textfield_editor` to moodle form, pass options through custom data in form constructor.
+You should set `$data->id` to null if data not exist yet.
+
+ ```php
+ $mform->addElement(
+ 'editor',
+ 'textfield_editor',
+ get_string('fieldname', 'somemodule'),
+ null,
+ $textfieldoptions
+ );
+ ```
+
+1. Prepare the data - this will ensure that any existing files will be copied to the draft file area, and any existing placeholder is replaced for use in the form.
+
+ ```php
+ $data = file_prepare_standard_editor(
+ // The existing data.
+ $data,
+
+ // The field name in the database.
+ 'textfield',
+
+ // The options.
+ $textfieldoptions,
+
+ // The combination of contextid, component, filearea, and itemid.
+ $context,
+ 'mod_somemodule',
+ 'somearea',
+ $data->id
+ );
+ ```
+
+1. After the form is submitted, process the editor - this will move the files from the draft file area into the persistent storage, and rewrite the URLs to use the placeholder.
+
+ ```php
+ $data = file_postupdate_standard_editor(
+ // The submitted data.
+ $data,
+
+ // The field name in the database.
+ 'textfield',
+
+ // The options.
+ $textfieldoptions,
+
+ // The combination of contextid, component, filearea, and itemid.
+ $context,
+ 'mod_somemodule',
+ 'somearea',
+ $data->id
+ );
+ ```
+
+Real world examples can be found in `mod/glossary/edit.php` and `mod/glossary/comment.php`.
+
+#### Low-level use
+
+If you prefer, you can call the various underlying functions yourself. This is not typically required.
+
+:::caution Important
+
+You must call both the pre-processing, and post-processing, functions to ensure the correct behaviour.
+
+:::
+
+1. detect if the form was already submitted (this usually means that the draft area already exists) - `file_get_submitted_draft_itemid()`
+1. prepare a draft file area for temporary storage of all files attached to the text - `file_prepare_draft_area()`
+1. convert encoded relative links to absolute links - `file_prepare_draft_area()`
+1. create the form and set current data
+1. after submission the changed files must be merged back into original area - `file_save_draft_area_files()`
+1. absolute links have to be replaced by relative links - `file_save_draft_area_files()`
+
+##### Prepare current data - text and files
+
+```php
+if (empty($entry->id)) {
+ $entry = (object) [
+ 'id' => null,
+ 'definition' => '',
+ 'format' => FORMAT_HTML,
+ ];
+}
+
+$draftid_editor = file_get_submitted_draft_itemid('entry');
+$currenttext = file_prepare_draft_area(
+ $draftid_editor,
+ $context->id,
+ 'mod_glossary',
+ 'entry',
+ $entry->id,
+ [
+ 'subdirs' => true),
+ $entry->definition,
+ ]
+);
+$entry->entry = [
+ 'text' => $currenttext,
+ 'format' => $entry->format,
+ 'itemid' => $draftid_editor,
+];
+
+$mform->set_data($entry);
+```
+
+:::note
+
+Multiple files can be stored in the same virtual bucket. They will have different file names and/or file paths within the same item id.
+
+:::
+
+##### Obtain text, format and save draft files
+
+To retrieve editor content, you need to use following code:
+
+```php
+if ($fromform = $mform->get_data()) {
+ // Content of the editor field.
+ $messagetext = $fromform->entry['text'];
+ // Format of the content.
+ $messageformat = $fromform->entry['format'];
+}
+```
+
+When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.
+
+When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call `$DB->update_record('tablename', $data);` to have the other parts of the form submission stored correctly.)
+
+The `save_files_from_draft_area` function and replace absolute links with internal relative links do:
+
+```php
+$messagetext = file_save_draft_area_files(
+ // The id of the draft file area.
+ $draftideditor,
+
+ // The combination of contextid / component / filearea / itemid
+ // form the virtual bucket that file are stored in.
+ $context->id,
+ 'mod_glossary',
+ 'entry',
+ $entry->id,
+
+ // The options to pass.
+ [
+ 'subdirs' => true,
+ ],
+
+ // The text received from the form.
+ $messagetext
+);
+```
+
+All URLs in content that point to files managed to the File API are converted to a form that starts `@@PLUGINFILE@@/` before the content is stored in the database. That is what we mean by rewriting.
+
+## File serving
+
+### Convert internal relative links to absolute links
+
+Before text content is displayed to the user, any URLs in the `@@PLUGINFILE@@/` form in the content need to be rewritten to the real URL where the user can access the files.
+
+```php
+$messagetext = file_rewrite_pluginfile_urls(
+ // The content of the text stored in the database.
+ $messagetext,
+ // The pluginfile URL which will serve the request.
+ 'pluginfile.php',
+
+ // The combination of contextid / component / filearea / itemid
+ // form the virtual bucket that file are stored in.
+ $context->id,
+ 'frankenstyle_component',
+ 'filearea',
+ $itemid
+);
+```
+
+### Implement file serving access control
+
+Attachments and embedded images should have the same access control as the text itself. In a majority of cases these files are served using `pluginfile.php`. Access control is defined in the `[componentpath]/lib.php` file, using a function named `[componentname]_pluginfile()`.
+
+## File browsing support
+
+Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.
+
+Activities may specify browsing support in own `module/lib.php` file by implementing functions `module_get_file_areas()` and `module_get_file_info()`.
+
+## See also
+
+- [File API](../../files/index.md)
+- [Using the file API](../../files/index.md)
+- [Repository plugins](../../../plugintypes/repository/index.md)
diff --git a/versioned_docs/version-4.5/apis/subsystems/form/usage/index.md b/versioned_docs/version-4.5/apis/subsystems/form/usage/index.md
new file mode 100644
index 0000000000..cb9d4fc80f
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/form/usage/index.md
@@ -0,0 +1,142 @@
+---
+title: Form Usage
+tags:
+ - core_form
+ - form
+ - core
+ - API
+documentationDraft: true
+---
+
+Moodle's Form API is an extension of the Pear HTML_QuickForm API, which is no longer supported. Some documentation for the upstream library is [available in the PEAR package page](http://pear.php.net/package/HTML_QuickForm), including a [short tutorial](http://pear.php.net/manual/en/package.html.html-quickform.tutorial.php). A [longer tutorial is also available](http://web.archive.org/web/20130630141100/http://www.midnighthax.com/quickform.php), courtesy of the Internet Archive.
+
+Moodle will attempt to provide a more complete tutorial in this documentation where possible.
+
+:::tip
+
+Some good examples of usage of the Forms API can be found at the following locations:
+
+- [Course edit form - definition](https://github.com/moodle/moodle/blob/main/course/edit_form.php)
+- [Course edit form - usage](https://github.com/moodle/moodle/blob/main/course/edit.php)
+
+:::
+
+Whilst much of the API originates in the PEAR package, all interaction with the library should be via the `moodleform` class, which acts as a controlling wrapper to HTML_QuickForm.
+
+## Basic Usage in A Normal Page
+
+Generally the structure of a page with a form on it looks like this:
+
+```php
+// You will process some page parameters at the top here and get the info about
+// what instance of your module and what course you're in etc. Make sure you
+// include hidden variable in your forms which have their defaults set in set_data
+// which pass these variables from page to page.
+
+// Setup $PAGE here.
+
+// Instantiate the form that you defined.
+$mform = new \plugintype_pluginname\form\myform();
+// Default 'action' for form is strip_querystring(qualified_me()).
+
+// Set the initial values, for example the existing data loaded from the database.
+// This is typically an array of name/value pairs that match the
+// names of data elements in the form.
+// You can also use an object.
+$mform->set_data($toform);
+
+if ($mform->is_cancelled()) {
+ // You need this section if you have a cancel button on your form.
+ // You use this section to handle what to do if your user presses the cancel button.
+ // This is often a redirect back to an older page.
+ // NOTE: is_cancelled() should be called before get_data().
+ redirect($returnurl);
+
+} else if ($fromform = $mform->get_data()) {
+ // This branch is where you process validated data.
+
+ // Typically you finish up by redirecting to somewhere where the user
+ // can see what they did.
+ redirect($nexturl);
+}
+
+// If the form was not cancelled, and data was not submitted, then display the form.
+echo $OUTPUT->header();
+$mform->display();
+echo $OUTPUT->footer();
+```
+
+You are encouraged to look at `lib/formslib.php` to see what additional functions and parameters are available. Available functions are well commented.
+
+## Defining Your Form Class
+
+The form class tells us about the structure of the form.
+
+In most cases you can place this in an auto-loadable class, in which case it should be placed in a folder named `form`, for example:
+
+```php title="mod/forum/classes/form/myform.php
+_form->addElement( ... );
+
+ // Add the standard elements.
+ $this->standard_coursemodule_elements();
+
+ // Add the form actions.
+ $this->add_action_buttons();
+ }
+}
+```
diff --git a/versioned_docs/version-4.5/apis/subsystems/group/index.md b/versioned_docs/version-4.5/apis/subsystems/group/index.md
new file mode 100644
index 0000000000..43f62590ff
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/group/index.md
@@ -0,0 +1,216 @@
+---
+title: Groups API
+tags:
+ - API
+ - Subsystem
+ - group
+ - grouping
+ - course
+documentationDraft: true
+---
+
+Moodle [Groups](https://docs.moodle.org/en/Groups) are a way of expressing collections of users within a course. They may be defined by the teacher in the course participants page, or created automatically during a bulk user upload (for example, from a text file).
+
+A teacher can choose whether to use, or even to force, the use of groups for an entire course (from within the Course settings page), or for an individual activity (from within the Activity settings).
+
+Groups can be used in different modes:
+
+- None - groups are not used
+- Separate - users can only see and interact with users in their own group
+- Visible - users can see a list of the other groups and, depending on the activity, may be able to interact with them
+
+If enabled at the course level, the group mode will affect how course-wide reporting functions - for example, if a course-wide group mode of "Separate groups" is enabled, this is applied within the gradebook.
+
+Groups may be grouped together into named [Groupings](https://docs.moodle.org/en/Groupings). The course, and individual activities, can be configured to filter the groups shown to those in a specific Grouping. If a user is a member of multiple groups, then only those groups which belong to the selected grouping are shown.
+
+When a course or activity is in the 'Separate' groups mode, only users within that group can interact with, unless they hold the `moodle/site:accessallgroups` capability. By default, this capability is given to users who hold a role with the `editingteacher`, and `manager` archetypes.
+
+Most of these settings are handled by the core groups code and core groups API. If the activity module wants to implement group support, it need only use the Groups API to:
+
+- Find out the current settings for this instance of the activity
+- Show group controls (for example group selection menus) when appropriate
+- Explore memberships and structures of groups
+- Modify it's own interface to hide/show data accordingly
+
+:::note
+
+Groups are typically only relevant to course features such as Activity modules, some blocks and reports.
+
+Some other core subsystems also need to be group-aware.
+
+:::
+
+## Group modes
+
+There are three different group modes, these modes allow for restrictions to be put in place for access and visibility.
+
+- No groups (`NOGROUPS` constant) - The course or activity has no groups.
+- Separate groups (`SEPARATEGROUPS` constant) - Teachers and students can normally only see information relevant to that group.
+- Visible groups (`VISIBLEGROUPS` constant) - Teachers and students are separated into groups but can still see all information.
+
+This is explained in more detail on the [Groups access control](https://docs.moodle.org/dev/Groups_access_control) page.
+
+:::caution
+
+Only groups with `participation` enabled are available for use in Separate and Visible groups mode. This is enabled by default for groups with *Visible to all* or *Visible to members* visibility (See below) but is always disabled for groups with *See own membership* or *Membership is hidden*.
+
+:::
+
+Calling `groups_get_all_groups()` with the `$participationonly` argument set to `true` will only return groups with `participation` enabled. If you are calling this function within an activity plugin, you will usually want to do this unless you have a good reason not to.
+
+## Group visibility
+
+To help protect user privacy, each group has a `visibility` setting, which controls who can see the group and its members. The possible values are defined as `GROUPS_VISIBILITY_*` constants in grouplib.
+
+Users with the `moodle/course:viewhiddengroups` capability can always see all groups, regardless of their visibility.
+Otherwise, the following restrictions apply:
+
+- Visible to all (`GROUPS_VISIBILITY_ALL` constant) - Everyone can see the group and its members. This is the default, and the legacy behaviour for all groups before Moodle 4.2.
+- Visible to members (`GROUPS_VISIBILITY_MEMBERS` constant) - Only members of the group can see the group, and members of the group can see each others' membership of the group.
+- See own membership (`GROUPS_VISIBILITY_OWN` constant) - Only members of the group can see the group, and members **cannot** see each others' membership, only their own.
+- Membership is hidden (`GROUPS_VISIBILITY_NONE` constant) - No-one can see the group or its members.
+
+The core API functions in `groupslib` such as `groups_get_all_groups()` and `groups_get_members()` will respect the group visibility and the current user's permissions, so use these as far as possible when fetching data about groups and their members. The `\core_group\visibility` class also has helper functions to determine whether a user is allowed to see a group, or its members.
+
+## File locations
+
+The Groups API is currently defined in [lib/grouplib.php](https://github.com/moodle/moodle/blob/main/lib/grouplib.php). This contains global functions which have the `groups_` prefix, for example: `groups_get_group()`.
+
+## Examples
+
+### How to find and use the "current" group
+
+This is using an example from the module forums.
+
+```php
+// Get the course module id from a post or get request.
+$id = required_param('id', PARAM_INT);
+
+// Get the course module.
+$cm = get_coursemodule_from_id('forum', $id, 0, false, MUST_EXIST);
+
+// Get the current group id.
+$currentgroupid = groups_get_activity_group($cm);
+// Get the current group name from the group id.
+$currentgroupname = groups_get_group_name($currentgroupid);
+
+// Do as you please with your newly obtained group information.
+```
+
+### How to make sure that the current user can see a given item in your activity
+
+The following example:
+
+- fetches the course module record for the specified forum id
+- checks whether it has a group mode specified (separate or visible groups)
+- fetches the list of possible groups for that activity
+- checks whether the forum discussion is in a valid group
+
+For this example we are going to check to see if groups are active and whether the user has access to the discussion.
+
+```php
+// Get the course module and discussion id from a post or get request.
+$id = required_param('id', PARAM_INT);
+$discussionid = required_param('discussionid', PARAM_INT);
+
+// Get the course module.
+$cm = get_coursemodule_from_id('forum', $id, 0, false, MUST_EXIST);
+
+// Get the group id for this discussion
+$discussiongroup = $DB->get_record('forum_discussions', ['id' => $discussionid], groupid);
+
+// Check access.
+if (groups_get_activity_groupmode($cm)) {
+ $groups = groups_get_activity_allowed_groups($cm);
+} else {
+ $groups = [];
+}
+if (!in_array($discussiongroup->groupid, array_keys($groups))) {
+ print_error('groupnotamember', 'group');
+}
+
+// Continue on with group specific discussion
+```
+
+### How to display a group menu
+
+The following example will display the group selection dropdown using the `groups_print_activity_menu()` function.
+
+This function will show all groups that the user has access to for the current course module.
+
+After making a selection, the user will be redirected to the `$url` provided.
+
+```php
+// Get the course module id from a post or get request
+$id = required_param('id', PARAM_INT);
+
+// Get the course module
+$cm = get_coursemodule_from_id('forum', $id, 0, false, MUST_EXIST);
+
+// Create a moodle_url. A URL is required so that if the user selects a different group, the page can be
+// reloaded with the new groups information.
+$url = new moodle_url('/mod/forum/view.php', ['id' => $cm->id]);
+
+// Print group information (A drop down box will be displayed if the user is a member of more than one group,
+// or has access to all groups).
+groups_print_activity_menu($cm, $url);
+```
+
+### How to get just the groups that the current user can see
+
+The following example will check whether the current user has permission to see hidden groups on a course, and **if they do not**, will apply additional conditions to a query to restrict the results to just those groups they should see.
+
+```php
+$courseid = required_param('courseid', PARAM_INT);
+$sql = "SELECT g.idnumber, gm.*
+ FROM {groups} g
+ JOIN {groups_members} gm ON gm.groupid = g.id
+ WHERE courseid = ?";
+
+$params = [$courseid];
+
+$context = context_course::instance($courseid);
+if (!has_capability('moodle/course:viewhiddengroups', $context)) {
+ // Apply visibility restrictions.
+ [
+ $visibilitywhere,
+ $visibilityparams
+ ] = \core_group\visibility::sql_group_visibility_where($userid);
+ $sql .= " AND " . $visibilitywhere;
+ $params = array_merge($params, $visibilityparams);
+}
+
+$rs = $DB->get_recordset_sql($sql, $params);
+```
+
+:::note
+
+A query like this must join on `group_members` as group visibility is dependant on the user's own memberships.
+
+:::
+
+
+
+### Group GeoPattern images
+
+Moodle now allows groups to generate GeoPattern images. This is intended to improve the overall user experience by allowing users to differentiate groups easier in their Moodle activities and resources.
+
+Generate the SVG image for the group and use it in your Moodle activities and resources:
+
+```php
+$group_svg_image = moodle_url::make_pluginfile_url(
+ $context->id,
+ 'group',
+ 'generated',
+ $group->id,
+ '/',
+ 'group.svg'
+);
+```
+
+This will create an SVG image for the specified group, which can then be used in Moodle activities and resources. Make sure to customize the code to fit your specific use case.
+
+## Further reading
+
+- [Groups FAQ](https://docs.moodle.org/en/Groups_FAQ)
+- [Groupings FAQ](https://docs.moodle.org/en/Groupings_FAQ)
diff --git a/versioned_docs/version-4.5/apis/subsystems/muc/_index/When_to_localize_cache.png b/versioned_docs/version-4.5/apis/subsystems/muc/_index/When_to_localize_cache.png
new file mode 100644
index 0000000000..1eca40ec71
Binary files /dev/null and b/versioned_docs/version-4.5/apis/subsystems/muc/_index/When_to_localize_cache.png differ
diff --git a/versioned_docs/version-4.5/apis/subsystems/muc/index.md b/versioned_docs/version-4.5/apis/subsystems/muc/index.md
new file mode 100644
index 0000000000..244ee390fc
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/muc/index.md
@@ -0,0 +1,355 @@
+---
+title: Cache API
+tags:
+ - API
+ - Subsystem
+ - MUC
+---
+This document provides an in-depth overview of the Cache API, also known as MUC (Moodle Universal Cache), a fundamental caching system within Moodle.
+
+This document uses a hypothetical module plugin named `myplugin` as the focal point.
+
+There is also a [Cache API - Quick reference](https://docs.moodle.org/dev/Cache_API_-_Quick_reference) if you would rather read that.
+
+## Basic usage
+
+Getting started with the Cache API is exceptionally straightforward. It's designed for quick and easy usage, emphasizing self-containment.
+All you need to do is add a definition for your cache and you are ready to start working with the Cache API.
+
+### Creating a definition
+
+Cache definitions exist within the `db/caches.php` file for a component/plugin.
+
+In the case of core that is the `lib/db/caches.php` file, in the case of a module that would be `mod/myplugin/db/caches.php`.
+
+The definition is used API in order to understand a little about the cache and what it is being used for, it also allows the administrator to set things up especially for the definition if they want.
+From a development point of view the definition allows you to tell the API about your cache, what it requires, and any (if any) advanced features you want it to have.
+
+The following shows a basic definition containing just the bare minimum:
+
+```php title="mod/myplugin/db/caches.php"
+$definitions = [
+ 'somedata' => [
+ 'mode' => cache_store::MODE_APPLICATION,
+ ]
+];
+```
+
+This informs the API that the `myplugin` module has a cache called `somedata` and that it is an application (globally shared) cache.
+
+When creating a definition that's the bare minimum, to provide an area (`somedata`) and declare the type of the cache application, session, or request.
+
+- An application cache is a shared cache, all users can access it.
+- Session caches are scoped to a single users session, but may not actually be stored in the session.
+- Request caches you can think of as static caches, only available to the user owning the request, and only alive until the end of the request.
+
+There are of course many more options available that allow you to really take the cache by the reigns, you can read about some of the important ones further on, or skip ahead to [the definition](#the-definition) section which details the available options in full.
+
+:::note
+
+For each definition, a language string with the name `cachedef_` followed by the name of the definition is expected.
+
+:::
+
+Using the example above you would have to define:
+
+```php title="mod/myplugin/lang/en/mod_myplugin.php"
+$string['cachedef_somedata'] = 'This is the description of the cache somedata';
+```
+
+### Getting a cache object
+
+Once your definition has been created you should bump the version number so that Moodle upgrades and processes the definitions file at which point your definition will be useable.
+
+Now within code you can get a cache object corresponding to the definition created earlier.
+
+```php
+$cache = cache::make('mod_myplugin', 'somedata');
+```
+
+The `cache::make()` method is a factory method, it will create a cache object to allow you to work with your cache. The cache object will be one of several classes chosen by the API based upon what your definition contains. All of these classes will extend the base cache class, and in nearly all cases you will get one of `cache_application`, `cache_session`, or `cache_request` depending upon the mode you selected.
+
+### Using your cache object
+
+Once you have a cache object (will extend the cache class and implements `cache_loader`) you are ready to start interacting with the cache.
+
+There are three basic basic operations: get, set, and delete.
+
+The first is to send something to the cache.
+
+```php
+$result = $cache->set('key', 'value');
+```
+
+Easy enough. The key must be an `int` or a `string`. The value can be absolutely anything your want that is [serializable](https://www.php.net/manual/en/function.serialize.php).
+The result is true if the operation was a success, false otherwise.
+
+The second is to fetch something from the cache.
+
+```php
+$data = $cache->get('key');
+```
+
+`$data` will either be whatever was being stored in the cache, or false if the cache could not find the key.
+
+The third and final operation is delete.
+
+```php
+$result = $cache->delete('key');
+```
+
+Again just like set the result will either be true if the operation was a success, or false otherwise.
+
+You can also set, get, and delete multiple `key => value` pairs in a single transaction.
+
+```php
+$result = $cache->set_many([
+ 'key1' => 'data1',
+ 'key3' => 'data3'
+]);
+// $result will be the number of pairs sucessfully set.
+
+$result = $cache->get_many(['key1', 'key2', 'key3']);
+print_r($result);
+// Will print the following:
+// array(
+// 'key1' => 'data1',
+// 'key2' => false,
+// 'key3' => 'data3'
+// )
+
+$result = $cache->delete_many(['key1', 'key3']);
+// $result will be the number of records sucessfully deleted.
+```
+
+That covers the basic operation of the Cache API.
+In many situations there is not going to be any more to it than that.
+
+## Ad-hoc Caches
+
+This is the alternative method of using the cache API.
+It involves creating a cache using just the required params at the time that it is required. It doesn't require that a definition exists making it quicker and easier to use, however it can only use the default settings and is only recommended for insignificant caches (rarely used during operation, never to be mapped or customised, only existing in a single place in code).
+
+Once a cache object has been retrieved it operates exactly as the same as a cache that has been created for a definition.
+
+To create an ad-hoc cache you would use the following:
+
+```php
+$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'mod_myplugin', 'mycache');
+```
+
+:::tip
+
+Don't be lazy, if you don't have a good reason to use an ad-hoc cache you should be spending an extra 5 minutes creating a definition.
+
+:::
+
+## The definition
+
+The above section illustrated how to create a basic definition, specifying just the area name (the key) and the mode for the definition. Those being the two required properties for a definition.
+
+There are many other options that will let you make the most of the Cache API and will undoubtedly be required when implementing and converting cache solutions to the Cache API.
+
+The following details the options available to a definition and their defaults if not applied:
+
+```php
+$definitions = [
+ // The name of the cache area is the key. The component/plugin will be picked up from the file location.
+ 'area' => [
+ 'mode' => cache_store::MODE_*,
+ 'simplekeys' => false,
+ 'simpledata' => false,
+ 'requireidentifiers' => ['ident1', 'ident2'],
+ 'requiredataguarantee' => false,
+ 'requiremultipleidentifiers' => false,
+ 'requirelockingread' => false,
+ 'requirelockingwrite' => false,
+ 'requirelockingbeforewrite' => false,
+ 'maxsize' => null,
+ 'overrideclass' => null,
+ 'overrideclassfile' => null,
+ 'datasource' => null,
+ 'datasourcefile' => null,
+ 'staticacceleration' => false,
+ 'staticaccelerationsize' => null,
+ 'ttl' => 0,
+ 'mappingsonly' => false,
+ 'invalidationevents' => ['event1', 'event2'],
+ 'canuselocalstore' => false
+ 'sharingoptions' => cache_definition::SHARING_DEFAULT,
+ 'defaultsharing' => cache_definition::SHARING_DEFAULT,
+ ],
+];
+```
+
+### Setting requirements
+
+The definition can specify several requirements for the cache.
+
+This includes identifiers that must be provided when creating the cache object, that the store guarantees data stored in it will remain there until removed, a store that supports multiple identifiers, and finally read/write locking.
+
+The options for these are as follows:
+
+- `simplekeys`: [bool] Set to true if your cache will only use simple keys for its items.
+Simple keys consist of digits, underscores and the 26 chars of the english language. `a-zA-Z0-9_`
+If true the keys won't be hashed before being passed to the cache store for gets/sets/deletes. It will be better for performance and possible only because we know the keys are safe.
+- `simpledata`: [bool] If set to true we know that the data is scalar or array of scalar. If true, the data values will be stored as they are. Otherwise they will be serialised first.
+- `requireidentifiers`: [array] An array of identifiers that must be provided to the cache when it is created.
+- `requiredataguarantee`: [bool] If set to true then only stores that can guarantee data will remain available once set will be used.
+- `requiremultipleidentifiers`: [bool] If set to true then only stores that support multiple identifiers will be used.
+- `requirelockingread`: [bool] If set to true then a lock will be gained before reading from the cache store. It is recommended not to use this setting unless 100% absolutely positively required.
+- `requirelockingbeforewrite`:[bool] If set to true then a lock must be gained and held during expensive computation such as the generation of modinfo before writing to the cache store by the calling code. This is to prevent cache stampedes. After gaining the lock code must check to ensure the cache hasn't already been updated by another process. This is so far only used by course `modinfo` application caches presently.
+
+### Cache modifiers
+
+You are also to modify the way in which the cache is going to operate when working for your definition.
+
+By enabling the static option the Cache API will only ever generate a single cache object for your definition on the first request for it, further requests will be returned the original instance
+
+This greatly speeds up the collecting of a cache object.
+
+Enabling persistence also enables a static store within the cache object, anything set to the cache, or retrieved from it will be stored in that static array for the life of the request.
+This makes the persistence options some of the most powerful. If you know you are going to be using you cache over and over again or if you know you will be making lots of requests for the same items then this will provide a great performance boost.
+
+Of course the static storage of cache objects and of data is costly in terms of memory and should only be used when actually required, as such it is turned off by default.
+As well as persistence you can also set a maximum number of items that the cache should store (not a hard limit, its up to each store) and a time to live (ttl) although both are discouraged as efficient design negates the need for both in most situations.
+
+- `staticacceleration`: [bool] This setting does two important things. First it tells the cache API to only instantiate the cache structure for this definition once, further requests will be given the original instance. Second the cache loader will keep an array of the items set and retrieved to the cache during the request. This has several advantages including better performance without needing to start passing the cache instance between function calls, the downside is that the cache instance + the items used stay within memory. Consider using this setting when you know that there are going to be many calls to the cache for the same information or when you are converting existing code to the cache and need to access the cache within functions but don't want to add it as an argument to the function.
+- `staticaccelerationsize`: [int] This supplements the above setting by limiting the number of items in the caches persistent array of items. Tweaking this setting lower will allow you to minimise the memory implications above while hopefully still managing to offset calls to the cache store.
+- `ttl`: [int] A time to live for the data (in seconds). It is strongly recommended that you don't make use of this and instead try to create an event driven invalidation system (even if the event is just time expiring, better not to rely on `ttl`). Not all cache stores will support this natively and there are undesired performance impacts if the cache store does not.
+- `maxsize`: [int] If set this will be used as the maximum number of entries within the cache store for this definition. It's important to note that cache stores don't actually have to acknowledge this setting or maintain it as a hard limit.
+- `canuselocalstore`: [bool] This setting specifies whether the cache can safely be local to each frontend in a cluster which can avoid latency costs to a shared central cache server. The cache needs to be carefully written for this to be safe. It is conceptually similar to using `$CFG->localcachedir` (can be local) vs `$CFG->cachedir` (must be shared). Look at `purify_html()` in `lib/weblib.php` for an example.
+
+### Overriding a cache loader
+
+:::danger
+
+This is a super advanced feature and should not be done. Ever. Unless you have a very good reason to do so.
+
+:::
+
+It allows you to create your own cache loader and have it be used instead of the default cache loader class. The cache object you get back from the make operations will be an instance of this class.
+
+- `overrideclass`: [string] A class to use as the loader for this cache. This is an advanced setting and will allow the developer of the definition to take 100% control of the caching solution. Any class used here must inherit the `cache_loader` interface and must extend default cache loader for the mode they are using.
+- `overrideclassfile`: [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.
+
+### Specifying a data source
+
+This is a great wee feature, especially if your code is object orientated.
+
+It allows you to specify a class that must inherit the `cache_data_source` object and will be used to load any information requested from the cache that is not already being stored.
+
+When the requested key cannot be found in the cache the data source will be asked to load it. The data source will then return the information to the cache, the cache will store it, and it will then return it to the user as a request of their get request. Essentially no get request should ever fail if you have a data source specified.
+
+- `datasource`: [string] A class to use as the data loader for this definition. Any class used here must inherit the cache_data_source interface.
+- `datasourcefile`: [string] Suplements the above setting indicated the file containing the class to be used. This file is included when required.
+
+:::note
+
+In Moodle versions prior to 3.8.6 and 3.9.3, if caching is disabled then *nothing* will be loaded through the data source which is probably not what you expect (rather than the data source being loaded every time but never cached). See also: [MDL-42012](https://tracker.moodle.org/browse/MDL-42012)
+
+:::
+
+### Misc settings
+
+The following are stand along settings that don't fall into any of the above categories.
+
+- `invalidationevents`: [array] An array of events that should cause this cache to invalidate some or all of the items within it. Note that these are NOT normal moodle events and predates the [Events API](https://docs.moodle.org/dev/Events_API). Instead these are arbitrary strings which can be used by `cache_helper::purge_by_event('changesincoursecat');` to mark multiple caches as invalid at once without the calling code knowing which caches are affected.
+- `mappingsonly`: [bool] If set to true only the mapped cache store(s) will be used and the default mode store will not. This is a super advanced setting and should not be used unless absolutely required. It allows you to avoid the default stores for one reason or another.
+- `sharingoptions`: [int] The sharing options that are appropriate for this definition. Should be the sum of the possible options.
+- `defaultsharing`: [int] The default sharing option to use. It's highly recommended that you don't set this unless there is a very specific reason not to use the system default.
+
+## Localized stores for distributed high performance caching
+
+Most cache definitions are simple in that the code expects to be able to purge the cache, or update it, and for this to be universally available from then on to all code which loads from this cache again. But as you scale up having a single mega shared cache store doesn't work well for a variety of reasons, including extra latency between multiple front ends and the shared cache service, the number of connections the cache server can handle, the cost of IO between services, and depending on the cache definition issues with locking while writing.
+
+So if you want very high performance caching then you need to write you code so that it can support being distributed, or localized, which means that each front end can have it's own independent cache store. But this architecture means that you have no direct way to communicate from code running in one place to invalidate the caches on all the other front ends. In order to achieve this you need to carefully construct cache keys so that if the content changes then it uses a new cache key, which will of course be a cache miss and then it will regenerate using fresh data. There are multiple ways to achieve this, a couple common example strategies are below.
+
+### When should you localize?
+
+Not all caches are good candidates to localize and some can have a detrimental effect if localized for the wrong reasons.
+![When to localize a cache](./_index/When_to_localize_cache.png)
+
+### Revision numbers as key suffix
+
+A simple method is storing a version number somewhere and appending that to your key. This is the most common method used in Moodle, simply store a number somewhere which is globally shared such as in a custom database field, or using `set_config` / `get_config`.
+
+One small edge case with this approach is you now need to make sure that the incrementing code is atomic, which means you should use a DB transaction, or gain a lock, before bumping the version so you don't get two conflicting changes ending up on the same version with a race condition. However if you are anticipating high turnover rate of the cache you probably have a deeper issue, see 'Fast churning keys' below.
+
+One potential benefit of a simple version number strategy if your cache misses are very expensive, is that you can check for the presence of a version, and if it doesn't exist it is easy to simply retrieve the previous version and use it in the mean time, while you could generate the new version asynchronously. This is an advanced concept and depends on the use case and has the obvious disadvantage of showing stale data.
+
+:::info
+
+An example of this strategy in Moodle is the theme versions cache: https://github.com/moodle/moodle/blob/main/lib/outputlib.php#L88-L100
+
+Note this is not actually in MUC but the caching concepts are the same.
+
+:::
+
+It works best with a cache store that supports Least Recently Used garbage collection.
+
+### Revision numbers as value suffix instead of key suffix
+
+This is conceptually the same as above but has two important differences. Firstly because the key itself is always the same then only a single version of some value will be stored on disk or in memory for any given cache store which makes it much more efficient in terms of storage. But secondly this comes with a higher coding complexity cost because it will no longer be guaranteed to be correct because a local cache could return a hit with a stale version. So if you need it to be correct you will need to parse out the revision from the value and then confirm the revision is correct before using the value. If it is invalid then you need to treat it as a miss and handle that. One way is to rebuild and set it, but this loses the advantage of primary and final caches (see below). A better way is to delete the local cache but not the final cache by passing a second param of false to delete, and then getting the value again which will repopulate the local cache from the shared cache:
+
+```php
+ $cache->delete('foo', false); // Delete the primary / local stale version
+ $cache->get('foo'); // Get the final / shared version (which may also be stale!)
+```
+
+But this also is imperfect if there are 3 layers of caching, see [MDL-72837](https://tracker.moodle.org/browse/MDL-72837) for a full discussion and a possible new api to handle this.
+
+This method is how the course modinfo cache is localized.
+
+### Using time as a key suffix
+
+Another common approach is to use a timestamp, this is how some of the core cache numbers work, see `increment_revision_number()` for some examples. This has the benefit of not needing any transaction or locking, but you do run the risk of two processes clashing if they happen to run in the same second.
+
+It may look like Moodle theme-related caching uses this strategy, but actually if you look at [the code for theme_get_next_revision](https://github.com/moodle/moodle/blob/df0e58adb140f90712bcd3229ad936d3b4bc15d9/lib/outputlib.php#L88), you will see that it is actually guaranteeing to generate a unique new integer which mitigates the clashing mentioned above. It is just making that integer close to current time-stamp, to make it more self-documenting.
+
+:::info
+
+https://github.com/moodle/moodle/blob/main/lib/datalib.php#L1131-L1145
+
+It works best with a cache store that supports Least Recently Used garbage collection.
+:::
+
+### Content hashes as keys
+
+A great strategy is to use a digest or hash such as `sha1` / `sha256` of the content stored inside the cache, or in even more advanced scenarios a hash of the dependencies of the content in the cache. This guarantees that the key will be unique, and can be truly distributed without any synchronous communication between the front ends.
+
+This strategy can work very well when building up large cache items from many smaller cache fragments in a nested tree structure, eg when caching a structure which is built of other cache items, eg 10 chunks of html to get combined into a large chunk to be cached, you would append the 10 hashes of the 10 smaller chunks and then hash that to use as the key for the combined cache item. This means you can mutate a small part of a tree structure and quickly re-generate the whole tree without expensively regenerating all of the other branches and leaves in the tree.
+
+:::info
+
+This is known as 'Russian Doll' caching: https://blog.appsignal.com/2018/04/03/russian-doll-caching-in-rails.html
+
+:::
+
+This is a very common strategy is many distributed systems, outside of the context of caching, and is conceptually how git works internally which is why commits are a `sha1` hash.
+
+This strategy works well if you may periodically change back and forth to previous states which will still be present and so be immediate hits without re-warming. It works best with a cache store that supports Least Recently Used garbage collection.
+
+### Primary and Final caches
+
+Because HTTP requests are generally assigned randomly or round-robin to front ends, when a cache item version changes you will now effectively have an empty cache on every front end. As you get the same cache item again and again on each front end it will continue to be a cache miss on each local box until they are all warm, which can ironically mean that on average for a fast-changing, but not often requested cache item, your cache hit rate will be very low and much worse than if you had a shared cache. The solution here is to have multiple levels of caches set up, a local cache backed by a shared cache. You do not need to do anything special in the code to support this if your cache is already localizable, the MUC manages this for you, ie if you request a key missing from the local cache it will then request it from the shared cache. If present it will copy it back to the local cache and then return the value. If it is not present in either then your fallback code will generate the item, and it will be written to both cache stores at the same time.
+
+This is especially important if you are scaling up and down the number of frontends quickly. If you suddenly need more horizontal scale and create a bunch of new front ends with empty caches and no shared cache they will all consume even more resources warming up and loading the shared services such as the database and filesystem.
+
+A good rule of thumb is to pair similar types of local and shared caches together. For instance, it is very common to store the Moodle string cache in APCu because it is very heavily used, so an in-memory cache is the fastest and is well paired this a shared cache like Redis. `coursemodinfo` on the other hand is often very large so isn't as practical to store in Redis so it is usually cached on disk, so you could have a local file cache (which could often be very fast `SSD`) and pair it with a shared disk cache in `dataroot` (often much slower over `NFS` or `Gluster`).
+
+As you scale even bigger, a new bottleneck can appear when purging a shared disk cache ie when you deploy a new version. A full purge needs to iterate over and remove and sync a very large number of files, which can take some time. See [MDL-69088](https://tracker.moodle.org/browse/MDL-69088) for a proposed fix.
+
+### Beware fast churning keys
+
+A big concern when designing a cache is how fast you anticipate it changing. If it contains very fast moving data but sparsely requested data (see above) then you can end up in a situation where you are effectively just using the shared final cache, and wasting latency and space and IO cloning data to the local cache where is may not be hit again very much. As always caching is a balancing act trading off between CPU, time and disk, and ultimately money.
+
+Even if your cache is able to use a local store that doesn't mean it actually will be configured to be local (and your code can't tell either way). So a wasteful cache item will consume much more space storing all the previous versions of its items even if it isn't localized, and it will be much worse if it is.
+
+### Time-To-Live for distributed caches
+
+Another consideration is the total size of your cache stores across all the front ends. As cache keys change they are never be invalidated or purged. So you should have in place some process to garbage collect stale items. This is more a concern for the cache store implementations and the configuration but worth considering. Some stores are deleted on upgrade, or have a Time-To-Live or a Least Recently Used strategy for deleting stale items.
+
+## Miscellaneous
+
+- Checkout important [discussion about the Cache API at the Moodle developer chat](https://moodle.org/local/chatlogs/index.php?q=cache)
diff --git a/versioned_docs/version-4.5/apis/subsystems/output/_inplace/inplace_editable_example.png b/versioned_docs/version-4.5/apis/subsystems/output/_inplace/inplace_editable_example.png
new file mode 100644
index 0000000000..e276ade2fa
Binary files /dev/null and b/versioned_docs/version-4.5/apis/subsystems/output/_inplace/inplace_editable_example.png differ
diff --git a/versioned_docs/version-4.5/apis/subsystems/output/index.md b/versioned_docs/version-4.5/apis/subsystems/output/index.md
new file mode 100644
index 0000000000..a9becaa651
--- /dev/null
+++ b/versioned_docs/version-4.5/apis/subsystems/output/index.md
@@ -0,0 +1,449 @@
+---
+title: Output API
+tags:
+ - Output
+ - API
+---
+
+
+
+The Output API is responsible for visual aspects of Moodle content. This page explains how renderers, renderables, themes and templates all work together.
+
+## Page Output Journey
+
+Let's start with building a page that is part of an admin tool.
+
+```php title="/admin/tool/demo/index.php"
+libdir.'/adminlib.php');
+
+admin_externalpage_setup('tooldemo');
+
+// Set up the page.
+$title = get_string('pluginname', 'tool_demo');
+$pagetitle = $title;
+$url = new moodle_url("/admin/tool/demo/index.php");
+$PAGE->set_url($url);
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+
+$output = $PAGE->get_renderer('tool_demo');
+
+echo $output->header();
+echo $output->heading($pagetitle);
+
+$renderable = new \tool_demo\output\index_page('Some text');
+echo $output->render($renderable);
+
+echo $output->footer();
+```
+
+:::info Setup of an admin page
+
+On admin pages, the `admin_externalpage_setup($sectionname);` function should be called to set up and perform permission checks, for example:
+
+```php title="admin/tool/demo/mypage.php"
+require_once(__DIR__ . '/../../config.php');
+require_once("{$CFG->libdir}/adminlib.php");
+admin_externalpage_setup('example');
+```
+
+:::
+
+Firstly, we set some general `$PAGE` properties. We load the title of the page from a language string (see [String API](https://docs.moodle.org/dev/String_API)).
+
+```php
+// Set up the page.
+$title = get_string('pluginname', 'tool_demo');
+$pagetitle = $title;
+$url = new moodle_url("/admin/tool/demo/index.php");
+$PAGE->set_url($url);
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+```
+
+:::note What is $PAGE and where did it come from ?
+
+`$PAGE` is a global variable used to track the state of the page that is being returned. It is an instance of the `moodle_page` class defined in `lib/pagelib.php`. See [Page API](https://docs.moodle.org/dev/Page_API) for more information on the `$PAGE` variable.
+
+:::
+
+The most important properties stored in `$PAGE` are the page context, URL, layout, title and headings. `$PAGE` also gives access to some other important classes such as `$PAGE->requires`, which is an instance of the `page_requirements_manager` (`lib/outputrequirementslib.php`). The `page_requirements_manager` class lets us set dependencies on e.g. JavaScript and CSS to be inserted in the correct place in the page (The order in which things are inserted into the page is hugely important for performance).
+
+`$PAGE` also lets us load specific renderers for a plugin, or plugin and subtype. We will cover renderers in more detail next.
+
+```php
+$output = $PAGE->get_renderer('tool_demo');
+```
+
+This gets an instance of the `plugin_renderer_base` class that we use to create all output for our page. Themers can subclass this renderer to override specific render methods in order to customise Moodle's output. See [Output renderers](https://docs.moodle.org/dev/Output_renderers) for more information, and [Overriding a renderer](https://docs.moodle.org/dev/Overriding_a_renderer) for information about how themers can customise a renderer.
+
+:::important
+
+Some pages use the global variable `$OUTPUT` to generate their output. This is a generic renderer used for core pages etc, but plugins should always use a more specific plugin renderer.
+
+:::
+
+```php
+echo $output->header();
+echo $output->heading($pagetitle);
+```
+
+This code prints the header of the page and adds one heading to the page at the top of the content region. Page headings are very important in Moodle and should be applied consistently. See [HTML Guidelines](https://docs.moodle.org/dev/HTML_Guidelines) for more information on how and where to use headings.
+
+```php
+$renderable = new \tool_demo\output\index_page('Some text');
+echo $output->render($renderable);
+```
+
+This is the most interesting part of our page. We are creating an instance of a renderable and telling our renderer to render it. The renderable is usually more complex than this. It should hold all the data required for the renderer to display the page. This means we should perform all our logic such as database queries, page parameters and access checks in advance then pass the results as data to the renderable. The renderable then just takes that data and returns an HTML representation of it.
+
+```php
+echo $output->footer();
+```
+
+This prints the HTML for the bottom of the page. It is very important because it also prints out things that were added to the `page_requirements_manager` and that need to be printed in the footer; things like JavaScript includes, navigation tree setup, closing open containers tags etc. The reason all JavaScripts are added to the footer of the page is for performance. If you add JavaScript includes to the top of the page, or inline with the content, the browser must stop and execute the JavaScript before it can render the page. See https://developers.google.com/speed/docs/insights/BlockingJS for more information.
+
+### Renderable
+
+In the code above, we created a renderable. This is a class that you have to add to your plugin. It holds all the data required to display something on the page. Here is the renderable for this example:
+
+```php title="/admin/tool/demo/classes/output/index_page.php"
+sometext = $sometext;
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @return stdClass
+ */
+ public function export_for_template(renderer_base $output): stdClass {
+ $data = new stdClass();
+ $data->sometext = $this->sometext;
+ return $data;
+ }
+}
+```
+
+This class implements the renderable interface, which has no methods, and the templatable interface, which means that this class could be rendered with a template, so it must implement the `export_for_template` method. So in this example, the class accepts data via it's constructor, and stores that data in class variables. It does nothing else fancy with the data in this example (but it could). Note that the `export_for_template` function should only return simple types (arrays, stdClass, bool, int, float, string).
+
+Now let's look at the renderer for this plugin.
+
+```php title="admin/tool/demo/classes/output/renderer.php"
+export_for_template($this);
+ return parent::render_from_template('tool_demo/index_page', $data);
+ }
+}
+```
+
+The renderer exists to provide `render_` methods for all renderables used in the plugin. A theme designer can provide a custom version of this renderer that changes the behaviour of any of the render methods and so to customize their theme. In this example, the render method for the index page (`render_index_page`) does 2 things. It asks the renderable to export it's data so that it is suitable for passing as the context to a template, and then renders a specific template with this context. A theme designer could either manipulate the data in the render method (e.g. removing menu entries), or change the template (change the generated HTML) to customize the output.
+
+The template used in this plugin is located in the plugin's templates folder. The template can also be overridden by a theme designer.
+
+```xml title="admin/tool/demo/templates/index_page.mustache"
+
+
Heading
+
{{sometext}}
+
+```
+
+This is the mustache template for this demo. It uses some bootstrap classes directly to position and style the content on the page. `{{sometext}}` is replaced with the variable from the context when this template is rendered. For more information on templates see [Templates](../../../guides/templates/index.md).
+
+## Output Functions
+
+This section explains how dynamic data should be sent from Moodle to the web browser in an organised and standard way.
+
+:::important
+It is possible to have your own output methods but, thinking that you are going to share your code (yep, this is an OpenSource project!) and in the collaborative way we try to build and maintain the system every day, it would be really better to follow the basic guidelines explained below.
+
+By using them you will be helping to have better, more secure and readable code. Spend some minutes trying to understand them, please!
+:::
+
+Of course, these functions can be discussed, modified and new functions can arrive if there are some good reasons for it. Just discuss it in the [General developer forum](http://moodle.org/mod/forum/view.php?id=55).
+
+For each of the functions below we'll try to explain when they should be used, explaining the most important parameters supported and their meaning. Let's review them!
+
+### String formatting functions
+
+The `format_string` and `format_text` functions should always be used when preparing the output of information. They may also be used to process information before it is stored in the database however, filters should only be applied at output. For example, language filters must only be applied as the content is prepared for output because we don't yet know the user's preferred language.
+
+#### p() and s()
+
+```php
+function s($var, $strip=false)
+function p($var, $strip=false)
+```
+
+The only difference between these two functions is that `s()` returns the string, while `p()` prints it directly.
+
+These functions should be used to:
+
+- Print all the **values of form fields** like `` or `