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 . '
Hi!
'; - } - [...] -} -``` - -
- - - -```php title="theme/example/classes/output/core_renderer.php" -class core_renderer extends \core_renderer { - [...] - protected function render_context_header(\context_header $contextheader) { - $context = $contextheader->export_for_template($this); - $output = $this->render_from_template('core/context_header', $context); - return $output . '
Hi!
'; - } - [...] -} -``` - -
- - - -```mustache title="theme/example/templates/core/context_header.mustache" -{{! - @template core/context_header - - Template context_header - - Example context (json): - { - } -}} -
Hi!
-``` - -
- -
- -### 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/* + +