diff --git a/.eslintrc.js b/.eslintrc.js index 1c62861345..f905b9a72a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { ignorePatterns: [ // Ignore code examples. 'docs/**/_examples/*', + 'versioned_docs/*/**/_examples/*', 'general/**/_examples/*', '!.markdownlint', '!.lintstagedrc.mjs', diff --git a/config/navbar.js b/config/navbar.js index 7cc9f2b8d8..d72abf9f3c 100644 --- a/config/navbar.js +++ b/config/navbar.js @@ -16,7 +16,6 @@ */ const VersionsArchived = require('../versionsArchived.json'); -// eslint-disable-next-line @typescript-eslint/no-unused-vars const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( 0, 5, @@ -53,27 +52,27 @@ const navbar = { }, // Right. - // { - // type: 'docsVersionDropdown', - // position: 'right', - // dropdownActiveClassDisabled: true, - // dropdownItemsAfter: [ - // ...ArchivedVersionsDropdownItems.map( - // ([versionName, versionUrl]) => ({ - // label: versionName, - // href: versionUrl, - // }), - // ), - // { - // href: 'https://docs.moodle.org/dev/', - // label: 'Legacy documentation', - // }, - // { - // to: '/versions', - // label: 'All versions', - // }, - // ], - // }, + { + type: 'docsVersionDropdown', + position: 'right', + dropdownActiveClassDisabled: true, + dropdownItemsAfter: [ + ...ArchivedVersionsDropdownItems.map( + ([versionName, versionUrl]) => ({ + label: versionName, + href: versionUrl, + }), + ), + { + href: 'https://docs.moodle.org/dev/', + label: 'Legacy documentation', + }, + { + to: '/versions', + label: 'All versions', + }, + ], + }, ], }; diff --git a/docs/guides/javascript/yui/index.md b/docs/guides/javascript/yui/index.md index 0734c55bcf..1457f5d5c8 100644 --- a/docs/guides/javascript/yui/index.md +++ b/docs/guides/javascript/yui/index.md @@ -6,7 +6,7 @@ tags: - YUI --- -import DeprecatedSince from '../../../../src/components/DeprecatedSince'; +import DeprecatedSince from '@site/src/components/DeprecatedSince'; diff --git a/docs/guides/javascript/yui/modules.md b/docs/guides/javascript/yui/modules.md index dd95106581..6868f04b23 100644 --- a/docs/guides/javascript/yui/modules.md +++ b/docs/guides/javascript/yui/modules.md @@ -5,7 +5,7 @@ tags: - YUI --- -import DeprecatedSince from '../../../../src/components/DeprecatedSince'; +import DeprecatedSince from '@site/src/components/DeprecatedSince'; diff --git a/docs/guides/javascript/yui/namespacing.md b/docs/guides/javascript/yui/namespacing.md index 6748415d9f..15f800e1fc 100644 --- a/docs/guides/javascript/yui/namespacing.md +++ b/docs/guides/javascript/yui/namespacing.md @@ -5,7 +5,7 @@ tags: - YUI --- -import DeprecatedSince from '../../../../src/components/DeprecatedSince'; +import DeprecatedSince from '@site/src/components/DeprecatedSince'; diff --git a/docs/intro.md b/docs/intro.md index b0a8e8198c..f086484f7d 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -1,13 +1,13 @@ --- id: introduction title: Introduction -description: Developer documentation for Moodle 4.0. +description: Developer documentation for Moodle 4.2. slug: / tags: - Getting started --- -Welcome to the Developer Documentation for **Moodle 4.0**. +Welcome to the Developer Documentation for **Moodle 4.2**. This documentation is version-specific and includes a range of useful guides and information. @@ -16,8 +16,7 @@ This documentation is version-specific and includes a range of useful guides and - If you're new to Moodle development, you should check out our [Getting started guide](/general/development/gettingstarted) - Look through our [guides to Moodle APIs](./apis.md) - Browse our [Moodle feature](./guides.md) deep dives -- You may want to read the [Release notes](/general/releases/4.0) for Moodle 4.0 -- Have a plugin that you want to prepare for Moodle 4.0, check out the [Developer update notes](./devupdate.md) + - Interested in supporting the Moodle App in your plugins? Read the [Moodle App documentation](/general/app) ::: diff --git a/sidebars/docs.js b/sidebars/docs.js index 6fd9353243..272ac7f9f7 100644 --- a/sidebars/docs.js +++ b/sidebars/docs.js @@ -50,11 +50,13 @@ const sidebars = { }, }, + /* { label: 'Developer update', type: 'doc', id: 'devupdate', }, +*/ { label: 'Moodle App', diff --git a/versioned_docs/version-4.1/_utils.tsx b/versioned_docs/version-4.1/_utils.tsx new file mode 100644 index 0000000000..437aa81219 --- /dev/null +++ b/versioned_docs/version-4.1/_utils.tsx @@ -0,0 +1,120 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React, { type ReactNode } from 'react'; +import ComponentFileSummaryGeneric, { + type ComponentFileSummaryProps, +} from '@site/src/components/ComponentFileSummary'; +import { MDXProvider } from '@mdx-js/react'; + +import { getExample } from '@site/src/moodleBridge'; + +export { + getExample, + ComponentFileSummaryProps, +}; + +/** + * Fill the default properties. + * @param {Props} props + * @return {Props} + */ +export const fillDefaultProps = (props: ComponentFileSummaryProps): ComponentFileSummaryProps => ({ + filetype: 'php', + examplePurpose: props.summary, + ...props, +}); + +const normaliseDescription = (Value: ReactNode | string): null | JSX.Element => { + if (typeof Value === 'boolean' || !Value) { + return null; + } + + if (typeof Value === 'string' || React.isValidElement(Value)) { + return ( + + {Value} + + ); + } + + return ( + + + + ); +}; + +/** + * Get the preferred description given a standard properties value which contains an optional description, + * and/or extraDescription, and a DefaultDescription object. + * + * @param {Props} props + * @param {DefaultDescription} DefaultDescription The default description to use if the `description` property is empty + * @returns {MDXLayout} + */ +export const getDescription = ({ + description = null, + extraDescription = null, + children = null, +}: ComponentFileSummaryProps, defaultDescription?: ReactNode | string): null | ReactNode | JSX.Element => { + if (children) { + const Description = normaliseDescription(children); + return ( + + {Description} + + ); + } + + if (description) { + const Description = normaliseDescription(description); + return ( + + {Description} + + ); + } + + const Description = normaliseDescription(defaultDescription); + const ExtraDescription = normaliseDescription(extraDescription); + + if (Description) { + return ( + + {Description} + {ExtraDescription} + + ); + } + + return null; +}; + +export const ComponentFileSummary = (initialProps: ComponentFileSummaryProps): JSX.Element => { + const props = fillDefaultProps({ + examplePurpose: initialProps?.summary ?? null, + ...initialProps, + }); + + props.description = getDescription(props, props?.defaultDescription ?? null); + + if (props?.example || props?.defaultExample) { + props.example = getExample(props, props?.defaultExample ?? null); + } + + return ComponentFileSummaryGeneric(props); +}; diff --git a/versioned_docs/version-4.1/apis.md b/versioned_docs/version-4.1/apis.md new file mode 100644 index 0000000000..1a7619e2c0 --- /dev/null +++ b/versioned_docs/version-4.1/apis.md @@ -0,0 +1,256 @@ +--- +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.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](https://docs.moodle.org/dev/Cache_API) 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. + +### 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. + +### 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](https://docs.moodle.org/dev/External_functions_API) allows you to create fully parametrised methods that can be accessed by external programs (such as [Web services](https://docs.moodle.org/dev/Web_services)). + +### 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](https://docs.moodle.org/dev/Message_API) 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](https://docs.moodle.org/dev/Report_builder_API) allows you to create reports in your plugin, as well as providing custom reporting data which users can use to build their own reports. + +### 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](https://docs.moodle.org/dev/Web_services_API) 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 + +The [Custom fields API](https://docs.moodle.org/dev/Custom_fields_API) 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.1/apis/_files/amd-dir.mdx b/versioned_docs/version-4.1/apis/_files/amd-dir.mdx new file mode 100644 index 0000000000..b22f14b4cd --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/amd-dir.mdx @@ -0,0 +1,10 @@ + +JavaScript in Moodle is written in the ESM format, and transpiled into AMD modules for deployment. + +The [Moodle JavaScript Guide](../guides/javascript) has detailed information and examples on writing JavaScript in Moodle. Further information is also available in the [JavaScript Modules](../../guides/javascript/modules.md) documentation. + +:::caution + +Although the AMD module format is supported, all new JavaScript is written in the EcmaScript Module (ESM) format. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/amd-dir.tsx b/versioned_docs/version-4.1/apis/_files/amd-dir.tsx new file mode 100644 index 0000000000..3587716005 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/amd-dir.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './amd-dir.mdx'; + +const defaultExample = ` +import {fetchThings} from './repository'; + +export const updateThings = (thingData) => { + return fetchThings(thingData); +}; +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/backup-dir.mdx b/versioned_docs/version-4.1/apis/_files/backup-dir.mdx new file mode 100644 index 0000000000..7b77168eb2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/backup-dir.mdx @@ -0,0 +1,7 @@ + +If your plugin stores data then you may need to implement the Backup feature which allows the activity to backed up, restored, and duplicated. + +For more information on Backup and restore, see the following: + +- [Backup 2.0 for developers](https://docs.moodle.org/dev/Backup_2.0_for_developers) +- [Restore 2.0 for developers](https://docs.moodle.org/dev/Restore_2.0_for_developers) diff --git a/versioned_docs/version-4.1/apis/_files/backup-dir.tsx b/versioned_docs/version-4.1/apis/_files/backup-dir.tsx new file mode 100644 index 0000000000..00e6225317 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/backup-dir.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './backup-dir.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/changes.mdx b/versioned_docs/version-4.1/apis/_files/changes.mdx new file mode 100644 index 0000000000..d4386313c8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/changes.mdx @@ -0,0 +1,7 @@ + +If your plugin includes a changelog in its root directory, this will be used to automatically pre-fill the release notes field when uploading new versions of your plugin to the [Plugins directory](https://docs.moodle.org/dev/Plugins_directory). 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.1/apis/_files/changes.tsx b/versioned_docs/version-4.1/apis/_files/changes.tsx new file mode 100644 index 0000000000..7ba853911c --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/changes.tsx @@ -0,0 +1,31 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './changes.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/classes-dir.mdx b/versioned_docs/version-4.1/apis/_files/classes-dir.mdx new file mode 100644 index 0000000000..d5a522cef6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/classes-dir.mdx @@ -0,0 +1,9 @@ + +Moodle supports, and recommends, the use of autoloaded PHP classes. + +By placing files within the `classes` directory or appropriate sub-directories, and with the correct PHP Namespace, and class name, Moodle is able to autoload classes without the need to manually require, or include them. + +Details on these rules and conventions are available in the following documentation: + +- [Coding style - namespace conventions](/general/development/policies/codingstyle#namespaces) +- [Automatic class loading](https://docs.moodle.org/dev/Automatic_class_loading) diff --git a/versioned_docs/version-4.1/apis/_files/classes-dir.tsx b/versioned_docs/version-4.1/apis/_files/classes-dir.tsx new file mode 100644 index 0000000000..7bacc979f6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/classes-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 './classes-dir.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/cli-dir.mdx b/versioned_docs/version-4.1/apis/_files/cli-dir.mdx new file mode 100644 index 0000000000..8757cadb97 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/cli-dir.mdx @@ -0,0 +1,8 @@ + +For plugins which make use of [CLI scripts](https://docs.moodle.org/dev/CLI_scripts), the convention is that these are placed into the `cli` folder to make their purpose clear, and easy to find. + +:::caution + +All CLI scripts **must** declare themselves as being a CLI script by defining the `CLI_SCRIPT` constant to true before including `config.php`. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/cli-dir.tsx b/versioned_docs/version-4.1/apis/_files/cli-dir.tsx new file mode 100644 index 0000000000..94f11fcb00 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/cli-dir.tsx @@ -0,0 +1,40 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './cli-dir.mdx'; + +const defaultExample = `define('CLI_SCRIPT', true); + +require_once(__DIR__ . '/../../config.php'); +require_once("{$CFG->libdir}/clilib.php"); + +// Your CLI features go here. +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-access-php.mdx b/versioned_docs/version-4.1/apis/_files/db-access-php.mdx new file mode 100644 index 0000000000..f4bd89b3a4 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-access-php.mdx @@ -0,0 +1,13 @@ + + +The `db/access.php` file contains the __initial__ configuration for a plugin's access control rules. + +Access control is handled in Moodle by the use of Roles, and Capabilities. You can read more about these in the [Access API](../subsystems/access.md) documentation. + +:::caution Changing initial configuration + +If you make changes to the initial configuration of _existing_ access control rules, these will only take effect for _new installations of your plugin_. Any existing installation **will not** be updated with the latest configuration. + +Updating existing capability configuration for an installed site is not recommended as it may have already been modified by an administrator. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/db-access-php.tsx b/versioned_docs/version-4.1/apis/_files/db-access-php.tsx new file mode 100644 index 0000000000..877cd86aae --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-access-php.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-access-php.mdx'; + +const defaultExample = `$capabilities = [ + // Ability to use the plugin. + 'plugintype/pluginname:useplugininstance' => [ + 'riskbitmask' => RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ], + ], +]; +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-events-php.mdx b/versioned_docs/version-4.1/apis/_files/db-events-php.mdx new file mode 100644 index 0000000000..11667d3c5e --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-events-php.mdx @@ -0,0 +1,14 @@ + +Moodle supports a feature known as _ [Event observers](https://docs.moodle.org/dev/Events_API#Event_observers) _ to allow components to make changes when certain events take place. + +The `db/events.php` file allows you define any event subscriptions that your plugin needs to listen for. + +Event subscriptions are a convenient way to observe events generated elsewhere in Moodle. + +:::caution Communication between components + +You _should not_ use event subscriptions to subscribe to events belonging to other plugins, without defining a dependency upon that plugin. + +See the [Component communication principles](/general/development/policies/component-communication#event-observers) documentation for a description of some of the risks of doing so. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/db-events-php.tsx b/versioned_docs/version-4.1/apis/_files/db-events-php.tsx new file mode 100644 index 0000000000..71e928813f --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-events-php.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-events-php.mdx'; + +const defaultExample = `$observers = [ + [ + 'eventname' => '\\core\\event\\course_module_created', + 'callback' => '\\plugintype_pluginname\\event\\observer\\course_module_created::store', + 'priority' => 1000, + ], +]; +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-install-php.mdx b/versioned_docs/version-4.1/apis/_files/db-install-php.mdx new file mode 100644 index 0000000000..e91709f741 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-install-php.mdx @@ -0,0 +1,10 @@ + +The `db/install.php` file allows you define a post-installation hook, which is called immediately after the initial creation of your database schema. + +:::caution + +This file is not used at all after the _initial_ installation of your plugin. + +It is _not called_ during any upgrade. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/db-install-php.tsx b/versioned_docs/version-4.1/apis/_files/db-install-php.tsx new file mode 100644 index 0000000000..3657478436 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-install-php.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-install-php.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-install-xml.mdx b/versioned_docs/version-4.1/apis/_files/db-install-xml.mdx new file mode 100644 index 0000000000..4af56d4989 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-install-xml.mdx @@ -0,0 +1,8 @@ + +The `install.xml` file is used to define any database tables, fields, indexes, and keys, which should be created for a plugin during its initial installation. + +:::caution + +When creating or updating the `install.xml` you **must** use the built-in [XMLDB editor](https://docs.moodle.org/dev/XMLDB_Documentation) within Moodle. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/db-messages-php.mdx b/versioned_docs/version-4.1/apis/_files/db-messages-php.mdx new file mode 100644 index 0000000000..000225f1dc --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-messages-php.mdx @@ -0,0 +1,4 @@ + +The `db/messages.php` file allows you to declare the messages that your plugin sends. + +See the [Message API](https://docs.moodle.org/dev/Message_API) documentation for further information. diff --git a/versioned_docs/version-4.1/apis/_files/db-messages-php.tsx b/versioned_docs/version-4.1/apis/_files/db-messages-php.tsx new file mode 100644 index 0000000000..f8c576ec84 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-messages-php.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-messages-php.mdx'; + +const defaultExample = ` +$messageproviders = [ + 'things' => [ + 'defaults' => [ + 'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED, + ], + ], +]; +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-mobile-php.mdx b/versioned_docs/version-4.1/apis/_files/db-mobile-php.mdx new file mode 100644 index 0000000000..b16a65f3c1 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-mobile-php.mdx @@ -0,0 +1,6 @@ + +The Moodle Mobile remote add-on is the mobile app version of the plugin that will be loaded when a user accesses the plugin on the app. + +A plugin can include several Mobile add-ons. Each add-on must indicate a unique name. + +See the [Moodle App Plugins development guide](/general/app/development/plugins-development-guide) for more information on configuring your plugin for the Moodle App. diff --git a/versioned_docs/version-4.1/apis/_files/db-mobile-php.tsx b/versioned_docs/version-4.1/apis/_files/db-mobile-php.tsx new file mode 100644 index 0000000000..322b29f8f9 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-mobile-php.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-mobile-php.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-renamedclasses-php.mdx b/versioned_docs/version-4.1/apis/_files/db-renamedclasses-php.mdx new file mode 100644 index 0000000000..91e69e0053 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-renamedclasses-php.mdx @@ -0,0 +1,8 @@ + +Details of classes that have been renamed to fit in with autoloading. See [forum discussion](https://moodle.org/mod/forum/discuss.php?d=262403) for details. + +:::note +Adding renamed or moved classes to `renamedclasses.php` is only necessary when the class is part of the component's API where it can be reused by other components, especially by third-party plugins. This is to maintain backwards-compatibility in addition to autoloading purposes. + +If the renamed or moved class is private/internal to the component and is not subject for external use, there is no need to add it to `renamedclasses.php`. +::: diff --git a/versioned_docs/version-4.1/apis/_files/db-renamedclasses-php.tsx b/versioned_docs/version-4.1/apis/_files/db-renamedclasses-php.tsx new file mode 100644 index 0000000000..ef6d5b81d6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-renamedclasses-php.tsx @@ -0,0 +1,49 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-renamedclasses-php.mdx'; + +const defaultExample = ` +defined('MOODLE_INTERNAL') || die; + +$renamedclasses = [ + 'old_class_name' => 'fully_qualified\\\\new\\\\name', + + // Examples: + 'assign_header' => 'mod_assign\\\\output\\\\header', + '\\assign_header' => 'mod_assign\\\\output\\\\header', + '\\assign' => 'mod_assign\\\\assignment', + + // Incorrect: + // The new class name should _not_ have a leading \\. + 'assign_header' => '\\\\mod_assign\\\\output\\\\header', +]; +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-services-php.mdx b/versioned_docs/version-4.1/apis/_files/db-services-php.mdx new file mode 100644 index 0000000000..c02c69a3ff --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-services-php.mdx @@ -0,0 +1,16 @@ + +The `db/services.php` file is used to describe the external functions available for use in web services. This includes + +web service functions defined for JavaScript, and for the [Moodle Mobile App](/general/app). + +:::note + +Web services should be named following the [naming convention for web services](https://docs.moodle.org/dev/Web_service_API_functions#Naming_convention). + +::: + +For further information on external functions and web services, see: + +- [Adding a web service to a plugin](https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin) +- [Web services API](https://docs.moodle.org/dev/Web_services_API) +- [External functions API](https://docs.moodle.org/dev/External_functions_API) diff --git a/versioned_docs/version-4.1/apis/_files/db-services-php.tsx b/versioned_docs/version-4.1/apis/_files/db-services-php.tsx new file mode 100644 index 0000000000..289114d035 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-services-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-services-php.mdx'; + +const defaultExample = ` +$functions = [ + 'plugintype_pluginname_create_things' => [ + 'classname' => 'plugintype_pluginname\\external\\create_things', + 'methodname' => 'execute', + 'description' => 'Create a new thing', + 'type' => 'write', + 'capabilities' => 'plugintype/pluginname:create_things', + 'ajax' => true, + 'services' => [ + MOODLE_OFFICIAL_MOBILE_SERVICE, + ], + ], +]; +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-tasks-example.php b/versioned_docs/version-4.1/apis/_files/db-tasks-example.php new file mode 100644 index 0000000000..1c8ac31514 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-tasks-example.php @@ -0,0 +1,11 @@ +$tasks = [ + [ + 'classname' => 'mod_example\task\do_something', + 'blocking' => 0, + 'minute' => '30', + 'hour' => '17', + 'day' => '*', + 'month' => '1,7', + 'dayofweek' => '0', + ], +]; diff --git a/versioned_docs/version-4.1/apis/_files/db-tasks-php.mdx b/versioned_docs/version-4.1/apis/_files/db-tasks-php.mdx new file mode 100644 index 0000000000..ea33364e06 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-tasks-php.mdx @@ -0,0 +1,18 @@ + + +The `db/tasks.php` file contains the initial schedule configuration for each of your plugins _scheduled_ tasks. Adhoc tasks are not run on a regular schedule and therefore are not described in this file. + +:::caution Editing the schedule for an existing task + +If an existing task is edited, it will only be updated in the database if the administrator has not customised the schedule of that task in any way. + +::: + +The following fields also accept a value of `R`, which indicates that Moodle should choose a random value for that field: + +- minute +- hour +- dayofweek +- day + +See [db/tasks.php](../commonfiles/db-tasks.php/index.md) for full details of the file format. diff --git a/versioned_docs/version-4.1/apis/_files/db-tasks-php.tsx b/versioned_docs/version-4.1/apis/_files/db-tasks-php.tsx new file mode 100644 index 0000000000..68c9ac809b --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-tasks-php.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary, type ComponentFileSummaryProps } from '../../_utils'; +import DefaultDescription from './db-tasks-php.mdx'; +// eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved +import DefaultExample from '!!raw-loader!./db-tasks-example.php'; + +export default (initialProps: ComponentFileSummaryProps): JSX.Element => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-uninstall-php.mdx b/versioned_docs/version-4.1/apis/_files/db-uninstall-php.mdx new file mode 100644 index 0000000000..5ed3089e4b --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-uninstall-php.mdx @@ -0,0 +1,2 @@ + +The `db/uninstall.php` file allows you define a pre-uninstallation hook, which is called immediately before all table and data from your plugin are removed. diff --git a/versioned_docs/version-4.1/apis/_files/db-uninstall-php.tsx b/versioned_docs/version-4.1/apis/_files/db-uninstall-php.tsx new file mode 100644 index 0000000000..8d4f8c2081 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-uninstall-php.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-uninstall-php.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/db-upgrade-php.mdx b/versioned_docs/version-4.1/apis/_files/db-upgrade-php.mdx new file mode 100644 index 0000000000..904cb5d8f2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-upgrade-php.mdx @@ -0,0 +1,30 @@ + +The `db/upgrade.php` file contains upgrade steps, including database schema changes, changes to settings, and other steps which must be performed during upgrade. + +See the [Upgrade API](../../guides/upgrade/index.md) documentation for further information. + +:::danger Generating Database Schema changes + +When making changes to the database schema you **must** use the build-in [XMLDB editor](https://docs.moodle.org/dev/XMLDB_Documentation) within +Moodle. This can be used to generate php upgrade steps. + +The [install.xml](#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](https://docs.moodle.org/dev/XMLDB_editor) 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.1/apis/_files/db-upgrade-php.tsx b/versioned_docs/version-4.1/apis/_files/db-upgrade-php.tsx new file mode 100644 index 0000000000..0078065bb2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/db-upgrade-php.tsx @@ -0,0 +1,55 @@ +/** + * 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'); + } +}`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/environment-xml.mdx b/versioned_docs/version-4.1/apis/_files/environment-xml.mdx new file mode 100644 index 0000000000..62af6b1660 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/environment-xml.mdx @@ -0,0 +1,4 @@ + +A plugin can declare its own environment requirements, in addition to those declared by Moodle core. These may includes features such as PHP extension requirements, version requirements, and similar items. + +Further information on this file and its format can be found in the [Environment checking](https://docs.moodle.org/dev/Environment_checking) documentation. diff --git a/versioned_docs/version-4.1/apis/_files/environment-xml.tsx b/versioned_docs/version-4.1/apis/_files/environment-xml.tsx new file mode 100644 index 0000000000..ce2afb8324 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/environment-xml.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './environment-xml.mdx'; + +const defaultExample = ` + + + + + + + + +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/index.tsx b/versioned_docs/version-4.1/apis/_files/index.tsx new file mode 100644 index 0000000000..d106049452 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/index.tsx @@ -0,0 +1,78 @@ +/** + * 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 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, + 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.1/apis/_files/install-xml.tsx b/versioned_docs/version-4.1/apis/_files/install-xml.tsx new file mode 100644 index 0000000000..98b03c8821 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/install-xml.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './db-install-xml.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/lang-extra.md b/versioned_docs/version-4.1/apis/_files/lang-extra.md new file mode 100644 index 0000000000..f6f10fb772 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/lang-extra.md @@ -0,0 +1,15 @@ + + +:::caution Activity modules are different + +Activity modules do not use the __frankenstyle__ name as a filename, they use the plugin name. For example the forum activity plugin: + +```php +// Plugin type: `mod` +// Plugin name: `forum` +// Frankenstyle plugin name: `mod_forum` +// Plugin location: `mod/forum` +// Language string location: `mod/forum/lang/en/forum.php` +``` + +::: diff --git a/versioned_docs/version-4.1/apis/_files/lang.md b/versioned_docs/version-4.1/apis/_files/lang.md new file mode 100644 index 0000000000..c73920bb90 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/lang.md @@ -0,0 +1,25 @@ + + +Each plugin must define a set of language strings with, at a minimum, an English translation. These are specified in the plugin's `lang/en` directory in a file named after the plugin. For example the LDAP authentication plugin: + +```php +// Plugin type: `auth` +// Plugin name: `ldap` +// Frankenstyle plugin name: `auth_ldap` +// Plugin location: `auth/ldap` +// Language string location: `auth/ldap/lang/en/auth_ldap.php` +``` + +:::warning + +Every plugin _must_ define the name of the plugin, or its `pluginname`. + +::: + +The `get_string` API can be used to translate a string identifier back into a translated string. + +``` +get_string('pluginname', '[plugintype]_[pluginname]'); +``` + +- See the [String API](https://docs.moodle.org/dev/String_API#Adding_language_file_to_plugin) documentation for more information on language files. diff --git a/versioned_docs/version-4.1/apis/_files/lang.tsx b/versioned_docs/version-4.1/apis/_files/lang.tsx new file mode 100644 index 0000000000..8356a3d5af --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/lang.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { ComponentFileSummaryProps } from '../../_utils'; +import DefaultDescription from './lang.md'; + +const defaultExample = "$string['pluginname'] = 'The name of my plugin will go here';"; + +export default (initialProps: ComponentFileSummaryProps): JSX.Element => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/lib.mdx b/versioned_docs/version-4.1/apis/_files/lib.mdx new file mode 100644 index 0000000000..c6a7a4f9e3 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/lib.mdx @@ -0,0 +1,11 @@ + + +The `lib.php` file is a legacy file which acts as a bridge between Moodle core, and the plugin. In recent plugins it is should only used to define callbacks and related functionality which currently is not supported as an auto-loadable class. + +All functions defined in this file **must** meet the requirements set out in the relevant section of the [Coding style](/general/development/policies/codingstyle#Functions-and-Methods). + +:::note Performance impact + +Moodle core often loads all the lib.php files of a given plugin types. For performance reasons, it is strongly recommended to keep this file as small as possible and have just required code implemented in it. All the plugin's internal logic should be implemented in the auto-loaded classes. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/lib.tsx b/versioned_docs/version-4.1/apis/_files/lib.tsx new file mode 100644 index 0000000000..bc3ddec6b9 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/lib.tsx @@ -0,0 +1,32 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './lib.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/locallib.mdx b/versioned_docs/version-4.1/apis/_files/locallib.mdx new file mode 100644 index 0000000000..524ce83ac8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/locallib.mdx @@ -0,0 +1,14 @@ + + + +:::caution Legacy feature + +The use of this file is no longer recommended, and new uses of it will not be permitted in core code. + +::: + +Rather than creating global functions in a global namespace in a locallib.php file, you should use autoloaded classes which are located in the classes/ directory. + +Where this file is in use, all functions **must** meet the requirements set out in the relevant section of the [Coding style](/general/development/policies/codingstyle#Functions-and-Methods) + +Existing functions which have been incorrectly named **will not** be accepted as an example of an existing convention. Existing functions which are incorrectly named **should** be converted to use a namespaced class. diff --git a/versioned_docs/version-4.1/apis/_files/locallib.tsx b/versioned_docs/version-4.1/apis/_files/locallib.tsx new file mode 100644 index 0000000000..0093700154 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/locallib.tsx @@ -0,0 +1,31 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './locallib.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/pix-dir.mdx b/versioned_docs/version-4.1/apis/_files/pix-dir.mdx new file mode 100644 index 0000000000..f58f9c6fee --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/pix-dir.mdx @@ -0,0 +1,6 @@ + +Plugins can provide icons in several formats, and most plugin types require that a default icon be provided. + +Where a browser supports it, the `svg` format is used, falling back to `png` formats when an SVG is unavailable. + +Full details of the correct naming, sizing, and design guidelines for icons in Moodle can be found in the [Moodle icons](https://docs.moodle.org/dev/Moodle_icons) documentation. diff --git a/versioned_docs/version-4.1/apis/_files/pix-dir.tsx b/versioned_docs/version-4.1/apis/_files/pix-dir.tsx new file mode 100644 index 0000000000..cdb803f34a --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/pix-dir.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './pix-dir.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/readme.mdx b/versioned_docs/version-4.1/apis/_files/readme.mdx new file mode 100644 index 0000000000..185121a363 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/readme.mdx @@ -0,0 +1,4 @@ + +We recommend that you include any additional information for your plugin in a project readme file. Ideally this should act as an offline version of all information in your plugin's page in the [Plugins directory](https://docs.moodle.org/dev/Plugins_directory). + +We recommend creating your readme file in either a `README.md`, or `README.txt` format. diff --git a/versioned_docs/version-4.1/apis/_files/readme.tsx b/versioned_docs/version-4.1/apis/_files/readme.tsx new file mode 100644 index 0000000000..8999ddaccc --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/readme.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './readme.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/readme_moodle-txt.mdx b/versioned_docs/version-4.1/apis/_files/readme_moodle-txt.mdx new file mode 100644 index 0000000000..ed589cbfd4 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/readme_moodle-txt.mdx @@ -0,0 +1,5 @@ + +When importing a third-party library into your plugin, it is advisable to create a `readme_moodle.txt` file detailing relevant information, including: + +- Download URLs +- Build instructions diff --git a/versioned_docs/version-4.1/apis/_files/readme_moodle-txt.tsx b/versioned_docs/version-4.1/apis/_files/readme_moodle-txt.tsx new file mode 100644 index 0000000000..55827c08aa --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/readme_moodle-txt.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './readme_moodle-txt.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/settings-php.mdx b/versioned_docs/version-4.1/apis/_files/settings-php.mdx new file mode 100644 index 0000000000..04b942c4c9 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/settings-php.mdx @@ -0,0 +1,16 @@ + +You can define settings for your plugin that the administrator can configure by creating a `settings.php` file in the root of your plugins' directory. + +:::caution + +Settings must named in the following format: + +``` +plugintype_pluginname/settingname +``` + +By following the correct naming, all settings will automatically be stored in the `config_plugins` database table. + +::: + +Full details on how to create settings are available in the [Admin settings](../subsystems/admin/index.md) documentation. diff --git a/versioned_docs/version-4.1/apis/_files/settings-php.tsx b/versioned_docs/version-4.1/apis/_files/settings-php.tsx new file mode 100644 index 0000000000..9dd13af88e --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/settings-php.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './settings-php.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/styles-css.mdx b/versioned_docs/version-4.1/apis/_files/styles-css.mdx new file mode 100644 index 0000000000..e62a0dd592 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/styles-css.mdx @@ -0,0 +1,11 @@ + +Plugins may define a '/styles.css' to provide plugin-specific styling. See the following for further documentation: + +- [Plugin contribution checklist#CSS styles](https://docs.moodle.org/dev/Plugin_contribution_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.1/apis/_files/styles-css.tsx b/versioned_docs/version-4.1/apis/_files/styles-css.tsx new file mode 100644 index 0000000000..21ea5adcc5 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/styles-css.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './styles-css.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/thirdpartylibs-xml.mdx b/versioned_docs/version-4.1/apis/_files/thirdpartylibs-xml.mdx new file mode 100644 index 0000000000..301dde8126 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/thirdpartylibs-xml.mdx @@ -0,0 +1,14 @@ + +Details of all third-party libraries should be declared in the `thirdpartylibs.xml` file. + +This information is used to generate ignore file configuration for linting tools. For Moodle core it is also used to generate library information as part of release notes and credits. + +Within the XML the `location` is a file, or directory, relative to your plugin's root. + +:::caution Licensing + +The license of any third-party code included in your plugin, and within the `thirdpartylibs.xml` file **must** be [compatible with the GNU GPLv3](http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses). + +::: + +See the [Third Party Libraries](https://docs.moodle.org/dev/Third_Party_Libraries) documentation for further information. diff --git a/versioned_docs/version-4.1/apis/_files/thirdpartylibs-xml.tsx b/versioned_docs/version-4.1/apis/_files/thirdpartylibs-xml.tsx new file mode 100644 index 0000000000..abcbcdf980 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/thirdpartylibs-xml.tsx @@ -0,0 +1,51 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './thirdpartylibs-xml.mdx'; + +const defaultExample = ` + + + javascript/html5shiv.js + Html5Shiv + 3.6.2 + Apache + 2.0 + + + vendor/guzzle/guzzle/ + guzzle + v3.9.3 + MIT + + +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/upgrade-php.mdx b/versioned_docs/version-4.1/apis/_files/upgrade-php.mdx new file mode 100644 index 0000000000..b8414b431e --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/upgrade-php.mdx @@ -0,0 +1,13 @@ + +The `db/upgrade.php` file contains upgrade steps, including database schema changes, changes to settings, and other steps which must be performed during upgrade. + +See the [Upgrade API](../../guides/upgrade/index.md) documentation for further information. + +:::danger Generating Database Schema changes + +When making changes to the database schema you **must** use the build-in [XMLDB editor](https://docs.moodle.org/dev/XMLDB_Documentation) within +Moodle. This can be used to generate php upgrade steps. + +The [install.xml](#dbinstallxml) schema must match the schema generated by the upgrade at all times. + +::: diff --git a/versioned_docs/version-4.1/apis/_files/upgrade-txt.mdx b/versioned_docs/version-4.1/apis/_files/upgrade-txt.mdx new file mode 100644 index 0000000000..5397e05ba0 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/upgrade-txt.mdx @@ -0,0 +1,56 @@ + +Each component and subsystem may make use of an `upgrade.txt` file in the top level folder. A section title is used to identify the Moodle version where the change was introduced, and significant changes for that version relating to that component or subsystem are noted. + +For example, given an API change is applied for the upcoming Moodle version 4.1 which is still in the **master** 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 master 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 master 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 **master** 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 **master** 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 **master** 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 master 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: master (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 **master** (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 **master** and **MOODLE_400_STABLE** branches. + +```txt title="Example 4a: master (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.1/apis/_files/upgrade-txt.tsx b/versioned_docs/version-4.1/apis/_files/upgrade-txt.tsx new file mode 100644 index 0000000000..db928bfec3 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/upgrade-txt.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './upgrade-txt.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/_files/version-php.mdx b/versioned_docs/version-4.1/apis/_files/version-php.mdx new file mode 100644 index 0000000000..23c9c7847a --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/version-php.mdx @@ -0,0 +1,11 @@ + +The version.php contains metadata about the plugin. + +It is used during the installation and upgrade of the plugin. + +This file contains metadata used to describe the plugin, and includes information such as: + +- the version number +- a list of dependencies +- the minimum Moodle version required +- maturity of the plugin diff --git a/versioned_docs/version-4.1/apis/_files/version-php.tsx b/versioned_docs/version-4.1/apis/_files/version-php.tsx new file mode 100644 index 0000000000..f855221dcb --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/version-php.tsx @@ -0,0 +1,51 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import { type ComponentFileSummaryProps } from '@site/src/components/ComponentFileSummary'; +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import DefaultDescription from './version-php.mdx'; + +const defaultExample = `defined('MOODLE_INTERNAL') || die(); + +$plugin->version = TODO; +$plugin->requires = TODO; +$plugin->supported = TODO; // Available as of Moodle 3.9.0 or later. +$plugin->incompatible = TODO; // Available as of Moodle 3.9.0 or later. +$plugin->component = 'TODO_FRANKENSTYLE'; +$plugin->maturity = MATURITY_STABLE; +$plugin->release = 'TODO'; + +$plugin->dependencies = [ + 'mod_forum' => 2022042100, + 'mod_data' => 2022042100 +]; +`; + +export default function VersionPHP(props: ComponentFileSummaryProps): JSX.Element { + return ( + + ); +} diff --git a/versioned_docs/version-4.1/apis/_files/yui-dir.mdx b/versioned_docs/version-4.1/apis/_files/yui-dir.mdx new file mode 100644 index 0000000000..ec589174f9 --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/yui-dir.mdx @@ -0,0 +1,11 @@ + +In older versions of Moodle, JavaScript was written in the YUI format. This is being phased out in favour of [JavaScript Modules](../../guides/javascript/modules.md), although some older uses still remain in Moodle core. + +- [YUI/Modules](../../guides/javascript/yui/modules.md) +- [YUI](../../guides/javascript/yui/index.md) + +:::caution + +New YUI code will not be accepted into Moodle core, except for new plugins for the [Atto editor](https://docs.moodle.org/dev/Atto). + +::: diff --git a/versioned_docs/version-4.1/apis/_files/yui-dir.tsx b/versioned_docs/version-4.1/apis/_files/yui-dir.tsx new file mode 100644 index 0000000000..fec0d588eb --- /dev/null +++ b/versioned_docs/version-4.1/apis/_files/yui-dir.tsx @@ -0,0 +1,32 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../_utils'; +import type { Props } from '../../_utils'; +import DefaultDescription from './yui-dir.mdx'; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/commonfiles/db-tasks.php/index.md b/versioned_docs/version-4.1/apis/commonfiles/db-tasks.php/index.md new file mode 100644 index 0000000000..34f5cb52d5 --- /dev/null +++ b/versioned_docs/version-4.1/apis/commonfiles/db-tasks.php/index.md @@ -0,0 +1,154 @@ +--- +title: db/tasks.php +tags: + - Plugins + - Common files + - Scheduled tasks +description: A description of the plugin scheduled task configuration file +--- + +import { LanguageProperty, Since } 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.1/apis/commonfiles/index.mdx b/versioned_docs/version-4.1/apis/commonfiles/index.mdx new file mode 100644 index 0000000000..19c8ab7ee2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/commonfiles/index.mdx @@ -0,0 +1,164 @@ +--- +title: Common files +tags: + - Plugins + - API + - Subsystem +--- + +import { + AmdDir, + BackupDir, + CLIDir, + Changes, + ClassesDir, + DbAccessPHP, + DbEventsPHP, + DbInstallPHP, + DbInstallXML, + DbMessagesPHP, + 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 + + + +### 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](https://docs.moodle.org/dev/Plugin_contribution_checklist) - read before submitting a plugin diff --git a/versioned_docs/version-4.1/apis/commonfiles/tag.php/index.md b/versioned_docs/version-4.1/apis/commonfiles/tag.php/index.md new file mode 100644 index 0000000000..bdd88ebe94 --- /dev/null +++ b/versioned_docs/version-4.1/apis/commonfiles/tag.php/index.md @@ -0,0 +1,112 @@ +--- +title: tag.php +tags: + - Plugins + - Tags + - API +description: A description of the library tag.php file, describing what plugins have tags where their callbacks are located. +--- + +import { LanguageProperty, Since } 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.1/apis/commonfiles/version.php/index.md b/versioned_docs/version-4.1/apis/commonfiles/version.php/index.md new file mode 100644 index 0000000000..4c69db1155 --- /dev/null +++ b/versioned_docs/version-4.1/apis/commonfiles/version.php/index.md @@ -0,0 +1,210 @@ +--- +title: version.php +tags: + - Plugins + - Common files + - Plugin types +description: A description of the plugin version.php file, describing the various features +--- + +import { LanguageProperty, Since } 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.1/apis/core/activitycompletion/index.md b/versioned_docs/version-4.1/apis/core/activitycompletion/index.md new file mode 100644 index 0000000000..11dbce63fb --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/activitycompletion/index.md @@ -0,0 +1,436 @@ +--- +title: Activity completion API +tags: + - Conditional activities + - API +documentationDraft: true +--- + +:::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. + +#### 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', 'completionpostsenabled', ' ', get_string('completionposts', 'forum')), + $mform->createElement('text', 'completionposts', ' ', ['size' => 3]), + ]; + $mform->setType('completionposts', PARAM_INT); + $mform->addGroup($group, 'completionpostsgroup', get_string('completionpostsgroup','forum'), [' '], false); + $mform->addHelpButton('completionpostsgroup', 'completionposts', 'forum'); + $mform->disabledIf('completionposts', 'completionpostsenabled', 'notchecked'); + + return ['completionpostsgroup']; +} +``` + +- 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['completionpostsenabled']) && $data['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->completion) && $data->completion==COMPLETION_TRACKING_AUTOMATIC; + if (empty($data->completionpostsenabled) || !$autocompletion) { + $data->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['completionpostsenabled']= + !empty($default_values['completionposts']) ? 1 : 0; + if(empty($default_values['completionposts'])) { + $default_values['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.1/apis/core/calendar/_index/my_overview_sam_student.png b/versioned_docs/version-4.1/apis/core/calendar/_index/my_overview_sam_student.png new file mode 100644 index 0000000000..4e33b2d305 Binary files /dev/null and b/versioned_docs/version-4.1/apis/core/calendar/_index/my_overview_sam_student.png differ diff --git a/versioned_docs/version-4.1/apis/core/calendar/index.md b/versioned_docs/version-4.1/apis/core/calendar/index.md new file mode 100644 index 0000000000..6818ffb379 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/calendar/index.md @@ -0,0 +1,479 @@ +--- +title: Calendar API +tags: [] +--- + +This page documents the Calendar API as it is in Moodle 3.3 and later. For the API in older versions of Moodle, see [Calendar API old](https://docs.moodle.org/dev/Calendar_API_old). + +The Calendar API allows you to add, modify and delete events in the calendar for user, groups, courses and the site. As of 3.3 it also allows you to provide actions for these events so that they are then displayed on block_myoverview, which by default is shown on users' dashboard. + +## Overview + +The Moodle [Calendar](https://docs.moodle.org/en/Calendar) collects and displays calendar events to users. These events are generated by other plugins, like activities, to let the user know of an important date. For example, when an assignment opens for submission. + +The block_myoverview plugin displays calendar events that have an action associated with them. For example, an activity may have a due date specified, in which case it will create a calendar action event so that the event will display on the dashboard for the user, as well as the calendar. In order to provide the action associated for this event you have to define a callback in your plugin which is detailed below. + +## Creating an event + +Creating a new calendar event. The optional parameter `$checkcapability` is used to check user's capability to add events. By default the `$checkcapability` parameter is set to true. You should set it to false if you have already checked that the user has the capabilities required for the event to be created, for example when an activity is creating an event based on a deadline. + +```php +require_once($CFG->dirroot.'/calendar/lib.php'); + +$event = new stdClass(); +$event->eventtype = SCORM_EVENT_TYPE_OPEN; // Constant defined somewhere in your code - this can be any string value you want. It is a way to identify the event. +$event->type = CALENDAR_EVENT_TYPE_STANDARD; // This is used for events we only want to display on the calendar, and are not needed on the block_myoverview. +$event->name = get_string('calendarstart', 'scorm', $scorm->name); +$event->description = format_module_intro('scorm', $scorm, $cmid, false); +$event->format = FORMAT_HTML; +$event->courseid = $scorm->course; +$event->groupid = 0; +$event->userid = 0; +$event->modulename = 'scorm'; +$event->instance = $scorm->id; +$event->timestart = $scorm->timeopen; +$event->visible = instance_is_visible('scorm', $scorm); +$event->timeduration = 0; + +calendar_event::create($event); +``` + +## Updating an event + +You can update an existing event in database by providing at least the event id. If the event is a part of a chain of repeated events, the rest of series event will also be updated (depending on the value of property `repeateditall`). This function could also be used to insert new event to database, if the given event does not exist yet. The optional parameter `$checkcapability` is used to check user's capability to edit/add events. By default the `$checkcapability` parameter is set to true. You should set it to false if you have already checked that the user has the capabilities required for the event to be updated, for example when an activity is updating an event based on a change to it's settings. + +```php +$eventid = required_param('id', PARAM_INT); +$event = calendar_event::load($eventid); + +$data = $mform->get_data(); +$event->update($data); +``` + +## Deleting an event + +You can delete an existing event from the database. The optional parameter `$deleterepeated` is used as an indicator to remove the rest of repeated events. The default value for `$deleterepeated` is true. Deleting an event will also delete all associated files related to the event's editor context. + +```php +$eventid = required_param('id', PARAM_INT); +$event = calendar_event::load($eventid); +$event->delete($repeats); +``` + +## Event priority + +There might be cases that an activity event will have user and/or group overrides. Therefore we need a way to show only the relevant event on the user's calendar. This is where the 'priority' field comes in. + +The event priority is set to the following: + +- NULL for non-override events. + +```php +$event->priority = null; +``` + +- 0 for user override events. + +```php +$event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY; +``` + +- A positive integer for group events. + +For integer and non-null event priorities, the lower the value, the higher the priority is. Meaning, user overrides always have a higher priority than group overrides. Group override priorities are currently being determined in two ways in core activities: + +1. In the assignment module, the event priorities for group overrides are being determined from the `sortorder` column in the 'assign_overrides' table. +1. In the lesson and quiz modules, the event priorities for group overrides are being calculated using the functions `lesson_get_group_override_priorities($lessonid)` and `quiz_get_group_override_priorities($quizid)`. + +Should you ever decide to sort out group override priorities by implementing `*_get_group_override_priorities()`, the recommended return structure would be something like + +```php +[ + 'youreventtype1' => $prioritiesforeventtype1, + ... +] +``` + +where '$prioritiesforeventtype1' is an associative array that has the timestamp of the group override event as key and the calculated priority as value. For more details, please see the implementation for the lesson module below: + +```php +/** + * Calculates the priorities of timeopen and timeclose values for group overrides for a lesson. + * + * @param int $lessonid The lesson ID. + * @return array|null Array of group override priorities for open and close times. Null if there are no group overrides. + */ +function lesson_get_group_override_priorities($lessonid) { + global $DB; + + // Fetch group overrides. + $where = 'lessonid = :lessonid AND groupid IS NOT NULL'; + $params = ['lessonid' => $lessonid]; + $overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline'); + if (!$overrides) { + return null; + } + + $grouptimeopen = []; + $grouptimeclose = []; + foreach ($overrides as $override) { + if ($override->available !== null && !in_array($override->available, $grouptimeopen)) { + $grouptimeopen[] = $override->available; + } + if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) { + $grouptimeclose[] = $override->deadline; + } + } + + // Sort open times in ascending manner. The earlier open time gets higher priority. + sort($grouptimeopen); + // Set priorities. + $opengrouppriorities = []; + $openpriority = 1; + foreach ($grouptimeopen as $timeopen) { + $opengrouppriorities[$timeopen] = $openpriority++; + } + + // Sort close times in descending manner. The later close time gets higher priority. + rsort($grouptimeclose); + // Set priorities. + $closegrouppriorities = []; + $closepriority = 1; + foreach ($grouptimeclose as $timeclose) { + $closegrouppriorities[$timeclose] = $closepriority++; + } + + return [ + 'open' => $opengrouppriorities, + 'close' => $closegrouppriorities + ]; +} +``` + +## Action events + +Action events are calendar events that can be actioned. E.g. A student submitting an assignment by a certain date. These events are displayed on the block_myoverview which by default is on users' dashboard. Creating these is the same as creating a normal calendar event except instead of using CALENDAR_EVENT_TYPE_STANDARD as your calendar event type, you use CALENDAR_EVENT_TYPE_ACTION. The events are also sorted on the dashboard by the value specified in the 'timesort' field (unixtime) for the event. + +Example of the changes to the above code would be to change the `type` and to specify the `timesort` value. + +```php +$event->type = CALENDAR_EVENT_TYPE_ACTION; +$event->timesort = $scorm->timeclose; +``` + +![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.1/apis/core/conditionalactivities/index.md b/versioned_docs/version-4.1/apis/core/conditionalactivities/index.md new file mode 100644 index 0000000000..eac7de5ed4 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/conditionalactivities/index.md @@ -0,0 +1,220 @@ +--- +title: Conditional activities API +tags: [] +documentationDraft: true +--- + +The Conditional Activities API allowsyou to specify whether to hide, or show, an activity when a series of conditions associated with it are met. + +:::note + +This should not be confused with the [completion API](../activitycompletion/index.md) which is used to mark if an activity is completed or not. The Conditional Activities API is used to handle the _availability_ of an activity, whilst the Completion API helps to track the _progress_ of student in an activity. + +::: + +## Files + +The main file containing all key functions is located at `lib/conditionlib.php`.. + +## Functions and Examples + +The class `condition_info` defined in `lib/conditionlib.php` is the main conditional API in Moodle. Following are some important methods of the above mentioned class. + +```php +fill_availability_conditions($cm) +get_full_course_module() +add_completion_condition($cmid, $requiredcompletion) +add_grade_condition($gradeitemid, $min, $max, $updateinmemory = false) +wipe_conditions() +get_full_information($modinfo = null) +is_available($information, $grabthelot = false, $userid = 0, $modinfo = null) +show_availability() +update_cm_from_form($cm, $fromform, $wipefirst=true) +``` + +The basic functionality of these methods can be classified as:- + +1. Fetching information related to conditions +1. Adding/Updating conditional clauses to activities +1. Deleting conditions attached to activities + +### Fetching information related to conditions + +The following functions are normally used to fetch information regarding conditions associated with activities: + +```php +get_full_course_module(); +get_full_information($modinfo=null); +is_available($information, $grabthelot = false, $userid = 0, $modinfo = null); +show_availability(); +``` + +#### get_full_course_module() + +This method can fetches and returns all necessary information as a course module object which are required to determine the availability conditions. + +Example:- + +```php +$cm->id = $id; +$test = new condition_info($cm, CONDITION_MISSING_EVERYTHING); +$fullcm = $test->get_full_course_module(); +``` + +#### get_full_information() + +This function returns a string which describes the various conditions in place for the activity in the given context.Some possible outputs can be:- + +```php + a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact) + b) From 14 Oct until 12:11 on 17 Oct (midnight, exact) + c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct) +``` + +Please refer to the inline documentation in the code for detailed explanation of the logic and all possible cases. + +Example:- + +```php +$ci = new condition_info($mod); +$fullinfo = $ci->get_full_information(); +``` + +#### is_available() + +This function is used to check if a given course module is currently available or not. A thing worth noting is that this doesn't take "visibility settings" and `viewhiddenactivities` capability into account. That is these settings should be properly checked after the result of is_available(), before dumping any data to the user. + +Example:- + +```php +$ci = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING); +$bool = $ci->is_available($text, false, 0); +``` + +#### show_availability() + +This function is used to check if information regarding availability of the current module should be shown to the user or not. + +Example:- + +```php +$ci = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING); +$bool = $ci->show_availability(); +``` + +### Adding/Updating conditional clauses to activities + +```php +fill_availability_conditions($cm); +add_completion_condition($cmid, $requiredcompletion); +add_grade_condition($gradeitemid, $min, $max, $updateinmemory = false); +update_cm_from_form($cm, $fromform, $wipefirst = true) +``` + +#### fill_availability_conditions() + +This function adds any extra availability conditions to given course module object. + +```php +$rawmods = get_course_mods($courseid); +if (empty($rawmods)) { + die; +} +if ($sections = $DB->get_records("course_sections", ["course" => $courseid], "section ASC")) { + foreach ($sections as $section) { + if (!empty($section->sequence)) { + $sequence = explode(",", $section->sequence); + foreach ($sequence as $seq) { + if (empty($rawmods[$seq])) { + continue; + } + if (!empty($CFG->enableavailability)) { + condition_info::fill_availability_conditions($rawmods[$seq]); + // Do something. + } + } + } + } + } +} +``` + +#### add_completion_condition() + +In Moodle availability condition of a Module or activity can depend on another activity. For example activity B will not be unlocked until activity A is successfully completed. To add such inter-dependent conditions, this function is used. + +Example:- + +```php +$test1 = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING); +$test1->add_completion_condition(13, 3); +``` + +#### add_grade_condition() + +This function is used to add a grade related restriction to an activity based on the grade secured in another activity. In the following example a minimum grade of 0.4 is required on gradeitem 666 to unlock the current activity in context. + +Example:- + +```php +$test1 = new condition_info((object) ['id' => $cmid], CONDITION_MISSING_EVERYTHING); +$test1->add_grade_condition(666, 0.4, null, true); +``` + +#### update_cm_from_form() + +This function is used to update availability conditions from a user submitted form. + +Example:- + +```php +$fromform = $mform->get_data(); +if (!empty($fromform->update)) { + if (!empty($course->groupmodeforce) or !isset($fromform->groupmode)) { + $fromform->groupmode = $cm->groupmode; // Keep the original. + } + + // update course module first + $cm->groupmode = $fromform->groupmode; + $cm->groupingid = $fromform->groupingid; + $cm->groupmembersonly = $fromform->groupmembersonly; + + $completion = new completion_info($course); + if ($completion->is_enabled()) { + // Update completion settings. + $cm->completion = $fromform->completion; + $cm->completiongradeitemnumber = $fromform->completiongradeitemnumber; + $cm->completionview = $fromform->completionview; + $cm->completionexpected = $fromform->completionexpected; + } + if (!empty($CFG->enableavailability)) { + $cm->availablefrom = $fromform->availablefrom; + $cm->availableuntil = $fromform->availableuntil; + $cm->showavailability = $fromform->showavailability; + condition_info::update_cm_from_form($cm,$fromform,true); + } + // Do something else with the data. +} +``` + +### Deleting conditions attached to activities + +we have a simple function wipe_conditions() that can erase all conditions associated with the current activity. +consider an example:- + +```php +$ci = new condition_info($cm, CONDITION_MISSING_EVERYTHING, false); +$ci->wipe_conditions(); +``` + +## See Also + +- [Conditional activities Adding module support](https://docs.moodle.org/dev/Conditional_activities_Adding_module_support) +- [Conditional activities](https://docs.moodle.org/dev/Conditional_activities) - original specification for this feature. + +### User documentation + +- [How to make a new availability condition plugin](../../plugintypes/availability/index.md). +- [Conditional Activities](https://docs.moodle.org/en/Conditional_activities) +- [Conditional Activities Settings](https://docs.moodle.org/en/Conditional_activities_settings) +- [Using Conditional Activities](https://docs.moodle.org/en/Using_Conditional_activities) diff --git a/versioned_docs/version-4.1/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png b/versioned_docs/version-4.1/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png new file mode 100644 index 0000000000..4b1c683898 Binary files /dev/null and b/versioned_docs/version-4.1/apis/core/dml/_delegated-transactions/TransactionsAndExceptionsFlow.png differ diff --git a/versioned_docs/version-4.1/apis/core/dml/_exceptions/Dml_exceptions.png b/versioned_docs/version-4.1/apis/core/dml/_exceptions/Dml_exceptions.png new file mode 100644 index 0000000000..304093c414 Binary files /dev/null and b/versioned_docs/version-4.1/apis/core/dml/_exceptions/Dml_exceptions.png differ diff --git a/versioned_docs/version-4.1/apis/core/dml/ddl.md b/versioned_docs/version-4.1/apis/core/dml/ddl.md new file mode 100644 index 0000000000..22d4148746 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/dml/ddl.md @@ -0,0 +1,133 @@ +--- +title: Data definition API +tags: + - DB + - XMLDB + - API + - core_dml + - ddl + - core +--- + +import { Since, ValidExample, InvalidExample } from '@site/src/components'; + + + +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](https://docs.moodle.org/dev/XMLDB_editor) 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](https://docs.moodle.org/dev/XMLDB_editor) 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.1/apis/core/dml/delegated-transactions.md b/versioned_docs/version-4.1/apis/core/dml/delegated-transactions.md new file mode 100644 index 0000000000..2dc3d7a03c --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/dml/delegated-transactions.md @@ -0,0 +1,78 @@ +--- +title: Transactions +tags: + - core_dml + - DML + - core + - API +--- + +Moodle allows data manipulation to take place within a database transaction, known as a *Delegated transaction*. This allows you to perform CRUD[^1] operations, and roll them back if a failure takes place. + +## General principles + +1. These **delegated transactions** work in a way that, when nested, the outer levels have control over the inner ones. +2. Code should **not** rely on a rollback happening. It is only a measure to reduce (not to eliminate) DB[^2] garbled information +3. Any code using transactions that result in unfinished, unbalanced, or finished twice transactions will generate a `transaction_exception` and the DB will perform a rollback +4. If one transaction (at any level) has been marked for `rollback()` there will not be any method to change it. Finally Moodle will perform the DB rollback +5. If one transaction (at any level) has been marked for `allow_commit()` it will be possible to change that status to `rollback()` in any outer level +6. It will be **optional** to catch exceptions when using transactions, but if they are caught, then it is mandatory to mark the transaction for `rollback()` +7. Any explicit `rollback()` call will pass the exception originating from it, as in `rollback($exception)`, to be re-thrown + +## The API + +1. All the handling must go, exclusively, to a `moodle_database` object, leaving real drivers only implementing (protected) the old begin/commit/rollback_sql() functions +2. One array of objects of type `moodle_transaction` will be stored / checked from `$DB` +3. `$DB` will be the responsible to instantiate / accumulate / pair / compare `moodle_transaction`s +4. Each `moodle_transaction` will be able to set the global mark for rollback. Commit won't change anything +5. Inner-most commit/rollback will printout one complete stack of `moodle_transaction`s information if we are under `DEBUG_DEVELOPER` and the new setting `delegatedtransactionsdebug` is enabled +6. Normal usage of the moodle_transaction will be: + + ```php + $transaction = $DB->start_delegated_transaction(); + // Perform some $DB stuff + $transaction->allow_commit(); + ``` + +7. If, for any reason, the developer needs to catch exceptions when using transactions, it will be mandatory to use it in this way: + + ```php + try { + $transaction = $DB->start_delegated_transaction(); + // Perform some $DB stuff. + $transaction->allow_commit(); + } catch (Exception $e) { + // Extra cleanup steps. + // Re-throw exception after commiting. + $transaction->rollback($e); + } + ``` + +8. In order to be able to keep some parts of code out from top transactions completely, if we know it can lead to problems, we can use: + + ```php + // Check to confirm we aren't using transactions at this point. + // This will throw an exception if a transaction is found. + $DB->transactions_forbidden(); + ``` + +## The Flow + +![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.1/apis/core/dml/drivers.md b/versioned_docs/version-4.1/apis/core/dml/drivers.md new file mode 100644 index 0000000000..efc34f1f6a --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/dml/drivers.md @@ -0,0 +1,47 @@ +--- +title: DML drivers +tags: + - core_dml + - DML + - core + - API + - RDBM +--- + +A number of native drivers are included with Moodle, including those with support for: + +- MySQLi +- MariaDB +- PostgreSQL +- Oracle +- Microsoft SQL Server + +These drivers are supported using DML Database Driver layer, which has a number of discreet benefits: + +- more optimised and probably faster +- consume less memory +- better possibility to improve logging, debugging, profiling, etc. +- less code, easier to fix and maintain +- and more + +## Query logging + +The native DML drivers support logging of database queries to database table, which can be enabled in config.php: + +```php title="config.php" +$CFG->dboptions = [ + 'dbpersist' => 0, + //'logall' => true, + 'logslow' => 5, + 'logerrors' => true, +]; +``` + +- `logall` - log all queries - suitable only for developers, causes high server loads +- `logslow` - log queries that take longer than specified number of seconds (float values are accepted) +- `logerrors` - log all error queries + +## See also + +- [DML functions](./index.md): Where all the functions used to handle DB data ([DML](https://en.wikipedia.org/wiki/Data_Manipulation_Language)) are defined. +- [DML exceptions](./exceptions.md): New DML code is throwing exceptions instead of returning false if anything goes wrong diff --git a/versioned_docs/version-4.1/apis/core/dml/exceptions.md b/versioned_docs/version-4.1/apis/core/dml/exceptions.md new file mode 100644 index 0000000000..f0b6a9b3c8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/dml/exceptions.md @@ -0,0 +1,33 @@ +--- +title: DML exceptions +tags: + - core_dml + - DB + - API + - core + - exception +--- + +The [DML](./index.md) API uses a selection of exceptions to indicate errors. + +## Types of exception + +![DML 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.1/apis/core/dml/index.md b/versioned_docs/version-4.1/apis/core/dml/index.md new file mode 100644 index 0000000000..f00e42a220 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/dml/index.md @@ -0,0 +1,1117 @@ +--- +title: Data manipulation API +tags: + - DB + - XMLDB + - API + - core + - core_dml +--- + +import { Since, ValidExample, InvalidExample } from '@site/src/components'; + +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 will be thrown if no record is found or 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'); +$comparedescription = $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.1/apis/core/grading/index.md b/versioned_docs/version-4.1/apis/core/grading/index.md new file mode 100644 index 0000000000..1a3112c82f --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/grading/index.md @@ -0,0 +1,97 @@ +--- +title: Advanced grading API +tags: + - Plugins + - Grading + - Activity grading +documentationDraft: true +--- + +Advanced grading was introduced in Moodle 2.2 for grading of assignments. It is intended to be used for grading of other types of activities in the later versions. + +## Glossary + +In advanced grading we operate with three main entities: + +- a grading area; +- a grading form definition; and +- a grading form instance. + +### Grading area + +The grading area is basically the area that can be graded, for example, a submission in an assignment module. Each grading area may have several grading definitions but only one for each grading method. In an assignment's edit form (or its Advanced grading page) the teacher may set one of the advanced grading methods as current. The class called **`grading_manager`** is responsible for grading method operations in the specified area. + +### Grading form definition + +Grading form definitions are the set of rules defining how the grading is performed. For example, in rubrics this is the set of criteria and levels and their display options. The basic information about definition is stored in the DB table grading_definitions. A separate entry is created for each grading area (that is for each module). Users with permission `moodle/grade:managegradingforms` are able to edit the definitions and mark them as "Ready". + +### Grading form instance + +A grading form instance is created for each evaluation of a submission, using advanced grading. One instance (usually the latest) has the status INSTANCE_STATUS_ACTIVE. Sometimes it may be the case that a teacher wants to change the definition after some students have already been graded. In this case their instances change status to `INSTANCE_STATUS_NEEDUPDATE`. The grade pushed to the gradebook remains unchanged but students will not see the grade in advanced grading format until teacher updates them. Plugins are also welcome to use these status levels. + +## Functions + +## Examples + +### Using advanced grading in grade-able modules + +The following example is drawn from **/mod/assignment/lib.php**. + +1. In order for module to support advanced grading, its function **`[activityname]_supports(FEATURE_ADVANCED_GRADING)`** must return true. + + ```php title="mod/[activityname]/lib.php" + function [activityname]_supports(string $feature): ?bool { + switch ($feature) { + // ... + case FEATURE_ADVANCED_GRADING: + return true; + // ... + } + } + ``` + +1. The module should define a function called **`[activityname]_grading_areas_list()`**. +1. There needs to be a check to see if there is an advanced grading method for this area and it is available. If it is, retrieve it and set the grade range used in this module. + + ```php + if ($controller = get_grading_manager(...)->get_active_controller()) { + $controller->set_grade_range(...); + ... + } + ``` + +1. There are two ways to create an grading object instance. Choose one of the following. + + ```php + $gradinginstance = $controller->get_current_instance(...); + $gradinginstance = $controller->get_or_create_instance(...); + ``` + +1. During population of the grading form, simple grading elements can now be substituted with advanced grading element. + + ```php + $mform->addElement( + 'grading', + 'ELEMENTNAME', + '...', + ['gradinginstance' => $gradinginstance] + ); + ``` + +1. On submission of a grading form, the advanced grading information shall be also submitted. The grade for the gradebook retrieved from plugin and saved to the gradebook. + + ```php + $grade = $gradinginstance->submit_and_get_grade($data->ELEMENTNAME, ...) + ``` + +1. When the grade is displayed to the student it is displayed using the grading objects renderer. + + ```php + $output .= $controller->render_grade(...); + ``` + +1. To show the grading method to students before they are actually graded: + + ```php + $output .= $controller->render_preview($PAGE); + ``` diff --git a/versioned_docs/version-4.1/apis/core/htmlwriter/index.md b/versioned_docs/version-4.1/apis/core/htmlwriter/index.md new file mode 100644 index 0000000000..2a1188580f --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/htmlwriter/index.md @@ -0,0 +1,62 @@ +--- +title: HTML Writer API +tags: + - API + - HTML + - DOM +--- + +Moodle has a class called _HTML writer_ which allows you to output basic HTML tags. This is typically used within renderer functions, for example `question/type/*pluginname*/renderer.php`. + +:::tip + +Please consider using [templates](../../../guides/templates/index.md) as an alternative to the _HTML writer_. + +::: + +:::note + +There is no documentation for most of this class. Please read [HTML Writer Class Reference](https://phpdoc.moodledev.io/master/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.1/apis/core/index.md b/versioned_docs/version-4.1/apis/core/index.md new file mode 100644 index 0000000000..68027faa93 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/index.md @@ -0,0 +1,8 @@ +--- +title: Core APIs +tags: + - core + - API +--- + +Moodle provides a series of Core APIs which can be used by any part of Moodle. diff --git a/versioned_docs/version-4.1/apis/core/lock/index.md b/versioned_docs/version-4.1/apis/core/lock/index.md new file mode 100644 index 0000000000..7b80a348f2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/lock/index.md @@ -0,0 +1,73 @@ +--- +title: Lock API +tags: + - API + - Lock +--- + +Locking is required whenever you need to prevent two, or more, processes accessing the same resource at the same time. The prime candidate for locking in Moodle is cron. Locking allows multiple cron processes to work on different parts of cron at the same time with no risk that they will conflict (work on the same job at the same time). + +## When to use locking + +When you want to prevent multiple requests from accessing the same resource at the same time. Accessing a resource is a vague description, but it could be for example running a slow running task in the background, running different parts of cron etc. + +## Performance + +Locking is not meant to be fast. Do not use it in code that will be triggered many times in a single request (for example MUC). It is meant to be always correct - even for multiple nodes in a cluster. This implies that the locks are communicated among all the nodes in the cluster, and hence it will never be super quick. + +## Usage + +The locking API is used by getting an instance of a lock_factory, and then using it to retrieve locks, and eventually releasing them. You are required to release all your locks, even on the event of failures. + +```php +$timeout = 5; + +// A namespace for the locks. Must be prefixed with the component name to prevent conflicts. +$locktype = 'mod_assign_download_submissions'; + +// Resource key - needs to uniquely identify the resource that is to be locked. E.g. If you +// want to prevent a user from running multiple course backups - include the userid in the key. +$resource = 'user:' . $USER->id; + +// Get an instance of the currently configured lock_factory. +$lockfactory = \core\lock\lock_config::get_lock_factory($locktype); + +// Get a new lock for the resource, wait for it if needed. +if ($lock = $lockfactory->get_lock($resource, $timeout)) { + // We have exclusive access to the resource, do the slow zip file generation... + + if ($someerror) { + // Always release locks on failure. + $lock->release(); + print_error('blah'); + } + + // Release the lock once finished. + $lock->release(); + +} else { + // We did not get access to the resource in time, give up. + throw new moodle_exception('locktimeout'); +} +``` + +## Use a different lock type from the default + +Change the $CFG->lock_factory setting to one of the other lock types included with core. These are all documented in config-dist.php. + +## Implementing new lock types + +If you really want to do this you can. I probably wouldn't recommend it - because the core lock types should be very reliable - and the performance is not really a concern. + +Add a new local_XXX plugin with an autoloaded class that implements \core\lock\lock_factory. +Set the site configuration variable "lock_factory" to the full namespaced path to your class in the config.php for example + +```php +$CFG->lock_factory = '\local_redis\lock\redis_lock_factory'; +``` + +:::note + +See `lib/tests/lock_test.php` for an example of unit tests which can be run on a custom lock instance to verify it for correctness (run_on_lock_factory). + +::: diff --git a/versioned_docs/version-4.1/apis/core/navigation/_index/Moodle-IA.png b/versioned_docs/version-4.1/apis/core/navigation/_index/Moodle-IA.png new file mode 100644 index 0000000000..b081cfa530 Binary files /dev/null and b/versioned_docs/version-4.1/apis/core/navigation/_index/Moodle-IA.png differ diff --git a/versioned_docs/version-4.1/apis/core/navigation/_index/assignmentmenu.png b/versioned_docs/version-4.1/apis/core/navigation/_index/assignmentmenu.png new file mode 100644 index 0000000000..94abeec2bd Binary files /dev/null and b/versioned_docs/version-4.1/apis/core/navigation/_index/assignmentmenu.png differ diff --git a/versioned_docs/version-4.1/apis/core/navigation/index.md b/versioned_docs/version-4.1/apis/core/navigation/index.md new file mode 100644 index 0000000000..aeab3f0fe4 --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/navigation/index.md @@ -0,0 +1,403 @@ +--- +title: Navigation API +tags: + - API + - Navigation +--- + +The Navigation API allows for the manipulation of the navigation system used in Moodle. + +## What the navigation is + +It's very important to understand what the navigation is exactly within Moodle. One of the goals for Moodle 2.0 was to standardise navigation throughout Moodle and try to bring order to the structure of a Moodle site. Navigation is available through the page object `$PAGE`, against which you set the heading for the page, the title, any JavaScript requirements, etc. The navigation structure uses the information `$PAGE` contains to generate a navigation structure for the site. The navigation or settings blocks are interpretations of the navigation structure Moodle creates. + +This navigation structure is available through three variables: + +- `$PAGE->navigation` - The main navigation structure, it will contain items that will allow the user to browse to the other available pages +- `$PAGE->settingsnav` - The settings navigation structure contains items that will allow the user to edit settings +- `$PAGE->navbar` - A special structure for page breadcrumbs + +A conceptual view of the information architecture that sits behind the navigation tree is here: + +![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? + +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.1/apis/core/preference/index.md b/versioned_docs/version-4.1/apis/core/preference/index.md new file mode 100644 index 0000000000..b71b7a83fd --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/preference/index.md @@ -0,0 +1,106 @@ +--- +title: Preference API +tags: + - API + - User Preferences + - User +--- + +The Preference API is used for the storage and retrieval of user preferences. These preferences are stored in the database for users with an account, however for guests or users who are not currently logged in the preferences are stored in the Session. + +All of these functions operate on the current user by default, however you can specify the user you wish to operate on by passing a user ID or a moodle user object to the $user parameter. Normally this is used for state indicators, where it can be as simple as a yes or no, however you can also use it to store more complex user data, such as a serialized PHP object. + +It is considered good practice to abstain from storing default values as a user preference as this creates a lot of redundancy. Instead you should apply the default value at the code level if there is no stored value for a given preference. + +## Primary functions + +### get_user_preferences() + +This function can be used to fetch the value of a requested (via $name) preference, or if it doesn't exist the value given in the $default parameter will be returned. If you do not specify a $name then all preferences will be returned. + +```php +get_user_preferences( + string $name = null, + mixed $default = null, + stdClass|int|null $user = null +): mixed; +``` + +### set_user_preference() + +This function can be used to set the value of a preference named $name. + +```php +set_user_preference( + string $name, + mixed $value, + stdClass|int|null $user = null +): bool; +``` + +### set_user_preferences() + +This function takes an array or preferences containing the name and value for each preference. For each element in the array this function passes the keys and values of each element as the $name and $value parameters (respectively) in calls to `set_user_preferences()`. + +```php +set_user_preferences( + array $preferences, + stdClass|int|null $user = null +): bool; +``` + +### unset_user_preference() + +This deletes the requested preference, specified by the $name parameter. + +```php +unset_user_preference( + string $name, + stdClass|int|null $user = null +): bool; +``` + +## Example usage of the API + +```php title="Set a preference and then retrieve it" +set_user_preference('foo_nameformat', 'long'); + +// Returns the string - "long" +get_user_preferences('foo_nameformat'); +``` + +```php title="Set another preference and then retrieve all preferences" +set_user_preference('foo_showavatars', 'no'); + +// returns [ +// foo_nameformat => "long", +// foo_showavatars => "no", +// ]; +get_user_preferences(); +``` + +```php title="Add an array of preferences and change foo_nameformat to short" +$preferences = [ + 'foo_displaynum' => '100', + 'foo_nameformat' => 'short', +]; + +set_user_preferences($preferences); + +// returns [ +// foo_nameformat => "short", +// foo_showavatars => "no", +// foo_displaynum => "100", +// ]; +get_user_preferences(); +``` + +```php title="Delete a preference" +unset_user_preference('foo_showavatars'); + +// returns [ +// foo_nameformat => "short", +// foo_displaynum => "yes", +// ]; +get_user_preferences(); +``` diff --git a/versioned_docs/version-4.1/apis/core/user/index.md b/versioned_docs/version-4.1/apis/core/user/index.md new file mode 100644 index 0000000000..3aa64cb00c --- /dev/null +++ b/versioned_docs/version-4.1/apis/core/user/index.md @@ -0,0 +1,85 @@ +--- +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. + +## 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.1/apis/plugintypes/antivirus/_files/scanner-php.mdx b/versioned_docs/version-4.1/apis/plugintypes/antivirus/_files/scanner-php.mdx new file mode 100644 index 0000000000..0b614a3177 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/antivirus/_files/scanner-php.mdx @@ -0,0 +1,13 @@ + + + +The `classes/scanner.php` class must be defined in the correct namespace for your plugin, and must extend the `\core\antivirus\scanner` class. + +It is responsible for implementing the interface between Moodle and the antivirus scanning tool. + +The following methods are compulsory: + +- `is_configured(): bool` - returns true, if this antivirus plugin is configured. +- `scan_file($file, $filename, $deleteinfected): void` - performs the **$file** scanning using antivirus functionality, using **$filename** as filename string in any reporting, deletes infected file if **$deleteinfected** is true. + +If a virus is found the `scan_file()` function _must_ throw an instance of the `\core\antivirus\scanner_exception` type. diff --git a/versioned_docs/version-4.1/apis/plugintypes/antivirus/_files/scanner-php.tsx b/versioned_docs/version-4.1/apis/plugintypes/antivirus/_files/scanner-php.tsx new file mode 100644 index 0000000000..167d78e6ff --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/antivirus/_files/scanner-php.tsx @@ -0,0 +1,88 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { Props } from '../../../../_utils'; +import DefaultDescription from './scanner-php.mdx'; + +const defaultExample = ` +namespace antivirus_scanmyfile; + +class scanner extends \\core\\antivirus\\scanner { + + public function is_configured() { + // Note: You will likely want a more specific check. + // This example just checks whether configuration exists. + return (bool) $this->get_config('pathtoscanner'); + } + + public function scan_file($file, $filename, $deleteinfected) { + if (!is_readable($file)) { + // This should not happen. + debugging('File is not readable.'); + return; + } + + // Execute the scan using the fictitious scanmyfile tool. + // In this case the tool returns: + // - 0 if no virus is found + // - 1 if a virus was found + // - [int] on error + $return = $this->scan_file_using_scanmyfile_scanner_tool($file); + + if ($return == 0) { + // Perfect, no problem found, file is clean. + return; + } else if ($return == 1) { + // Infection found. + if ($deleteinfected) { + unlink($file); + } + throw new \\core\\antivirus\\scanner_exception( + 'virusfounduser', + '', + ['filename' => $filename] + ); + } else { + // Unknown problem. + debugging('Error occurred during file scanning.'); + return; + } + } + + public function scan_file_using_scanmyfile_scanner_tool($file): int { + // Scanning routine using antivirus own tool goes here.. + // You should choose a return value appropriate for your tool. + // These must match the expected values in the scan_file() function. + // In this example the following are returned: + // - 0: No virus found + // - 1: Virus found + return 0; + } +} +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/antivirus/index.mdx b/versioned_docs/version-4.1/apis/plugintypes/antivirus/index.mdx new file mode 100644 index 0000000000..dc9d920541 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/antivirus/index.mdx @@ -0,0 +1,114 @@ +--- +title: Antivirus plugins +tags: + - Plugins + - Antivirus +description: Integrate your preferred Antivirus tool to with Moodle to automatically check new file uploads. +--- + + + + +import { Since } from '@site/src/components'; + + + +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://phpunit.de/manual/current/en/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.1/apis/plugintypes/assign/_files/submission_settings.php b/versioned_docs/version-4.1/apis/plugintypes/assign/_files/submission_settings.php new file mode 100644 index 0000000000..60e7a7248f --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/assign/_files/submission_settings.php @@ -0,0 +1,22 @@ +// Note: This is on by default. +$settings->add( + new admin_setting_configcheckbox('assignsubmission_file/default', + new lang_string('default', 'assignsubmission_file'), + new lang_string('default_help', 'assignsubmission_file'), + 1 + ) +); + +if (isset($CFG->maxbytes)) { + $name = new lang_string('maximumsubmissionsize', 'assignsubmission_file'); + $description = new lang_string('configmaxbytes', 'assignsubmission_file'); + + $element = new admin_setting_configselect( + 'assignsubmission_file/maxbytes', + $name, + $description, + 1048576, + get_max_upload_sizes($CFG->maxbytes) + ); + $settings->add($element); +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/assign/feedback.md b/versioned_docs/version-4.1/apis/plugintypes/assign/feedback.md new file mode 100644 index 0000000000..aa7a5c58a9 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/assign/feedback.md @@ -0,0 +1,541 @@ +--- +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 { Since } from '@site/src/components'; +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/master/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 13 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 28 chars due to a limitation with Oracle. + +If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 28 character limit. + +::: + +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.1/apis/plugintypes/assign/index.md b/versioned_docs/version-4.1/apis/plugintypes/assign/index.md new file mode 100644 index 0000000000..120ab233f0 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/assign/index.md @@ -0,0 +1,13 @@ +--- +title: Assignment sub-plugins +tags: + - Assign + - Assignment + - Subplugin + - Plugintype +--- + +The `mod_assign` activity can be extended using two sub-plugin types, namely: + +- submission plugins, used to provide different ways for students to submit their content +- feedback plugins, used to extend the ways in which feedback may be provided to students on their submissions diff --git a/versioned_docs/version-4.1/apis/plugintypes/assign/submission.md b/versioned_docs/version-4.1/apis/plugintypes/assign/submission.md new file mode 100644 index 0000000000..dd5083acd8 --- /dev/null +++ b/versioned_docs/version-4.1/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 { Since } from '@site/src/components'; +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/master/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 11 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 28 chars due to a limitation with Oracle. + +If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 28 character limit. + +::: + +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.1/apis/plugintypes/availability/_examples/lang.md b/versioned_docs/version-4.1/apis/plugintypes/availability/_examples/lang.md new file mode 100644 index 0000000000..4e73de046c --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/availability/_examples/lang.md @@ -0,0 +1,14 @@ + + +Each plugin must define a set of language strings with, at a minimum, an English translation. These are specified in the plugin's lang/en directory in a file named after the plugin. For example the LDAP authentication plugin: + +Language strings for the plugin. Required strings: + +- **pluginname** - name of plugin. +- **title** - text of button for adding this type of plugin. +- **description** - explanatory text that goes alongside the button in the 'add restriction' dialog. + +You will usually need to add your own strings for two main purposes: + +- Creating suitable form controls for users who are editing the activity settings. +- Displaying information about the condition. diff --git a/versioned_docs/version-4.1/apis/plugintypes/availability/_examples/lang.php b/versioned_docs/version-4.1/apis/plugintypes/availability/_examples/lang.php new file mode 100644 index 0000000000..307d2e9ca6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/availability/_examples/lang.php @@ -0,0 +1,3 @@ +$string['description'] = 'Allow only students who belong to a group within a specified grouping.'; +$string['pluginname'] = 'Restriction by grouping'; +$string['title'] = 'Grouping'; diff --git a/versioned_docs/version-4.1/apis/plugintypes/availability/index.md b/versioned_docs/version-4.1/apis/plugintypes/availability/index.md new file mode 100644 index 0000000000..1c5518eba1 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/availability/index.md @@ -0,0 +1,338 @@ +--- +title: Availability conditions +tags: + - Availability + - core_availability +--- + +Availability conditions allow teachers to restrict an activity or section so that only certain users can access it. These are accessed using the [Availability API](../../subsystems/availability/index.md). + +Some of the conditions included with Moodle are: + +- Date - users can only access activity after specified date +- Grade - users can only access activity if they have a certain grade in another activity + +A relatively simple example is the grouping condition which can be found in `/availability/condition/grouping`. It is a good basis for a new plugin when starting to implement a new condition. + +To see this condition in action: + +- Go to a course and edit any section +- Expand the **Restrict access** heading +- Click the **Add restriction** button +- Click **Grouping** + +## File structure + +import { + Lang, + Lib, +} from '../../_files'; + +All availability condition plugin files must be located inside the **/availability/condition/pluginname** folder. + +
+ View an example directory layout for the `availability_grouping` plugin. + +```console + availability/condition/grouping +├── classes +│   ├── condition.php +│   ├── frontend.php +├── lang +│   └── en +│   └── availability_grouping.php +├── version.php +└── yui + ├── build + │   └── moodle-availability_grouping-form + │   ├── moodle-availability_grouping-form-debug.js + │   ├── moodle-availability_grouping-form-min.js + │   └── moodle-availability_grouping-form.js + └── src + └── form + ├── build.json + ├── js + │   └── form.js + └── meta + └── form.json +``` + +
+ +Some of the important files for the format plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +### lang/en/availability_name.php + +import langExample from '!!raw-loader!./_examples/lang.php'; +import langDescription from './_examples/lang.md'; + + + +### classes/condition.php + +This PHP class implements the back-end of the condition; in other words, this class contains the code which decides whether a user is allowed to access an activity that uses this condition, or not. + +Here's an outline of the code (with standard PHPdoc comments omitted to save space) for a simple example in which there is a boolean value that controls whether access is allowed or not. + +```php +// You must use the right namespace (matching your plugin component name). +namespace availability_name; + +class condition extends \core_availability\condition { + // Any data associated with the condition can be stored in member + // variables. Here's an example variable: + protected $allow; + + public function __construct($structure) { + // Retrieve any necessary data from the $structure here. The + // structure is extracted from JSON data stored in the database + // as part of the tree structure of conditions relating to an + // activity or section. + // For example, you could obtain the 'allow' value: + $this->allow = $structure->allow; + + // It is also a good idea to check for invalid values here and + // throw a coding_exception if the structure is wrong. + } + + public function save() { + // Save back the data into a plain array similar to $structure above. + return (object)array('type' => 'name', 'allow' => $this->allow); + } + + public function is_available( + $not, + \core_availability\info $info, + $grabthelot, + $userid + ) { + // This function needs to check whether the condition is available + // or not for the user specified in $userid. + + // The value $not should be used to negate the condition. Other + // parameters provide data which can be used when evaluating the + // condition. + + // For this trivial example, we will just use $allow to decide + // whether it is allowed or not. In a real condition you would + // do some calculation depending on the specified user. + $allow = $this->allow; + if ($not) { + $allow = !$allow; + } + return $allow; + } + + public function get_description( + $full, + $not, + \core_availability\info $info + ) { + // This function returns the information shown about the + // condition on editing screens. + // Usually it is similar to the information shown if the + // user doesn't meet the condition. + // Note: it does not depend on the current user. + $allow = $not ? !$this->allow : $this->allow; + return $allow ? 'Users are allowed' : 'Users not allowed'; + } + + protected function get_debug_string() { + // This function is only normally used for unit testing and + // stuff like that. Just make a short string representation + // of the values of the condition, suitable for developers. + return $this->allow ? 'YES' : 'NO'; + } +} +``` + +There are other functions you might also want to implement. For example, if your condition should apply to lists of users (in general, conditions which are 'permanent' such as group conditions apply to lists, whereas those which are 'temporary' such as date or grade conditions do not) then you should also implement is_applied_to_user_lists and filter_user_list functions. To see the full list, look at the PHPdoc for the condition and tree_node classes inside availability/classes. + +### classes/frontend.php + +You will also need to write a frontend.php class which defines the behaviour of your plugin within the editing form (when a teacher is editing the activity settings). + +The class is required, but all the functions are theoretically optional; you can leave them out if you don't need any special behaviour for that function. In practice it's likely you will need at least one of them. + +```php +namespace availability_name; + +class frontend extends \core_availability\frontend { + + protected function get_javascript_strings() { + // You can return a list of names within your language file and the + // system will include them here. + // Should you need strings from another language file, you can also + // call $PAGE->requires->strings_for_js manually from here.) + return []; + } + + protected function get_javascript_init_params( + $course, + \cm_info $cm = null, + \section_info $section = null + ) { + // If you want, you can add some parameters here which will be + // passed into your JavaScript init method. If you don't include + // this function, there will be no parameters. + return ['frog']; + } + + protected function allow_add( + $course, + \cm_info $cm = null, + \section_info $section = null + ) { + // This function lets you control whether the 'add' button for your + // plugin appears. For example, the grouping plugin does not appear + // if there are no groupings on the course. This helps to simplify + // the user interface. If you don't include this function, it will + // appear. + return true; + } +} +``` + +### YUI + +The Availability API generates a dialogue to allow teachers to configure the availability conditions. Each availability plugin can add to this form by writing a JavaScript module in the YUI format which generates its form fields, errors, and configuration. + +:::note + +Although JavaScript standards in Moodle have moved on, the core availability system is implemented in YUI, so for now, the plugins need to use YUI too. (Please, someone, do [MDL-69566](https://tracker.moodle.org/browse/MDL-69566)!) + +::: + +YUI does require more boilerplate configuration that AMD modules, but the same build toolset is used as for AMD modules and you can still make use of the `grunt watch` command. + +#### yui/src/form/meta/form.json + +The metadata file lists any dependencies that your YUI module has on other code. + +Typically this will include the `moodle-core_availability-form` dependency, and possibly some other YUI dependencies. + +```javascript title="availability/condition/example/yui/src/form/meta/form.json" +{ + "moodle-availability_name-form": { + "requires": [ + "base", + "node", + "event", + "moodle-core_availability-form" + ] + } +} +``` + +#### yui/src/form/build.json + +The build.json file describes how the YUI compiler will build your YUI module. + +YUI modules can be broken down into smaller, succint, pieces of code. This is very useful for larger modules, but rarely necessary in smaller code. + +Typically you should only need to set the name of your plugin in this file> + +```javascript title="availability/condition/example/yui/src/form/build.json" +{ + "name": "moodle-availability_name-form", + "builds": { + "moodle-availability_name-form": { + "jsfiles": [ + "form.js" + ] + } + } +} +``` + +#### yui/src/js/form.js + +This file contains the actual JavaScript code for your plugin. It should follow the below format in order to integrate with the core JavaScript. Additional feautres are available and you can add any extra functions you like to break your code down too. + +```javascript title="availability/condition/example/yui/src/js/form.js" +M.availability_name = M.availability_name || {}; + +M.availability_name.form = Y.Object(M.core_availability.plugin); + +M.availability_name.form.initInner = function(param) { + // The 'param' variable is the parameter passed through from PHP (you + // can have more than one if required). + + // Using the PHP code above it'll show 'The param was: frog'. + console.log('The param was: ' + param); +}; + +M.availability_name.form.getNode = function(json) { + // This function does the main work. It gets called after the user + // chooses to add an availability restriction of this type. You have + // to return a YUI node representing the HTML for the plugin controls. + + // Example controls contain only one tickbox. + var html = ''; + var node = Y.Node.create('' + html + ''); + + // Set initial values based on the value from the JSON data in Moodle + // database. This will have values undefined if creating a new one. + if (json.allow) { + node.one('input').set('checked', true); + } + + // Add event handlers (first time only). You can do this any way you + // like, but this pattern is used by the existing code. + if (!M.availability_name.form.addedEvents) { + M.availability_name.form.addedEvents = true; + var root = Y.one('#fitem_id_availabilityconditionsjson'); + root.delegate('click', function() { + // The key point is this update call. This call will update + // the JSON data in the hidden field in the form, so that it + // includes the new value of the checkbox. + M.core_availability.form.update(); + }, '.availability_name input'); + } + + return node; +}; + +M.availability_name.form.fillValue = function(value, node) { + // This function gets passed the node (from above) and a value + // object. Within that object, it must set up the correct values + // to use within the JSON data in the form. Should be compatible + // with the structure used in the __construct and save functions + // within condition.php. + var checkbox = node.one('input'); + value.allow = checkbox.get('checked') ? true : false; +}; + +M.availability_name.form.fillErrors = function(errors, node) { + // If the user has selected something invalid, this optional + // function can be included to report an error in the form. The + // error will show immediately as a 'Please set' tag, and if the + // user saves the form with an error still in place, they'll see + // the actual error text. + + // In this example an error is not possible... + if (false) { + // ...but this is how you would add one if required. This is + // passing your component name (availability_name) and the + // name of a string within your lang file (error_message) + // which will be shown if they submit the form. + errors.push('availability_name:error_message'); + } +}; +``` + +### Testing + +We strongly recommend writing both unit tests, and functional tests for your availability conditions. + +## See also + +- Using the [Availability API as a consumer](../../subsystems/availability/index.md) +- [Conditional activities API](../../core/conditionalactivities/index.md) diff --git a/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/access.php b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/access.php new file mode 100644 index 0000000000..0b702aa8af --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/access.php @@ -0,0 +1,20 @@ +$capabilities = [ + 'block/pluginname:myaddinstance' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'user' => CAP_ALLOW + ], + 'clonepermissionsfrom' => 'moodle/my:manageblocks' + ], + 'block/pluginname:addinstance' => [ + 'riskbitmask' => RISK_SPAM | RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => [ + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ], + 'clonepermissionsfrom' => 'moodle/site:manageblocks' + ], +]; diff --git a/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/block_pluginname.php b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/block_pluginname.php new file mode 100644 index 0000000000..7482aa9f2c --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/block_pluginname.php @@ -0,0 +1,49 @@ +class block_pluginname extends block_base { + + /** + * Initialises the block. + * + * @return void + */ + public function init() { + $this->title = get_string('pluginname', 'block_pluginname'); + } + + /** + * Gets the block contents. + * + * @return string The block HTML. + */ + public function get_content() { + global $OUTPUT; + + if ($this->content !== null) { + return $this->content; + } + + $this->content = new stdClass(); + $this->content->footer = ''; + + // Add logic here to define your template data or any other content. + $data = ['YOUR DATA GOES HERE']; + + $this->content->text = $OUTPUT->render_from_template('block_yourplugin/content', $data); + + return $this->content; + } + + /** + * Defines in which pages this block can be added. + * + * @return array of the pages where the block can be added. + */ + public function applicable_formats() { + return [ + 'admin' => false, + 'site-index' => true, + 'course-view' => true, + 'mod' => false, + 'my' => true, + ]; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/edit_form.php b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/edit_form.php new file mode 100644 index 0000000000..0951869db3 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/edit_form.php @@ -0,0 +1,10 @@ +class block_pluginname_edit_form extends block_edit_form { + protected function specific_definition($mform) { + // Section header title according to language file. + $mform->addElement('header', 'config_header', get_string('blocksettings', 'block')); + // A sample string variable with a default value. + $mform->addElement('text', 'config_text', get_string('blockstring', 'block_pluginname')); + $mform->setDefault('config_text', 'default value'); + $mform->setType('config_text', PARAM_TEXT); + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml new file mode 100644 index 0000000000..43bfafbab6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/blocks/_examples/pluginskel_recipe.yaml @@ -0,0 +1,103 @@ +## This is an example recipe file that you can use as a template for your own plugins. +## See the list of all files it would generate: +## +## php generate.php example.yaml --list-files +## +## View a particular file contents without actually writing it to the disk: +## +## php generate.php example.yaml --file=version.php +## +## To see the full list of options, run: +## +## php generate.php --help +## +--- +## Frankenstyle component name. +component: block_pluginname + +## Human readable name of the plugin. +name: Example block + +## Human readable release number. +release: "0.1.0" + +## Plugin version number, e.g. 2016062100. Will be set to current date if left empty. +#version: 2016121200 + +## Required Moodle version, e.g. 2015051100 or "2.9". +requires: "3.11" + +## Plugin maturity level. Possible options are MATURIY_ALPHA, MATURITY_BETA, +## MATURITY_RC or MATURIY_STABLE. +maturity: MATURITY_BETA + +## Copyright holder(s) of the generated files and classes. +copyright: Year, You Name + +## Features flags can control generation of optional files/code fragments. +features: + readme: true + license: true + + ## Privacy API implementation +privacy: + haspersonaldata: false + uselegacypolyfill: false + +block_features: + ## Creates the file edit_form.php + edit_form: true + + ## Allows multiple instances of the block on the same course. + instance_allow_multiple: false + + ## Choose where to display the block. + applicable_formats: + - page: all + allowed: false + - page: course-view + allowed: true + - page: course-view-social + allowed: false + + ## Backup the block plugin. + backup_moodle2: + restore_task: true + restore_stepslib: true + backup_stepslib: true + settingslib: true + backup_elements: + - name: elt + restore_elements: + - name: elt + path: /path/to/file + +## Capabilities defined by the plugin. +capabilities: + ## Required by block plugins. + - name: myaddinstance + title: Add a new pluginname block to the My dashboard + captype: write + contextlevel: CONTEXT_SYSTEM + archetypes: + - role: user + permission: CAP_ALLOW + clonepermissionsfrom: moodle/my:manageblocks + + - name: addinstance + title: Add a new pluginname block + captype: write + contextlevel: CONTEXT_BLOCK + archetypes: + - role: editingteacher + permission: CAP_ALLOW + - role: manager + permission: CAP_ALLOW + clonepermissionsfrom: moodle/site:manageblocks + +## Explicitly added strings +lang_strings: + - id: mycustomstring + text: You can add 'extra' strings via the recipe file. + - id: mycustomstring2 + text: Another string with {$a->some} placeholder. diff --git a/versioned_docs/version-4.1/apis/plugintypes/blocks/index.md b/versioned_docs/version-4.1/apis/plugintypes/blocks/index.md new file mode 100644 index 0000000000..1a7e48418b --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/blocks/index.md @@ -0,0 +1,441 @@ +--- +title: Block plugins +tags: + - Blocks + - Tutorial + - Plugins +--- + +import { + Lang, + VersionPHP, + DbAccessPHP, +} from '../../_files'; +import { ComponentFileSummary } from '../../../_utils'; +import { + CodeBlock, + Tabs, + TabItem +} from '@site/src/components'; + +Block plugins allow you to show supplemental information, and features, within different parts of Moodle. + +## File structure + +Blocks plugins are located in the `/blocks` directory. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +
+ View an example directory layout for the `block_pluginname` plugin. + +```console + blocks/pluginname/ + |-- db + | `-- access.php + |-- lang + | `-- en + | `-- block_pluginname.php + |-- pix + | `-- icon.png + |-- block_pluginname.php + |-- edit_form.php (optional) + `-- version.php +``` + +
+ +### block_pluginname.php + +import BlockFile from '!!raw-loader!./_examples/block_pluginname.php'; + + + +:::info + +The `init` method is essential for all blocks, and its purpose is to give values to any class member variables that need instantiating. + +::: + +### db/access.php + +import accessExample from '!!raw-loader!./_examples/access.php'; + + + +### lang/en/block_pluginname.php + +export const langExample = `$string['pluginname'] = 'Pluginname block'; +$string['pluginname'] = 'Pluginname'; +$string['pluginname:addinstance'] = 'Add a new pluginname block'; +$string['pluginname:myaddinstance'] = 'Add a new pluginname block to the My Moodle page';`; + + + +### version.php + + + +### edit_form.php + +import EditForm from '!!raw-loader!./_examples/edit_form.php'; + + + +The example below adds a text attribute to the block instance settings. + +:::caution + +All your field names need to start with **"config_"**, otherwise they will not be saved and will not be available within the block via $this->config. + +::: + +## Creating a new block plugin + +The easiest way to create a new block plugin is by using the latest version of [Tool Pluginskel](https://moodle.org/plugins/tool_pluginskel). You can use the following yaml file to generate a basic block skeleton. + +
+ View pluginskel recipe +
+ +import PluginskelRecipe from '!!raw-loader!./_examples/pluginskel_recipe.yaml'; + +{PluginskelRecipe} + +
+
+ +## Block base class API methods + +All blocks must provide a main class that extends the core block class. However, there are two different types of blocks: + +- `block_base` - The default base class for content blocks. +- `block_list` - For blocks that displays a list items. + +Depending on your plugin needs your main class in `blocks/pluginname/block_pluginname.php` must extend either `block_base` or `block_list`. + +### Block class attributes + +Once the block instance is created, there are several $this attributes that can be used: + +- `$this->config` The block instance configuration. By default it is an empty object but if the block has an [edit_form.php](#edit_form.php) file, it will be an object with the form data. +- `$this->content` This variable holds all the actual content that is displayed inside each block. Valid values for it are either NULL or an object of class stdClass, which must have specific member variables depending on the extended block base class. +- `$this->page` The page object that the block is being displayed on. +- `$this->context` The context object that the block is being displayed in. +- `$this->title` The title of the block. + +### init() + +The init method is called before the block is displayed. It is essential for all blocks, and its purpose is to give values to any class member variables that need instantiating. However, it is called before $this->config is set, if your plugin needs some configation value to define global attributes like the block title, it should be done in the specialization method. + +### specialization() + +This function is called on your subclass right after an instance is loaded. It is used to customize the title and other block attributes depending on the page type, context, configuration, etc. + +
+ View example +
+ +Example of a specialization method using the instance configuration. + +```php +function specialization() { + if (isset($this->config->title)) { + $this->title = format_string($this->config->title, true, ['context' => $this->context]); + } else { + $this->title = get_string('newhtmlblock', 'block_html'); + } +} +``` + +
+
+ +### get_content(): string + +In order to get our block to actually display something on screen, we need to add one more method to our class (before the final closing brace in our file) inside of the block_pluginname.php script. + + + + +```php +class block_pluginname extends block_base { + + // (...) + + public function get_content() { + if ($this->content !== null) { + return $this->content; + } + + $this->content = new stdClass; + $this->content->text = 'The content of pluginname block'; + $this->content->footer = 'Footer here...'; + + return $this->content; + } +} +``` + + + + +```php +class block_pluginname extends block_list { + + // (...) + + public function get_content() { + global $OUTPUT; + if ($this->content !== null) { + return $this->content; + } + + $this->content = (object) [ + 'items' => [], + 'icons' => [], + 'footer' => 'Footer here...', + ]; + + $this->content->items[] = 'An item of pluginname block'; + $this->content->icons[] = $OUTPUT->pix_icon('i/course', get_string('course')); + + // Add more list items here. + + return $this->content; + } +} +``` + + + + +:::caution + +The get_content can be called several times during the page rendering. To prevent your class from calculating it every time your plugin should check if $this->content is already defined at the beginning of the method. + +::: + +:::tip + +If the block content is empty (an empty string) the block will not be displayed. In the case of an extending block_base block this means empty the `$this->content->text` and the `$this->content->footer` values. In a block_list block, the `$this->content->items` array should be empty. Moodle performs this check by calling the block's `is_empty()` method, and if the block is indeed empty then it is not displayed at all. + +::: + +### applicable_formats(): array + +Blocks can be added to any kind of page. However, some blocks may only be displayed on certain page types. This method is used to define the page types that the block can be displayed on. See [Limit the block to specific contexts](#limit-the-block-to-specific-contexts) section below for more information. + +### instance_allow_multiple() + +By default, only one instance of each block plugin can be added to a page. However, if your plugin allows multiple instances you can overrdie the instance_allow_multiple method. + +
+ View example +
+ +```php +public function instance_allow_multiple() { + return true; +} +``` + +
+
+ +:::note + +Even if a block itself allows multiple instances in the same page, the administrator still has the option of disallowing such behavior. This setting can be set separately for each block from the Administration / Configuration / Blocks page. + +::: + +### hide_header(): bool + +Using this method each block instance can decide if the standard block header is shown or not. This method will be ignored in edit mode. + +
+ View example +
+ +```php +public function hide_header() { + return true; +} +``` + +
+
+ +### html_attributes(): array + +The block base class can inject extra HTML attributes to the block wrapper. This is useful for example to add a class to the block wrapper when the block is being displayed in a specific context. + +By default, each block section in the page will use a standard `block` class and the specific `block_pluginname` class. However, if you want to add a class to the block wrapper, you can override html_attributes to alter those attrributes. + +
+ View example +
+ +```php +public function html_attributes() { + // Get default values. + $attributes = parent::html_attributes(); + // Append our class to class attribute. + $attributes['class'] .= ' block_'. $this->name(); + return $attributes; +} +``` + +
+
+ +This results in the block having all its normal HTML attributes, as inherited from the base block class, plus our additional class name. We can now use this class name to change the style of the block, add JavaScript events to it via YUI, and so on. And for one final elegant touch, we have not set the class to the hard-coded value "block_simplehtml", but instead used the Blocks/Appendix_A#name.28.29| name() method to make it dynamically match our block's name. + +### instance_config_save(): stdClass + +An optional method to modify the instance configuration before it is saved. See [add instance configuration settings](#add-instance-configuration-settings) section below for more information. + +### has_config(): bool + +An optional method to tell Moodle that the block has a global configuration settings form. See [enabling Global Configuration](#enabling-global-configuration) section below for more information. + +## Add instance configuration settings + +By default, block instances have no configuration settings. If you want to add some, you can add them by adding a few methods and classes to your block. + +### Create an edit_form.php file + +To have a configuration form, you need to add an [edit_form.php](#edit_formphp) file into your plugin. After defining the configuration, your block's base instance will have all your settings in its [$this->config attribute](#block-class-attributes). See the [edit_form.php section above](#edit_formphp) for an example. + +:::caution + +Note that $this->config is available in all block methods **except the init() one**. This is because init() is called immediately as the block is being created, with the purpose of setting things up. Use [specialization](#specialization) instead. + +::: + +:::note + +You cannot use the 'checkbox' element in the form (once set it will stay set). You must use advcheckbox instead. + +::: + +### Optional instance_config_save method + +By default, all config_* settings will be stored in the `block_instances` table. The complete form data will be encoded in base64 before storing it in the `configdata` field. Every time a block instance is initialized all that data will be decoded in the [$this->config attribute](#block-class-attributes). + +However, for some cases like the Atto HTML editor, you may want to store them in the database instead, or to alter the config data before storing it. In that case you can create a instance_config_save method. + +
+ View example +
+ +```php title="Example of adding data before storing it +public function instance_config_save($data,$nolongerused =false) { + // Example of add new data. + $data->somenewattribute = 'Some new value'; + + // Example of alter the current data. + $data->text = 'Some new text'; + + // Call the parent method to the data inside block_instance.configdata. + return parent::instance_config_save($data,$nolongerused); +} +``` + +
+
+ +## Add global settings to the block plugin + +Apart from the specific block instance configuration, the block plugin can use global settings to customize its behavior. Those settings can only be set in the site administration and are a great way to customize the behavior of all blocks on a site. + +:::note + +Global settings are not part of hte block instance and should be accessed via the global get_config method. For example: + +```php +$settingvalue = get_config('block_pluginname', 'settingname'); +``` + +::: + +### create a settings.php file + +Implementing such configuration for our block is quite similar to implementing the [instance configuration](#add-instance-configuration-settings). To enable global configuration for the block, your plugin should contain **/blocks/simplehtml/settings.php** file. This file will populate the global admin form with form field definitions for each setting. See [Common files: settings.php](../commonfiles#settingsphp) for more information. + +### Enabling Global Configuration + +While in other Moodle pulgins the existence of a settings.php is enough to enable global configuration, for the blocks plugins it is mandatory to override the has_config method in the base class. + +
+ View example +
+ +```php" +function has_config() { + return true; +} +``` + +
+
+ +## Limit the block to specific contexts + +Some blocks are useful in some circumstances, but not in others. An example of this would be the "Social Activities" block, which is useful in courses with the "social" course format, but not courses with the "weeks" format. Moodle allows us to declare in which pages a block is available on. The information is given to Moodle as a standard associative array, with each key corresponding to a page format and defining a boolean value (true/false) that declares whether the block should be allowed to appear in that page format. + +Each page in Moodle can define it's own page type name. However, there are some conventions: + +- `all` value is used as a catch-all option. This means that if a block returns `['all' => true]` it can be used in any kind of page. +- `site-index` - Moodle frontpage. +- `course-view` - Course page, independent from the course format. +- `course-view-FORMATNAME` - Course page, with the "FORMATNAME" course format. For example, course-view-weeks is for courses with weeks format. +- `mod` - Any activity page, independent from the module. +- `mod-MODNAME-view` - Activity page, with the "MODNAME" activity. For example, mod-forum-view is for forums. +- `my` - The Moodle dashboard page. +- `admin` - Any administration page. + +
+ View example +
+ +```php +public function applicable_formats() { + return [ + 'admin' => false, + 'site-index' => false, + 'course-view' => true, + 'mod' => true, + 'my' => false + ]; +} +``` + +
+
diff --git a/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/data_controller.php b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/data_controller.php new file mode 100644 index 0000000000..8e2d30416a --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/data_controller.php @@ -0,0 +1,53 @@ +namespace customfield_checkbox; + +class data_controller extends \core_customfield\data_controller { + + /** + * Return the name of the field where the information is stored + * @return string + */ + public function datafield(): string { + return 'intvalue'; + } + + /** + * Add fields for editing a checkbox field. + * + * @param \MoodleQuickForm $mform + */ + public function instance_form_definition(\MoodleQuickForm $mform) { + $field = $this->get_field(); + $config = $field->get('configdata'); + $elementname = $this->get_form_element_name(); + + // If checkbox is required (i.e. "agree to terms") then use 'checkbox' form element. + // The advcheckbox element cannot be used for required fields because advcheckbox elements always provide a value. + $isrequired = $field->get_configdata_property('required'); + $mform->addElement($isrequired ? 'checkbox' : 'advcheckbox', $elementname, $this->get_field()->get_formatted_name()); + $mform->setDefault($elementname, $config['checkbydefault']); + $mform->setType($elementname, PARAM_BOOL); + + if ($isrequired) { + $mform->addRule($elementname, null, 'required', null, 'client'); + } + } + + /** + * Returns the default value as it would be stored in the database (not in human-readable format). + * + * @return mixed + */ + public function get_default_value() { + return $this->get_field()->get_configdata_property('checkbydefault') ? 1 : 0; + } + + /** + * Returns value in a human-readable format + * + * @return mixed|null value or null if empty + */ + public function export_value() { + $value = $this->get_value(); + return $value ? get_string('yes') : get_string('no'); + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/data_controller.tsx b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/data_controller.tsx new file mode 100644 index 0000000000..abc3371585 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/data_controller.tsx @@ -0,0 +1,28 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { ComponentFileSummaryProps } from '../../../../_utils'; + +export default (initialProps: ComponentFileSummaryProps): JSX.Element => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/field_controller.php b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/field_controller.php new file mode 100644 index 0000000000..f2572e41d6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/field_controller.php @@ -0,0 +1,28 @@ +namespace customfield_myfield; + +class field_controller extends \core_customfield\field_controller { + + /** @var string Plugin type */ + const TYPE = 'radio'; + + /** + * Add fields for editing a checkbox field. + * + * @param \MoodleQuickForm $mform + */ + public function config_form_definition(\MoodleQuickForm $mform) { + $mform->addElement( + 'header', + 'header_specificsettings', + get_string('specificsettings', 'customfield_checkbox') + ); + $mform->setExpanded('header_specificsettings', true); + + $mform->addElement( + 'selectyesno', + 'configdata[checkbydefault]', + get_string('checkedbydefault', 'customfield_checkbox') + ); + $mform->setType('configdata[checkbydefault]', PARAM_BOOL); + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/field_controller.tsx b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/field_controller.tsx new file mode 100644 index 0000000000..e4ace539f7 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/customfield/_files/field_controller.tsx @@ -0,0 +1,28 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { ComponentFileSummaryProps } from '../../../../_utils'; + +export default (initialProps: ComponentFileSummaryProps): JSX.Element => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/customfield/index.md b/versioned_docs/version-4.1/apis/plugintypes/customfield/index.md new file mode 100644 index 0000000000..78a507c5f6 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/customfield/index.md @@ -0,0 +1,128 @@ +--- +title: Course Custom fields +tags: + - core_course + - Course + - Custom field +--- + +Course custom fields allow you to create field types to be used for course custom fields. Instances of these field types can be added to a course. for example, if you want to display radio buttons on the course edit page, then you can create a radio custom course field plugin. + +import { + Lang, +} from '../../_files'; +import FieldController from './_files/field_controller'; +import DataController from './_files/data_controller'; + +## File structure + +Course custom field plugins are located in the `/customfield/field` directory. A plugin should not include any custom files outside of it's own plugin folder. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +:::important + +Some of the important files are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin. + +::: + +
+ View an example directory layout for the `customfield_checkbox` plugin. + +```console +customfield/field/checkbox +├── classes +│   ├── data_controller.php +│   ├── field_controller.php +│   └── privacy +│   └── provider.php +├── lang +│   └── en +│   └── customfield_checkbox.php +└── version.php +``` + +
+ +A course custom field plugin requires two _controller_ classes: + +- a _field_ controller, which describes the field itself; and +- a _data_ controller, which describes with interface within the context of the course page. + +### Field Controller + +The field controller defines the available configuration options that an administrator can select within the user interface to configure the field. + +Examples might include the prompt to show alongside the custom field element, and whether the element is required. + +:::note Class naming + +The class must be named `field_controller` within your plugin's namespace (for example `customfield_myfield`) and must extend the `\core_customfield\field_controller` class. + +::: + + + + +import fieldExample from '!!raw-loader!./_files/field_controller.php'; + + + + +The `\core_customfield\field_controller` class is an abstract class and defines a number of functions which you can choose to override. At a minimum, the following two items are required: + +- the `TYPE` constant to match the name of the plugin; and +- the `config_form_definition()` function. + +:::danger Element names + +All element names must be in the format `$configdata[configname]` for values to be saved, for example `configdata[cfgdefault]`. + +::: + +In addition to these requried a functions a number of other functions exist and can be overridden, with the following being particularly useful: + +- `config_form_validation($formdata, $formfiles)` - control the form validation + +Details of all available functions can be found in the `\core_customfield\field_controller` class defined in `/customfield/classes/field_controller.php`. + +### Data Controller + +The data controller defines the user interface that teachers use within the course edit form. + +:::note Class naming + +The class must be named `data_controller` within your plugin's namespace (for example `customfield_myfield`) and must extend the `\core_customfield\data_controller` class. + +::: + + + + +import dataExample from '!!raw-loader!./_files/data_controller.php'; + + + + +The `\core_customfield\data_controller` class is an abstract class and defines a number of functions which you can choose to override. At a minimum, the following two items are required: + +- the `datafield(): string` function; and +- the `instance_form_definition()` function. + +#### datafield() + +The `datafield()` function returns an enumerated string and describes which database field the data for the custom field is stored in. The possible options are: + +- `intvalue` - can store integer values, this field is indexed +- `decvalue` - can store decimal values +- `shortcharvalue` - can store character values up to 255 characters long, this field is indexed +- `charvalue` - can store character values up to 1333 characters long, this field is not indexed +- `value` - can store character values of unlimited length ("text" field in the db) + +#### instance_form_definition() + +The `instance_form_definition()` function adds any required field elements that are displayed on the course editing page. + +## See Also + +- [User Profile Fields](https://docs.moodle.org/dev/User_profile_fields) diff --git a/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/access.php b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/access.php new file mode 100644 index 0000000000..c7e1d3581e --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/access.php @@ -0,0 +1,39 @@ +$capabilities = [ + + // Enrol anybody. + 'enrol/pluginname:enrol' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ], + ], + + // Manage enrolments of users. + 'enrol/pluginname:manage' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ], + ], + + // Unenrol anybody (including self) - watch out for data loss. + 'enrol/pluginname:unenrol' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ], + ], + + // Unenrol self - watch out for data loss. + 'enrol/pluginname:unenrolself' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [], + ], +]; diff --git a/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/enrol_lang.php b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/enrol_lang.php new file mode 100644 index 0000000000..b9e70e318a --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/enrol_lang.php @@ -0,0 +1,7 @@ +$string['fee:config'] = 'Configure enrolment on payment enrol instances'; +$string['fee:manage'] = 'Manage enrolled users'; +$string['fee:unenrol'] = 'Unenrol users from course'; +$string['fee:unenrolself'] = 'Unenrol self from course'; +$string['pluginname'] = 'Enrolment on payment'; +$string['pluginname_desc'] = 'The enrolment on payment enrolment method allows you to set up courses requiring a payment. If the fee for any course is set to zero, then students are not asked to pay for entry. There is a site-wide fee that you set here as a default for the whole site and then a course setting that you can set for each course individually. The course fee overrides the site fee.'; +$string['privacy:metadata'] = 'The enrolment on payment enrolment plugin does not store any personal data.'; diff --git a/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/lib.php b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/lib.php new file mode 100644 index 0000000000..bb1b237d6b --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/lib.php @@ -0,0 +1,5 @@ +class enrol_pluginname_plugin extends enrol_plugin { + + // Enrolment plugins can define many workflows to handle enrolment + // depending on the overridden methods. See the methods section for more information. +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/message_lib.php b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/message_lib.php new file mode 100644 index 0000000000..8855bcafde --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/message_lib.php @@ -0,0 +1,48 @@ +class enrol_pluginname_plugin extends enrol_plugin { + + // (...) + + public function edit_instance_form($instance, MoodleQuickForm $mform, $context) { + $mform->addElement( + 'select', + 'customint4', + get_string('sendcoursewelcomemessage', 'enrol_pluginname'), + enrol_send_welcome_email_options() + ); + } + + /** + * Enrol a user using a given enrolment instance. + * + * @param stdClass $instance the plugin instance + * @param int $userid the user id + * @param int $roleid the role id + * @param int $timestart enrolment start timestamp + * @param int $timeend enrolment end timestamp + * @param int $status default to ENROL_USER_ACTIVE for new enrolments + * @param bool $recovergrades restore grade history + */ + public function enrol_user( + stdClass $instance, + $userid, + $roleid = null, + $timestart = 0, + $timeend = 0, + $status = null, + $recovergrades = null + ) { + parent::enrol_user( + $instance, + $userid, + $roleid, + $timestart, + $timeend, + $status, + $recovergrades + ); + // Send welcome message. + if ($instance->customint4 != ENROL_DO_NOT_SEND_EMAIL) { + $this->email_welcome_message($instance, core_user::get_user($userid)); + } + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/ui_lib.php b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/ui_lib.php new file mode 100644 index 0000000000..d56a1a65f3 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/enrol/_examples/ui_lib.php @@ -0,0 +1,100 @@ +record_exists('enrol', ['courseid' => $courseid, 'enrol' => 'pluginname'])) { + return false; + } + + return true; + } + + /** + * Add elements to the edit instance form. + * + * @param stdClass $instance + * @param MoodleQuickForm $mform + * @param context $context + * @return bool + */ + public function edit_instance_form($instance, MoodleQuickForm $mform, $context) { + $options = [ + 'example1' => get_string('example1', 'enrol_pluginname'), + 'example2' => get_string('example2', 'enrol_pluginname'), + ]; + $mform->addElement( + 'select', + 'customchar1', + get_string('something', 'enrol_pluginname'), + $options + ); + $mform->setDefault('customchar1', $this->get_config('something')); + + $mform->addElement( + 'text', + 'customtext1', + get_string('extraname', 'enrol_pluginname') + ); + } + + /** + * Perform custom validation of the data used to edit the instance. + * + * @param array $data array of ("fieldname"=>value) of submitted data + * @param array $files array of uploaded files "element_name"=>tmp_file_path + * @param object $instance The instance loaded from the DB + * @param context $context The context of the instance we are editing + * @return array of "element_name"=>"error_description" if there are errors, + * or an empty array if everything is OK. + */ + public function edit_instance_validation($data, $files, $instance, $context) { + $errors = []; + + // Do some validation. + if ($data['customchar1'] != 'example2' && empty($data['customtext1'])) { + $errors['customtext1'] = get_string('missing_extraname', 'enrol_pluginname'); + } + + return $errors; + } + + /** + * Add new instance of enrol plugin. + * @param object $course the course object + * @param array $fields instance fields + * @return int id of new instance, null if can not be created + */ + public function add_instance($course, array $fields = null) { + // Add $fields calculations here. + $instanceid = parent::add_instance($course, $fields); + // Insert elements to the enrolment plugins tables if needed. + return $instanceid; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/enrol/index.md b/versioned_docs/version-4.1/apis/plugintypes/enrol/index.md new file mode 100644 index 0000000000..84269a467c --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/enrol/index.md @@ -0,0 +1,424 @@ +--- +title: Enrolment plugins +tags: + - Enrolment + - Plugins +--- + + + + + +import { CodeBlock } from '@site/src/components'; +import { + Lang, + Lib, + VersionPHP, + DbAccessPHP, +} from '../../_files'; + +Moodle provides a number of ways of managing course enrolment, called enrolment plugins. Each course can decide its enabled enrolment plugins instances and any enrolment plugin can define a workflow the user must follow in order to enrol in the course. + +Course enrolment information is stored in tables **enrol**, **user_enrolments** and optionally other custom database tables defined by individual enrolment plugins. By default user enrolments are protected and can not be modified manually by teachers but only via the specific enrolment plugin. + +Enrolment gives users following privileges: + +- User with active enrolment may enter course, other users need either temporary guest access right or moodle/course:view capability. +- "My courses" shows list of active enrolments for current user. +- Course participation - some activities restrict participation to enrolled users only. The behaviour is defined independently by each activity, for example only enrolled users with submit capability may submit assignments, the capability alone is not enough. +- Only enrolled users may be members of groups. +- Gradebook tracks grades of all enrolled users, visibility of grades is controlled by role membership. + +:::caution + +Enrolments and role assignments are separate concepts, you may be enrolled and not have any role and you may have a role in course and not be enrolled. Roles at course context level and below may be controlled by enrolment plugins. + +::: + +## File structure + +All enrolment plugin files must be located inside the **enrol/pluginname** folder. + +
+ View an example directory layout for the `enrol_pluginname` plugin. + +```console + enrol/pluginname/ + |-- db + | `-- access.php + |-- lang + | `-- en + | `-- enrol_pluginname.php + `-- lib.php + `-- version.php +``` + +
+ +Some of the important files for the format plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +### lib.php + +import LibExample from '!!raw-loader!./_examples/lib.php'; + + +The plugin lib.php must contain the plugin base class. + + +Enrolment plugins must extend `enrol_plugin` base class which is defined at the end of lib/enrollib.php. This base class contains all standard methods to define the plugin workflow. + +### lang/en/enrol_pluginname.php + +import langExample from '!!raw-loader!./_examples/enrol_lang.php'; + + + +### db/access.php + +import RepositoryAccessExample from '!!raw-loader!./_examples/access.php'; + + + +Depending on the enrolment workflow, the access.php file should define the following capabilities: + +- **enrol/xxx:enrol** - used when `enrol_plugin::allow_enrol()` returns true. +- **enrol/xxx:unenrol** - used when `enrol_plugin::allow_unenrol()` or `enrol_plugin::allow_unenrol_user()` returns true. +- **enrol/xxx:manage** - used when `enrol_plugin::allow_manage()` returns true. +- **enrol/xxx:unenrolself** - used when plugin support self-unenrolment. +- **enrol/xxx:config** - used when plugin allows user to modify instance properties. Automatic synchronisation plugins do not usually need this capability. + +See [enrolment API methods](#enrolment-api-methods) for more information. + +### version.php + + + +## User enrolment process + +Manual enrolment plugins are the simplest way to handle user enrolments. In the core *enrol_manual*, users with necessary permissions may enrol or unenrol users manually. In the *enrol_flatfile* plugin allows automation of enrolment and unenrolment actions. + +Fully automatic plugins are configured at the system level, they synchronise user enrolments with information stored in external systems (for example *enrol_ldap*, *enrol_database* and *enrol_category*). Some non-interactive plugins may require configuration of enrolment instances (for example *enrol_cohort*). + +Interactive enrolment plugins require user interaction during enrolment (for example: *enrol_self* and *enrol_fee*). These plugins need to override `enrol_plugin::show_enrolme_link()`, `enrol_plugin::enrol_page_hook()` and to implement adding and editing of enrol instance. + +## Enrolment expiration and suspending + +User has active enrolment if all following conditions are met: + +- User has record in `user_enrolments` table, +- User enrolment already started, +- User enrolment is not past timeend, +- User enrolment has active status, +- Enrol instance has active status in `enrol` table, +- Enrol plugin is enabled. + +Most synchronisation plugins include a setting called *External unenrol action*. It is used to decide what happens when previously enrolled user is not supposed to be enrolled any more. Enrol plugins can provide schedulled tasks to synchronize enrolments. + +Plugins that set `timeend` in `user_enrolments` table may want to specify expiration action and optional expiration notification using `enrol_plugin::process_expirations()` and `enrol_plugin::send_expiry_notifications()` methods. + +## Enrolment API methods. + +Each enrolment plugin can define the enrolment workflow by overriding some of the `enrol_plugin` methods. + +### enrol_plugin::get_user_enrolment_actions(): array + +By default, all enrolment plugins will have *editing enrolment* and *user unenrolment* actions. However, some plugins may override this method to add extra actions. + +
+ View example +
+ +```php +/** + * Gets an array of the user enrolment actions + * + * @param course_enrolment_manager $manager + * @param stdClass $userenrolment + * @return array An array of user_enrolment_actions + */ +public function get_user_enrolment_actions(course_enrolment_manager $manager, $userenrolment) { + $actions = parent::get_user_enrolment_actions($manager, $userenrolment); + $context = $manager->get_context(); + $instance = $userenrolment->enrolmentinstance; + $params = $manager->get_moodlepage()->url->params(); + $params['ue'] = $userenrolment->id; + + // Edit enrolment action. + if ($this->allow_manage($instance) && has_capability("enrol/{$instance->enrol}:something", $context)) { + $title = get_string('newaction', 'enrol'); + $icon = new pix_icon('t/edit', ''); + $url = new moodle_url('/enrol/pluginname/something.php', $params); + $actions[] = new user_enrolment_action($icon, $title, $url); + } + + return $actions; +} +``` + +
+
+ +### enrol_plugin::allow_unenrol(): bool + +This method returns true if other code allowed to unenrol everybody from one instance. This method is used on course reset and manual unenrol. + +:::note + +The unenrol action will allow resetif all following conditions are met: + +- The method `enrol_plugin::allow_unenrol()` returns true +- The current user has the `enrol/pluginname:unenrol` capability. + +::: + +
+ View example +
+ +```php +public function allow_unenrol(stdClass $instance) { + // Add any extra validation here. + return true; +} +``` + +
+
+ +### enrol_plugin::allow_unenrol_user(): bool + +This method returns true if other code allowed to unenrol a specific user from one instance. + +:::tip + +If `allow_unenrol_user` is not overridden, the default behaviour is to call `allow_unenrol()` method. + +::: + +:::note + +The unenrol action will be displayed if all following conditions are met: + +- The method `enrol_plugin::allow_unenrol_user()` returns true +- The current user has the `enrol/pluginname:unenrol` capability. + +::: + +
+ View example +
+ +```php +public function allow_unenrol_user(stdClass $instance, stdClass $userenrolment) { + // Add any extra validation here. + return true; +} + +``` + +
+
+ +It is quite common in enrolment plugins to allow unenrol only if the user enrolment is suspended (for example: *enrol_database*, *enrol_flatfile*, *enrol_meta*). + +
+ View suspended enrolment example +
+ +```php +public function allow_unenrol_user(stdClass $instance, stdClass $userenrolment) { + if ($userenrolment->status == ENROL_USER_SUSPENDED) { + return true; + } + return false; +} +``` + +
+
+ +### enrol_plugin::allow_enrol(): bool + +Define if the enrol plugin is compatible with manual enrolments. + +:::note + +The edit manual enrolment action will be displayed if if all following conditions are met: + +- The method `enrol_plugin::allow_enrol()` returns true +- The current user has the `enrol/pluginname:enrol` capability. + +::: + +
+ View example +
+ +```php +public function allow_enrol(stdClass $instance) { + // Add any extra validation here. + return true; +} +``` + +
+
+ +### enrol_plugin::enrol_user() + +This method is the plugin enrolment hook. It will be called when user is enrolled in the course using one of the plugin instances. It is used to alter the enrolment data (for example altering the dates or the role) and also to throw exceptions if some external condions are not met. + +
+ View example +
+ +```php +/** + * Enrol a user using a given enrolment instance. + * + * @param stdClass $instance the plugin instance + * @param int $userid the user id + * @param int $roleid the role id + * @param int $timestart enrolment start timestamp + * @param int $timeend enrolment end timestamp + * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates + * @param bool $recovergrades restore grade history + */ +public function enrol_user( + stdClass $instance, + $userid, + $roleid = null, + $timestart = 0, + $timeend = 0, + $status = null, + $recovergrades = null +) { + // Add validations here. + + parent::enrol_user( + $instance, + $userid, + $roleid, + $timestart, + $timeend, + $status, + $recovergrades + ); +} +``` + +
+
+ +### enrol_plugin:allow_manage(): bool + +Return true if plugin allows manual modification of user enrolments from other code. False is usually returned from plugins that synchronise data with external systems, otherwise the manual changes would be reverted immediately upon synchronisation. + +:::note + +The edit enrolment action in the participants list will be displayed if if all following conditions are met: + +- The method `allow_manage` returns true +- The current user has the `enrol/pluginname:manage` capability. + +::: + +
+ View example +
+ +```php +public function allow_manage(stdClass $instance) { + // Add any extra validation here. + return true; +} +``` + +
+
+ +### enrol_plugin::roles_protected(): bool + +Enrolment plugins can protect roles from being modified by any other plugin. Returning false will allow users to remove all roles assigned by this plugin. By default, this method returns true. + +::: + +
+ View example +
+ +```php +public function roles_protected() { + // Add any extra validation here if necessary. + return false; +} +``` + +
+
+ +## Standard Editing UI + +Moodle participants page has a standard editing UI for manual enrolments. To integrate a plugin into the start UI you need to implement the following methods: + +- `enrol_plugin::use_standard_editing_ui()` +- `enrol_plugin::edit_instance_form()` +- `enrol_plugin::edit_instance_validation()` +- `enrol_plugin::can_add_instance()` +- `enrol_plugin::add_instance()` + +This means that the following functions from the plugin will be called to build the add/edit form, perform validation of the data and add standard navigation links to the manage enrolments page and course navigation. + +
+ View example +
+ +import UiLib from '!!raw-loader!./_examples/ui_lib.php'; + +{UiLib} + +
+
+ +## Sending a welcome email + +Some enrol methods has the support for sending welcome mesages to users. To grant the enrol messages are consistent acorrs enrolments methods, the enrol API provides the `enrol_send_welcome_email_options` function. This method returns a list of all possible options for sending welcome email when the user enrol in a course and each option has a respective constant defined on **enrollib.php**: + +```php +define('ENROL_DO_NOT_SEND_EMAIL', 0); // Do not send the welcome email. +define('ENROL_SEND_EMAIL_FROM_COURSE_CONTACT', 1); // Send welcome email from course contact. +define('ENROL_SEND_EMAIL_FROM_KEY_HOLDER', 2); // Send welcome email from course key holder. +define('ENROL_SEND_EMAIL_FROM_NOREPLY', 3); // Send welcome email from no reply. +``` + +
+ View example +
+ +import MessageLib from '!!raw-loader!./_examples/message_lib.php'; + +{MessageLib} + +
+
+ +## See also + +- [Enrolment API](../../subsystems/enrol.md) diff --git a/versioned_docs/version-4.1/apis/plugintypes/fileconverter/_files/converter.md b/versioned_docs/version-4.1/apis/plugintypes/fileconverter/_files/converter.md new file mode 100644 index 0000000000..da4cf2ea22 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/fileconverter/_files/converter.md @@ -0,0 +1,6 @@ + + + +The `classes/converter.php` class must be defined in the correct namespace for your plugin, and must implement the `\core_files\converter_interface` interface. + +It is responsible for converting files diff --git a/versioned_docs/version-4.1/apis/plugintypes/fileconverter/_files/converter.tsx b/versioned_docs/version-4.1/apis/plugintypes/fileconverter/_files/converter.tsx new file mode 100644 index 0000000000..a1a3702eac --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/fileconverter/_files/converter.tsx @@ -0,0 +1,39 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { Props } from '../../../../_utils'; +import DefaultDescription from './converter.md'; + +const defaultExample = ` +namespace fileconverter_myexample; + +class converter implements \core_files\converter_interface { + // ... +} +`; + +export default (initialProps: Props): typeof ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/fileconverter/index.md b/versioned_docs/version-4.1/apis/plugintypes/fileconverter/index.md new file mode 100644 index 0000000000..2db5fe978f --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/fileconverter/index.md @@ -0,0 +1,100 @@ +--- +title: File Converters +tags: + - File + - core_file + - file_converter + - API + - PDF + - Conversion + - Document +--- + +File converters are an important tool to support other plugins with file conversion supported between a wide range of file formats. File converters are accessed using the [File conversion API](../../subsystems/files/converter.md) and are typically consumed by other plugins rather than by the user directly. + +## File structure + +File converter plugins are located in the `/file/converter` directory. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +
+ View an example directory layout for the `fileconverter_unoconv` plugin. + +```console +files/converter/unoconv +├── classes +│   ├── converter.php +│   └── privacy +│   └── provider.php +├── lang +│   └── en +│   └── fileconverter_unoconv.php +├── settings.php +└── version.php +``` + +
+ +Some of the important files for the fileconverter plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +### Converter class + +import Converter from './_files/converter'; + + + +#### are_requirements_met() + +This function informs the File Converter API whether the system requirements of the plugin are met. That is whether appropriate API keys are present, and the API might be available. + +It should be lightweight to call and cache where required. + +```php title="Example implementation" +public static function are_requirements_met() { + return extension_loaded('my_php_extension'); +} +``` + +#### start_document_conversion() and poll_conversion_status() + +The `start_document_conversion()` function starts a conversion, whilst `poll_conversion_status` should poll for any status update. The following apply: + +- If any failures occur, it should set the conversion status to `\core_files\conversion::STATUS_FAILED` and immediately return. There is no need to update the `$conversion` record in this situation. +- When the conversion process starts, the status should be set to `\core_files\conversion::STATUS_IN_PROGRESS` and the record **must** be updated. This ensures that, should the process take a long time, the current status is accurately reflected. +- Upon successful completion, the status should be updated to `\core_files\conversion::STATUS_COMPLETE` and the newly created `\stored_file` should be stored against the conversion using either the `store_destfile_from_string` or `store_destfile_from_path` function as appropriate. + +#### supports() + +This function allows the plugin to answer whether it supports conversion between two formats. It is typically only used internally by the File Conversion subsystem. + +```php title="Example implementation" +class converter implements \core_files\converter_interface { + // ... + public static function supports($from, $to) { + // This plugin supports conversion from doc and docx to pdf only. + if ($from !== 'doc' && $from !== 'docx') { + return false; + } + + return $to === 'pdf'; + } +} +``` + +```php title="Example usage" +if (\fileconverter_example::supports('jpg', 'pdf')) { + // ... +} +``` + +#### get_supported_conversion() + +This function is used purely for information purposes to display possible conversions to an administrator. + +## See also + +- Using the [File Converter API](../../subsystems/files/converter.md) diff --git a/versioned_docs/version-4.1/apis/plugintypes/filter/_examples/dynamic.js b/versioned_docs/version-4.1/apis/plugintypes/filter/_examples/dynamic.js new file mode 100644 index 0000000000..8b93652604 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/filter/_examples/dynamic.js @@ -0,0 +1,31 @@ +import {eventTypes} from 'core_filters/events'; + +/** @var {bool} Whether this is the first load of videojs module */ +let firstLoad; + +/** + * Initialise the dynamic content filter. + * + * @method + * @listens event:filterContentUpdated + */ +export const init = () => { + if (!firstLoad) { + return; + } + firstLoad = true; + // Add the event listener. + document.addEventListener(eventTypes.filterContentUpdated, contentUpdatedHandler); +}; + +/** + * Notify video.js of new nodes. + * + * @param {Event} event The event. + */ +const contentUpdatedHandler = (event) => { + const updatedContent = event.detail.nodes; + updatedContent.forEach(content => { + // Alter any updated content. + }); +}; diff --git a/versioned_docs/version-4.1/apis/plugintypes/filter/_examples/filter.php b/versioned_docs/version-4.1/apis/plugintypes/filter/_examples/filter.php new file mode 100644 index 0000000000..b812f5f644 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/filter/_examples/filter.php @@ -0,0 +1,6 @@ +class filter_pluginname extends moodle_text_filter { + function filter(string $text, array $options = []) { + // Return the modified text. + return $text; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/filter/index.md b/versioned_docs/version-4.1/apis/plugintypes/filter/index.md new file mode 100644 index 0000000000..42ef7c0dbf --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/filter/index.md @@ -0,0 +1,202 @@ +--- +title: Filter plugins +tags: + - Filter + - Plugins +--- + + + + + + +import { + Since, + CodeBlock +} from '@site/src/components'; + +import { + DbAccessPHP, + Lang, + Lib, + VersionPHP, +} from '../../_files'; + +import { + ComponentFileSummary, +} from '../../../_utils'; + +Filters are a way to automatically transform content before it is output. Filters may be used to: + +- Render embedded equations to images (the TeX filter). +- Automatically convert links to media files to embedded players. +- Automatically convert mentions of glossary terms to links. + +Filters are one of the easiest types of plugin to create. + +Filters are applied to content passed into the `format_string()` and `format_text()` functions, which are part of the [Output API](../subsystems/output). + +## File structure + +Filter plugins are located in the `/filter` directory. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +
+ View an example directory layout for the `filter_pluginname` plugin. + +```console + filter/pluginname/ + |-- lang + | `-- en + | `-- filter_pluginname.php + |-- filter.php + `-- version.php +``` + +
+ +Some of the important files for the filter plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +### filter.php + +import Filter from '!!raw-loader!./_examples/filter.php'; + + + +### version.php + + + +### lang/en/filter_pluginname.php + + +export const langExample = ` + $string['filtername'] = 'Activity names auto-linking'; +`; + + + +## Test a filter + +To enable a filter, go to the [filters administration screen](./index.md) and set the filter active to "On". + +Filters are applied to all text that is printed with the output functions `format_text()` and `format_string()`. To see a filter in action, add some content to a label resource. When you look at that course in the course listing, you should see that your filter has transformed the text accordingly. + +## Filter performance + +It is important to note that all active filters will be called to transform every bit of text output using `format_text()` (headers and content), and `format_string()` (headers only). As a result a filter plugin can cause big performance problems. It is extremely important to uses caches if your filter must retrieve data from the database, or other similar sources. + +If a filter uses a special syntax or it is based on an appearance of a substring in the text, it is recommend to perform a quick and cheap `strpos()` search first prior to executing the full regex-based search and replace. + +
+ View example +
+ +```php +/** + * Example of a filter that uses links in some way. + */ +public function filter($text, array $options = []) { + + if (!is_string($text) or empty($text)) { + // Non-string data can not be filtered anyway. + return $text; + } + + if (stripos($text, '') === false) { + // Performance shortcut - if there is no tag, nothing can match. + return $text; + } + + // Here we can perform some more complex operations with the + // links in the text. +} +``` + +
+
+ +## Local configuration + +Filters can use different configuration depending on the context in which they are called. For example, the glossary filter can be configured such that when displayed in Forum A it only links words from a particular glossary, while in Forum B it links words from a different glossary.. + +To support this behaviour, a filter plugin must provide a `filterlocalsettings.php` file which defines a Moodle form which subclasses the `filter_local_settings_form` class. In addition to the standard formslib methods, you must also define a `save_changes` method. + +
+ View example +
+ +```php title="filterlocalsettings.php" +class pluginfile_filter_local_settings_form extends filter_local_settings_form { + protected function definition_inner(\MoodleQuickForm $mform) { + $mform->addElement( + 'text', + 'word', + get_string('word', 'filter_helloworld'), + ['size' => 20] + ); + $mform->setType('word', PARAM_NOTAGS); + } +} +``` + +
+
+ +All the local configurations can be accessed in the main filter class in the `$this->localconfig` property. + +
+ View example +
+ +```php title="filter.php" +localconfig['word'] ?? 'default'; + return str_replace($search, "Hello $search!", $text); + } +} +``` + +
+
+ +## Filtering dynamic content + +It is possible that page content is loaded by ajax after the page is loaded. In certain filter types (for example MathJax) JavaScript is required to be run on the output of the filter in order to do the final markup. For these types of filters, a JavaScript event is triggered when new content is added to the page (the content will have already been processed by the filter in php). The JavaScript for a filter can listen for these event notifications and reprocess the affected dom nodes. + +The content updated event is registered in the `core_filters/events` module and can be imported as: + +```js +import {eventTypes} from 'core_filters/events'; + +document.addEventListener(eventTypes.filterContentUpdated, eventHandler); +``` + +
+ View example +
+ +import DynamicContent from '!!raw-loader!./_examples/dynamic.js'; + +{DynamicContent} + +
+
diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/format.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/format.php new file mode 100644 index 0000000000..3683a8ca54 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/format.php @@ -0,0 +1,27 @@ +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/filelib.php'); +require_once($CFG->libdir . '/completionlib.php'); + +// Retrieve course format option fields and add them to the $course object. +$format = core_courseformat\base::instance($course); +$course = $format->get_course(); +$context = context_course::instance($course->id); + +// Add any extra logic here. + +// Make sure section 0 is created. +course_create_sections_if_missing($course, 0); + +$renderer = $format->get_renderer($PAGE); + +// Setup the format base instance. +if (!empty($displaysection)) { +$format->set_section_number($displaysection); +} +// Output course content. +$outputclass = $format->get_output_classname('content'); +$widget = new $outputclass($format); +echo $renderer->render($widget); + +// Include any format js module here using $PAGE->requires->js. diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/format_lang.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/format_lang.php new file mode 100644 index 0000000000..e5183221b2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/format_lang.php @@ -0,0 +1,12 @@ + +$string['addsections'] = 'Add section'; +$string['currentsection'] = 'This section'; +$string['deletesection'] = 'Delete section'; +$string['editsection'] = 'Edit section'; +$string['editsectionname'] = 'Edit section name'; +$string['hidefromothers'] = 'Hide section'; +$string['newsectionname'] = 'New name for section {$a}'; +$string['pluginname'] = 'pluginname format'; +$string['privacy:metadata'] = 'The Sample format plugin does not store any personal data.'; +$string['sectionname'] = 'Section'; +$string['showfromothers'] = 'Show section'; diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib.php new file mode 100644 index 0000000000..3c2e9152ea --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib.php @@ -0,0 +1,104 @@ + +class format_pluginname extends core_courseformat\base { + + /** + * Returns true if this course format uses sections. + * + * @return bool + */ + public function uses_sections() { + return true; + } + + public function uses_indentation(): bool { + return false; + } + + public function uses_course_index() { + return true; + } + + /** + * Returns the information about the ajax support in the given source format. + * + * The returned object's property (boolean)capable indicates that + * the course format supports Moodle course ajax features. + * + * @return stdClass + */ + public function supports_ajax() { + $ajaxsupport = new stdClass(); + $ajaxsupport->capable = true; + return $ajaxsupport; + } + + public function supports_components() { + return true; + } + + /** + * Whether this format allows to delete sections. + * + * Do not call this function directly, instead use {@link course_can_delete_section()} + * + * @param int|stdClass|section_info $section + * @return bool + */ + public function can_delete_section($section) { + return true; + } + + /** + * Indicates whether the course format supports the creation of a news forum. + * + * @return bool + */ + public function supports_news() { + return true; + } + + /** + * Returns the display name of the given section that the course prefers. + * + * This method is required for inplace section name editor. + * + * @param int|stdClass $section Section object from database or just field section.section + * @return string Display name that the course format prefers, e.g. "Topic 2" + */ + public function get_section_name($section) { + $section = $this->get_section($section); + if ((string)$section->name !== '') { + return format_string( + $section->name, + true, + ['context' => context_course::instance($this->courseid)] + ); + } else { + return $this->get_default_section_name($section); + } + } +} + +/** + * Implements callback inplace_editable() allowing to edit values in-place. + * + * This method is required for inplace section name editor. + * + * @param string $itemtype + * @param int $itemid + * @param mixed $newvalue + * @return inplace_editable + */ +function format_pluginname_inplace_editable($itemtype, $itemid, $newvalue) { + global $DB, $CFG; + require_once($CFG->dirroot . '/course/lib.php'); + if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') { + $section = $DB->get_record_sql( + 'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?', + [$itemid, 'pluginname'], + MUST_EXIST + ); + $format = core_courseformat\base::instance($section->course); + return $format->inplace_editable_update_section_name($section, $itemtype, $newvalue); + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib_components.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib_components.php new file mode 100644 index 0000000000..32d9a1a0ce --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib_components.php @@ -0,0 +1,8 @@ +class format_PLUGINNAME extends core_courseformat\base { + + // More methods can be added here. + + public function supports_components() { + return true; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib_course_index.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib_course_index.php new file mode 100644 index 0000000000..dbb6109248 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/lib_course_index.php @@ -0,0 +1,8 @@ +class format_PLUGINNAME extends core_courseformat\base { + + // More methods can be added here. + + public function uses_course_index() { + return true; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/cmitem.mustache b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/cmitem.mustache new file mode 100644 index 0000000000..7b0d80da79 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/cmitem.mustache @@ -0,0 +1,8 @@ +{{! + Include the core content/section/cmitem template. +}} +{{< core_courseformat/local/content/section/cmitem }} + {{! + Add any cm item blocks override here. + }} +{{/ core_courseformat/local/content/section/cmitem }} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/cmitem.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/cmitem.php new file mode 100644 index 0000000000..670db67e0c --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/cmitem.php @@ -0,0 +1,16 @@ + +namespace format_pluginname\output\courseformat\content\section; + +use core_courseformat\output\local\content\section\cmitem as cmitem_base; + +class cmitem extends cmitem_base { + + /** + * Returns the output class template path. + * + * This method redirects the default template when the section activity item is rendered. + */ + public function get_template_name(\renderer_base $renderer): string { + return 'format_pluginname/local/content/section/cmitem'; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/content.mustache b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/content.mustache new file mode 100644 index 0000000000..377189df1c --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/content.mustache @@ -0,0 +1,14 @@ +{{! + Include the core content template. + This is the top template for a course. +}} +{{< core_courseformat/local/content }} + {{! + Add any general structure blocks override here. + }} + + {{! Mandatory the content/section block. }} + {{$ core_courseformat/local/content/section }} + {{> format_pluginname/local/content/section }} + {{/ core_courseformat/local/content/section }} +{{/ core_courseformat/local/content }} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/content.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/content.php new file mode 100644 index 0000000000..6432366ea0 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/content.php @@ -0,0 +1,16 @@ + +namespace format_pluginname\output\courseformat; + +use core_courseformat\output\local\content as content_base; + +class content extends content_base { + + /** + * Returns the output class template path. + * + * This method redirects the default template when the course content is rendered. + */ + public function get_template_name(\renderer_base $renderer): string { + return 'format_pluginname/local/content'; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/renderer.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/renderer.php new file mode 100644 index 0000000000..e9821f2314 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/renderer.php @@ -0,0 +1,9 @@ + +namespace format_pluginname\output; + +use core_courseformat\output\section_renderer; +use moodle_page; + +class renderer extends section_renderer { + // Your renderer methods goes here. +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/section.mustache b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/section.mustache new file mode 100644 index 0000000000..3225b76066 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/section.mustache @@ -0,0 +1,12 @@ +{{! + Include the core content/section template. +}} +{{< core_courseformat/local/content/section }} + {{! + Add any section blocks override here. + }} + {{! Mandatory cmitem override }} + {{$ core_courseformat/local/content/section/cmitem }} + {{> format_pluginname/local/content/section/cmitem }} + {{/ core_courseformat/local/content/section/cmitem }} +{{/ core_courseformat/local/content/section }} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/section.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/section.php new file mode 100644 index 0000000000..d1d8e4c64f --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/output/section.php @@ -0,0 +1,16 @@ + +namespace format_pluginname\output\courseformat\content; + +use core_courseformat\output\local\content\section as section_base; + +class section extends section_base { + + /** + * Returns the output class template path. + * + * This method redirects the default template when the course section is rendered. + */ + public function get_template_name(\renderer_base $renderer): string { + return 'format_pluginname/local/content/section'; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/pluginskel_recipe.yaml b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/pluginskel_recipe.yaml new file mode 100644 index 0000000000..a57d181216 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/pluginskel_recipe.yaml @@ -0,0 +1,84 @@ +## This is an example recipe file that you can use as a template for your own plugins. +## See the list of all files it would generate: +## +## php generate.php example.yaml --list-files +## +## View a particular file contents without actually writing it to the disk: +## +## php generate.php example.yaml --file=version.php +## +## To see the full list of options, run: +## +## php generate.php --help +## +--- +## Frankenstyle component name. +component: format_pluginname + +## Human readable name of the plugin. +name: Example pluginname format + +## Human readable release number. +release: "0.1.0" + +## Plugin version number, e.g. 2016062100. Will be set to current date if left empty. +#version: 2016121200 + +## Required Moodle version, e.g. 2015051100 or "2.9". +requires: "4.0" + +## Plugin maturity level. Possible options are MATURIY_ALPHA, MATURITY_BETA, +## MATURITY_RC or MATURIY_STABLE. +maturity: MATURITY_BETA + +## Copyright holder(s) of the generated files and classes. +copyright: YOURNAME + +## Features flags can control generation of optional files/code fragments. +features: + readme: true + license: true + + ## Privacy API implementation +privacy: + haspersonaldata: false + uselegacypolyfill: false + +format_features: + # Create the Moodle 4.0+ basic template structure. + basic_outputs: true + + # General format features. + uses_sections: true + uses_course_index: true + uses_indentation: false + uses_inplace_editor: true + uses_reactive_components: true + uses_news: true + +## Explicitly added strings +lang_strings: + - id: mycustomstring + text: You can add 'extra' strings via the recipe file. + - id: mycustomstring2 + text: Another string with {$a->some} placeholder. + + ## Needed for course format plugins. + - id: addsections + text: Add section + - id: currentsection + text: This section + - id: editsection + text: Edit section + - id: editsectionname + text: Edit section name + - id: deletesection + text: Delete section + - id: newsectionname + text: New name for section {$a} + - id: sectionname + text: Section + - id: hidefromothers + text: Hide section + - id: showfromothers + text: Show section diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_examples/renderer.php b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/renderer.php new file mode 100644 index 0000000000..a29a9cad99 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/_examples/renderer.php @@ -0,0 +1,41 @@ +namespace format_pluginname\output; + +use core_courseformat\base as format_base; +use core_courseformat\output\section_renderer; +use moodle_page; + +/** +* Basic renderer for pluginname format. +* +* @copyright 2022 Someone + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + class renderer extends section_renderer { + // Override any necessary renderer method here. + + /** + * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page. + * + * This method is required to enable the inplace section title editor. + * + * @param section_info|stdClass $section The course_section entry from DB + * @param stdClass $course The course entry from DB + * @return string HTML to output. + */ + public function section_title($section, $course) { + return $this->render(format_base::instance($course)->inplace_editable_render_section_name($section)); + } + + /** + * Generate the section title to be displayed on the section page, without a link. + * + * This method is required to enable the inplace section title editor. + * + * @param section_info|stdClass $section The course_section entry from DB + * @param int|stdClass $course The course entry from DB + * @return string HTML to output. + */ + public function section_title_without_link($section, $course) { + return $this->render(format_base::instance($course)->inplace_editable_render_section_name($section, false)); + } + } diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_files/course_editor_workflow.png b/versioned_docs/version-4.1/apis/plugintypes/format/_files/course_editor_workflow.png new file mode 100644 index 0000000000..c4013b05fd Binary files /dev/null and b/versioned_docs/version-4.1/apis/plugintypes/format/_files/course_editor_workflow.png differ diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/_files/course_format_output.png b/versioned_docs/version-4.1/apis/plugintypes/format/_files/course_format_output.png new file mode 100644 index 0000000000..794c0dd84c Binary files /dev/null and b/versioned_docs/version-4.1/apis/plugintypes/format/_files/course_format_output.png differ diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/index.md b/versioned_docs/version-4.1/apis/plugintypes/format/index.md new file mode 100644 index 0000000000..4b5a229830 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/index.md @@ -0,0 +1,631 @@ +--- +title: Course format +tags: + - Plugins + - Format +--- + + + + + + +import { CodeBlock, Since } from '@site/src/components'; +import { getExample } from '@site/src/moodleBridge'; +import { ComponentFileSummary } from '../../../_utils'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import { + Lang, + Lib, + VersionPHP, +} from '../../_files'; + +Course formats are plugins that determine the layout of course resources. + +Course formats determine how the course main page looks like (/course/view.php) in both view and editing mode. They are also responsible for building a navigation tree inside the course (displayed to users in the navigation block, course index, and breadcrumb). They can organize the course content in sections. The course creator or teacher can specify the course format for the course in the course edit form. + +Course formats also can add their own options fields to the course edit form. They can add course-dependent content to the header/footer of any page inside the course, not only /course/view.php + +## File structure + +All course format files must be located inside the **course/format/pluginname** folder. + +
+ View an example directory layout for the `format_pluginname` plugin. + +```console + course/format/pluginname/ + |-- classes + | `-- output + | `-- courseformat + | `-- (Overridden outputs) + | `-- renderer.php + |-- db + | `-- access.php + |-- lang + | `-- en + | `-- format_pluginname.php + |-- format.php + |-- lib.php + `-- version.php +``` + +
+ +Some of the important files for the format plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +### format.php + +import Format from '!!raw-loader!./_examples/format.php'; + + + +As it can be seen in the example, once the format base instance is created (the return object of `core_courseformat\base::instance` function), the plugin must define any necessary setting (in the example is just set_section_number but any plugin can define its own extra methods). Once this is done, the get_output_classname will return the correct class name for the content output and the format renderer will be responsible for rendering the full course. See [Rendering a course](#rendering-a-course) for more information. + +### lib.php + +import LibExample from '!!raw-loader!./_examples/lib.php'; + + +The main library of the format. It should contain a class `format_pluginname` extending `core_courseformat\base`, this class is known as the format base class. Also, it may contain callbacks for other core and contributed APIs if necessary. + + +The format base class is the most important part of the course format as it defines how the format interacts with both frontend and backend. Depending on the methods a format plugin overrides the course will behave in different ways. + +### lang/en/format_pluginname.php + +import langExample from '!!raw-loader!./_examples/format_lang.php'; + + + +### classes/output/renderer.php + +import Renderer from '!!raw-loader!./_examples/renderer.php'; + + + +This class should: + +- use the namespace `format_pluginname\output` (all files in the classes folder should be namespaced) +- be called renderer (all files in the classes folder match the name of the file) +- extend `core_courseformat\output\section_renderer`. See Output renderers for more information. +- Use the namespace `format_pluginname\output` (all files in the classes folder should be namespaced) +- The class should be called renderer (all files in the classes folder match the name of the file) +- Should extend `core_courseformat\output\section_renderer`. See Output renderers for more information. + +Renderer methods are powerful enough to override all the rendering logic of the course. However, by default, the way to render a course should be based on output classes and templates (see the [output structure section](#format-output-classes-and-templates) for more information). + +Since the course format is all about the display it is very important to separate HTML and PHP. All HTML code should be in your `format_pluginname_renderer` class in renderer.php. Ideally, format.php will only call one function from the renderer. Use of renderer is required if you want to output content header and footer. + +### version.php + + + +### classes/output/courseformat/ + + + +The course rendering is based on output classes and templates. Course format plugins can override specific output classes to provide alternative data to the templates. All output classes inside output/courseformat folder will override the default ones automatically. See the output structure section below for more information + +### templates/ and templates/local/ + + + +The course rendering is based on output classes and templates. This folder will contain the specific mustache templates of your plugin. See the [override mustache blocks section](#override-mustache-blocks) section for more information. + +## Creating a new format + +### Using tool_pluginskel + +The easiest way to create a new course format using the latest version of tool_pluginskel. You can use the following yaml file to generate a basic course format skeleton. It is important to note that the "requires" attributes should be at least 4.0 in order to generate a Moodle 4.0+ version of the plugin, otherwise the resulting plugin will use a deprecated structure. + +
+ View pluginskel recipe +
+ +import PluginskelRecipe from '!!raw-loader!./_examples/pluginskel_recipe.yaml'; + +{PluginskelRecipe} + +
+
+ +### Manual option: copy the code from an existing code + +However, if for some reason you cannot use the latest version of tool_pluginskel, you can copy the code from the topics or weeks formats. Once you do the copy you should: + +1. Copy the folder containing the format files. +2. Rename the folder to the new name. Course format names cannot exceed 21 characters. +3. Rename language files in course/format/pluginname/lang/ +4. Change `$string['pluginname']` in course/format/pluginname/lang/en/format_pluginname.php to the new name. +5. Rename class name in lib.php to format_pluginname. +6. Search and replace other occurrences of the old format name, for example in renderer, capabilities names, settings, JavaScript libraries, etc. +7. The new format is ready for modification. +8. After modifying the code, check it with the Code checker. + +## Upgrading format to the next Moodle version + +Read the files course/format/upgrade.txt, lib/upgrade.txt, and also upgrade.txt of the core APIs that you use in your course format plugin and make changes according to them. When testing don't forget to enable Developer debugging level and error display (Settings->Developer->Debugging). + +In case your plugin is a Moodle 3.11 compatible plugin, see the [migration guide](./format/migration) for more information. + +## Extending the format base class + +All format plugins require a lib.php file containing a format_pluginname class. This class should extend `core_courseformat\base` and define how the plugin will integrate with the core_courseformat subsystem. + +Here are the main features in course formats and responsible for them format_base functions. + +## Course sections + +There is existing functionality in Moodle core to support organizing course modules into sections. Course format plugins do not have to use them but most of them do. Database table {course_sections} stores basic information about sections. Also section info is cached and returned by `format->get_modinfo()` (or the global function `get_fast_modinfo()`) so every action changing the sections must be followed by rebuild_course_cache(). Course module must always belong to the section. Even if your course format does not use sections, the section with the number 0 is always created. + +You must define `$string['sectionname']` if your language file even if the format does not use sections because it can be called unconditionally from other parts of the code, even though it won't be displayed. + +| `core_courseformat\base` Overridable method | Description | +|---|---| +| `uses_sections()` | returns true or false if the format uses sections or not. There is a global function
`course_format_uses_sections()` that invokes it. It affects default navigation tree building. Various modules and reports may call this function to know whether to display the section name for the particular module or not. | +| `get_default_section_name()` | This method gets the default section name if the user has not provided a |value for the section name. In format_base, it basically calls `get_section_name()`, which returns the `$string['sectionname']` + the section number of the current section, if available, or blank, otherwise. It can be used in conjunction with your course format's `get_section_name()` implementation. For reference, please refer to the implementations in format_topics and format_weeks classes. | +| `get_section_name()` | Returns the name for a particular section. This function may be called often so it should use only fields cached in section_info object (field course_sections.name is always cached)
In 3.0+, it checks if the `$string['sectionname']` is available in the lang file. If the section name string is not available, it returns an empty string. | +| `get_view_url()` | Returns the URL for a particular section, it can be either anchored on course view page or separate page. See parent function PHPdocs for more details | +| `is_section_current()` | Specifies if the section is current, for example, current week or highlighted topic. This function is only used if your renderer uses it for example if your renderer extends format_section_renderer_base. This function is not called from anywhere else in Moodle core. | +| `get_section_highlighted_name()` | Return the textual label for a current/highlighted section.| +| `set_section_number()` | Setup the format base instance to display a single section instead of all. This method is used to prepare the format base instance to render the course. | +| `get_section_number()` | Return zero if the course will deploy all sections or a section number if the current page is only presenting a single section. | +| `get_course_display()` | Return `COURSE_DISPLAY_SINGLEPAGE` or `COURSE_DISPLAY_MULTIPAGE` depending if the course has multiple section per page or not. | +| `get_last_section_number()` | Returns the last section | +| `get_max_sections()` | Returns the maximum number of sections this format can contain | +| `page_title()` | Formats can override this method to alter the page title. | + +### Course features + +The format base class has several methods to integrate the course format with the frontend course editor and its webservices. + +| `core_courseformat\base` Overridable method | Description | +|---|---| +| `ajax_section_move()` | Code executed after sections were rearranged using drag and drop. See the example in format_topics where sections have automatic names depending on their sequence number | +| `uses_course_index()` | Return true if the course format is compatible with the course index drawer. Note that classic based themes are not compatible with the course index. | +| `uses_indentation()` | If the format uses the legacy activity indentation. | +| `supports_components()` | Since Moodle 4.0 the course is rendered using reactive UI components. This kind of component will be the only standard in Moodle 4.3+ but, until then, formats can override this method to specify if they want to use the previous UI elements or the new ones. | +| `supports_news()` | Determine if the news forum is mandatory or not on the course format. | +| `get_default_blocks()` | Course format can specify which blocks should be added to the course page when the course is created in this format.
If the course format is changed during course edit, blocks are not changed.
Whatever course format specifies in the method, site admin can override it with `$CFG->defaultblocks_override` or `$CFG->defaultblocks_pluginname` | +| `extend_course_navigation()` | This function is called when a navigation tree is built.
Node for the course will be created in navigationlib for you and all standard available branches like 'Participants' or 'Reports' will be added to it. After that course format can add nodes for sections and modules. There is a default implementation that adds branches for course sections and modules under them. Or if the course format does not use sections, all modules will just be placed under course mode. The course format is able to override the default navigation tree building.
Note that if navigationlib can not find the node for the current course module, the node will be added automatically (after this callback). | + +### Course format options + +The core table {course_format_options} in Moodle database is designed to store additional options for course formats. Those options may belong for the whole course or just for course section. + +Course format options must not have the same names as fields in database table {course}, section options must not have the same names as fields in {course_sections}. Also, make sure names do not duplicate completion and conditional fields in edit forms. + +When the teacher changes the course format in the course edit form AND the old and the new course formats share the same option name, the value of this option is copied from one format to another. For example, if the course had format Topics and had 8 sections in it and teacher changes format to Weeks, the course will have 8 weeks in it. + +During backup the course format options are stored as if they were additional fields in {course} table. Do not store IDs of elements (courses, sections, etc.) in course format options because they will not be backed up and restored properly. You can use section numbers because they are relative inside the course. If absolute ids are necessary you can create your own backup/restore scripts, see Backup API. + +Webservices expect course format options to be passed in additional entities but for backward compatibility `numsections`, `hiddensections` and `coursedisplay` can also be passed as if they were fields in {course} table. + +| `core_courseformat\base` Overridable method | Description | +|---|---| +| `course_format_options()` | By overriding this method course format specifies which additional options it has for course | +| `section_format_options()` | By overriding this method course format specifies which additional options it has for course section. Note that since section information is cached you may want to cache some additional options as well. See PHPdocs for more information | +| `get_format_options()` | (usually no need to override) low level function to retrieve course format options values. It is more convenient to use methods get_course() and get_section() | +| `create_edit_form_elements()` | This function is called to alter course edit form and standard section edit form. The default implementation creates simple form elements for each option defined in either `course_format_options()` or `section_format_options()`. Overwrite it if you want to have more comprehensive form elements or if you do not want options to appear in edit forms, etc. | +| `edit_form_validation()` | Overwrite if course format plugin needs additional validation for it's option in course edit form | +| `update_format_options()` | (usually no need to override) low level function to insert/update records in db table {course_format_options} | +| `update_course_format_options()` | updates course format options with the data from the edit course form. The plugin can override for example to include calculated options fields, especially when the course format is being changed. For example, format_topics and format_weeks automatically fill field `numsections` when the user switches from other format | +| `update_section_format_options()` | updates course format options for the section with the data from the edit section form | +| `editsection_form()` | Return an instance of moodleform to edit a specified section. Default implementation returns instance of `editsection_form` that automatically adds additional fields defined in section_format_options() | +| `get_default_course_enddate()` | Overwrite if the course format is time-based. The base class calculates the default course end date based on the number of sections. | +| `delete_format_data()` | This hook method is called when the course is deleted and can be used to remove any course data not stored in the standards {course_format_options} and {course} tables (like user preferences, for example). | + +Course format base helpers +The format base class is used for all the core_courseformat integrations, from settings to rendering. All course output classes will receive the course format instance as a primary param and the class has several helper methods to get information about the format. + +| `core_courseformat\base` Overridable method | Description | +|---|---| +| `get_course()` | (no need to override) returns object with all fields from db table {course} AND all course format options | +| `get_section()` | (no need to override) returns instance of section_info. It will contain all fields from table {course_sections} and all course format options for this section | +| `get_sections()` | Return all course sections (it is just a wrapper fo the modinfo get_section_info_all) | +| `get_course_display()` | Returns if the course is using a multi page or a single page display (`COURSE_DISPLAY_MULTIPAGE` or `COURSE_DISPLAY_SINGLEPAGE`) | +| `get_modinfo()` | Returns the current course modinfo (equivalent to get_fast_modinfo but without specifying the course) | +| `get_renderer()` | Return the course format renderer instance | +| `get_output_classname()` | This method gets a relative output class path (for example, "content\\section") and returns the correct output class namespace depending on if the format has overridden outputs or not. See [overriding output classes](#override-output-classes) section for more information. | +| `is_section_current()` | Returns if a specific section is marked as current (highlighted) or not. | +| `show_editor()` | Do all the user and page validations to know if the current course display has to include editor options or not. This includes both page editing mode and user capabilities. You can pass an array of capabilities which should be checked. If none specified, will default to `moodle/course:manageactivities`. | + +## Rendering a course + +Each format plugin is responsible for rendering the course in the format.php file, this means each plugin can choose how to render the course content. However, there are some conventions on how a course should be rendered to integrate the plugin with the existing components. + +The course rendering is done using four mains elements: + +- **File view.php:** responsible for setting up the format base instance and rendering the content. +- **Course format renderer:** all format plugins must provide its own version of the `core_courseformat\output\section_renderer` (or provide an equivalent class with all the necessary methods). +- **Output classes:** by default all course elements have a specific output class inside course/format/output/local folder. Each one of them is responsible for generating the necessary data to render the templates. Format plugins can provide alternative versions of those output classes (see [override output classes](#override-output-classes) for more information) +- **Mustache templates:** each output class has its equivalent mustache template inside course/format/templates. All the course content is rendered using a single "content" template that includes all the rest as sub-templates. For this reason, a plugin that wants to override some templates must provide some extra templates in order to keep the template structure. See [Creating the basic output structure](#creating-the-basic-output-structure) section for more information. + +Unless there's a reason for it, the course structure should be rendered overriding the standard outputs and mustache templates. + +## Format output classes and templates + +The following diagram shows the standard output classes structure: + +![Output classes structure](./_files/course_format_output.png) + +There are three renderer methods used for refreshing fragments of the course page: + +- **render_content:** used to render the full course. This method is not necessary as the content output itself will be used by default. +- **course_section_updated:** needed when the frontend needs to render a particular section. It is used mostly when a new section is created. By default it renders the core_courseformat\output\local\content\section output. +- **Course_section_updated_cm_item:** used every time the frontend needs to update an activity card on the course page. By default it will render the core_courseformat\output\local\content\section\cmitem. The reason why it uses this specific output is that it renders the full course module list item, not just the activity card. + +By default, the base renderer methods will use the format output components to render the full course. In case your plugins have special needs, it is possible to override those three methods directly into the format renderer class. + +### Override output classes + +Instead of having several renderer methods on a single file, the core_courseformat subsystem splits the output logic through several small classes, each one for a specific UI component. Format plugins can easily override specific classes to alter the template data. + +The course format base class has a special method called **get_output_classname** that returns the overridden class name if available in the format plugin (or the core one if not). In order to detect the format classes, your plugin must place the overridden one in the equivalent path inside your plugin format_pluginname\output\courseformat\ folder + +For example, if a format plugin wants to add new options to the section action menu it should override the core_courseformat\output\local\content\section\controlmenu. To do so the plugin class should be format_pluginname\output\courseformat\content\section\controlmenu. You can find an example of an overridden output in the "course/format/topics/classes/output/courseformat/content/section/controlmenu.php" file. + +### Creating the basic output structure + +By default, the course renderer will use the core_courseformat output classes and templates. To override some parts of the course elements the plugin must provide a minimum output classes and template structure. + +#### Basic output classes + +It is recommended your plugin overrides the 3 main course format elements: + +- format_pluginname\output\courseformat\content +- format_pluginname\output\courseformat\content\section +- format_pluginname\output\courseformat\content\section\cmitem + +Those output classes should extend the equivalent core ones but, at least, they should override the **get_template_name** method to redirect the rendering template to the overridden template. It could also override the export_for_template to alter the template data if necessary. +This is the minimum output classes your plugin must provide: + + + + +import OutputContent from '!!raw-loader!./_examples/output/content.php'; +const OutputContentProps = { + examplePurpose: 'Output content', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/output/courseformat/content.php', +}; + +
{getExample(OutputContentProps, OutputContent)}
+ +
+ + +import OutputSection from '!!raw-loader!./_examples/output/section.php'; +const OutputSectionProps = { + examplePurpose: 'Output section', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/output/courseformat/content/section.php', +}; + +
{getExample(OutputSectionProps, OutputSection)}
+ +
+ + +import OutputCmitem from '!!raw-loader!./_examples/output/cmitem.php'; +const OutputCmitemProps = { + examplePurpose: 'Output cmitem', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/output/courseformat/content/section/cmitem.php', +}; + +
{getExample(OutputCmitemProps, OutputCmitem)}
+ +
+
+ +#### Basic template files + +Unlike output classes, mustache files cannot be extended nor overridden. To be able to alter specific mustaches your plugin must provide a minimum template structure. To allow partial overriding, the core_courseformat template uses blocks instead of inclusions to include sub templates. + +This is the minimum template structure your plugin must provide: + + + + +import TemplateContent from '!!raw-loader!./_examples/output/content.mustache'; + +{TemplateContent} + + + + +import TemplateSection from '!!raw-loader!./_examples/output/section.mustache'; + +{TemplateSection} + + + + +import TemplateCmitem from '!!raw-loader!./_examples/output/cmitem.mustache'; + +{TemplateCmitem} + + + + +### Override mustache blocks + +Once your plugin has the basic mustache structure, you can provide extra mustache blocks to override parts of the page. To do so it is important to understand first the special way in which the course format mustaches are included. + +Most moodle mustache files include sub-templates by doing {{> path/to/the/template }}. However, in the course format subsystems, all sub-templates are loaded using a slightly different pattern. + +
+ View example: override course format templates using mustache blocks +
+ +For example, imagine a mustache file "original/path/parent" including "original/path/to/the/template": + +```handlebars +{{$ original/path/to/the/template }} + {{> original/path/to/the/template }} +{{/ original/path/to/the/template }} +``` + +Using this pattern any parent template can replace the sub-template doing: + +```handlebars +{{! Then include the parent template }} +{{< original/path/parent }} + {{! Add custom blocks. }} + {{$ original/path/to/the/template }} + {{> new/template/path }} + {{/ original/path/to/the/template }} +{{/ original/path/parent }} +``` + +Or can wrap the content with extra HTML: + +```handlebars +{{! Then include the parent template }} +{{< original/path/parent }} + {{! Add custom blocks. }} + {{$ original/path/to/the/template }} +
+ {{> new/template/path }} +
+ {{/ original/path/to/the/template }} +{{/ original/path/parent }} +``` + +Of even replace the fill block by an alternative HTML: + +```handlebars +{{! Then include the parent template }} +{{< original/path/parent }} + {{! Add custom blocks. }} + {{$ original/path/to/the/template }} +
+ Here you can use the template {{outputdata}} +
+ {{/ original/path/to/the/template }} +{{/ original/path/parent }} +``` + +
+
+ +Due to the fact that mustache blocks are not scoped, blocks can be overridden by any of the parent templates. This generates some possible scenarios depending on your format needs: + +- **[Scenario 1](#scenario-1-adding-blocks-directly-on-the-three-main-mustache-templates):** your format just overrides a few course elements like adding menu options o tweaking the sections or activity HTML. +- **[Scenario 2](#scenario-2-keep-all-the-intermediate-templates-structure):** your plugin needs a big UI change, keeping the general structure but altering several course elements. +- **[Scenario 3](#scenario-3-just-keep-a-few-renderer-methods):** Your plugin is a completely different thing that does not follow any standard course rule. Almost everything in your format is done from scratch. + +#### Scenario 1: adding blocks directly on the three main mustache templates + +If your format only overrides a few inner templates of the course, the overriding blocks can be located in one of the 3 basic templates: + +- **local/content:** for general course structure elements +- **local/content/section:** to alter section elements +- **local/content/section/cmitem:** to alter activity elements + +
+ View example +
+ +If a format requires to override the activity visibility badges your format_pluginname/local/content/section/cmitem template will look like: + +```handlebars +{{! include the original course format template block }} +{{< core_courseformat/local/content/section/cmitem }} + {{! Add custom blocks here. }} + {{$ core_courseformat/local/content/section/badges }} + {{> format_pluginname/local/content/cm/badges }} + {{/ core_courseformat/local/content/section/badges }} +{{/ core_courseformat/local/content/section/cmitem }} +``` + +
+
+ +Benefits of this approach: + +- Easy to maintain in a short term. All templates overrides are located on one of the 3 main templates. +- Fewer files. The plugin only contains the three mains files and the overridden ones. + +Negatives of this approach: + +- Harder to maintain in the long run. Is it possible that the format must refactor the template structure if future versions require more main templates to be refreshed via ajax. + +#### Scenario 2: keep all the intermediate templates structure + +If your plugin needs a big UI change, altering a considerable number of the course elements at different levels (course, section and/or activity), the previous approach is not recommended as it may require more code refactoring in future versions. + +To keep your plugin more stable through time the best approach is to override the mustache blocks at the inner parts of the mustache files structure instead of at the main elements. It will require more code but the final result will be more stable. + +
+ View example +
+ +Let's say your format requires overriding the activity visibility badges as in the previous scenario example. Apart from the three main templates, the plugin must create several new template overrides until it reacher the activity badges one: + +CM item main file: format_pluginname/local/content/section/cmitem template: + +```handlebars +{{! include the original course format template block }} +{{< core_courseformat/local/content/section/cmitem }} + {{$ core_courseformat/local/content/cm }} + {{> format_pluginname/local/content/cm }} + {{/ core_courseformat/local/content/cm }} +{{/ core_courseformat/local/content/section/cmitem }} +``` + +The content/cm template: + +```handlebars +{{< core_courseformat/local/content/cm/activity }} + {{$ core_courseformat/local/content/cm/badges }} + {{> format_pluginname/local/content/cm/badges }} + {{/ core_courseformat/local/content/cm/badges }} +{{/ core_courseformat/local/content/cm/activity }} +``` + +The content/cm/badges will contain only the overridden HTML. + +Apart from the templates files, the plugin could also provide overridden output classes to ensure that future versions will remain compatible if new ajax partials are required: + +In the example the extra output classes can look like: + +```php tile="format_pluginname\output\local\content\cm class" + +
+ +Benefits of this approach: + +- Easy to maintain in the long term. The code will remain the same if future versions require more main templates. +- Overridden templates can be rendered as a regular course format output. Because each of the overridden mustaches has also its output class, the course format subsystem can render them independently. + +Negatives of this approach: + +- Requires extra files to keep the structure. Most of the files are just there to ensure the course format subsystem knows how to render them if needed in the future. + +#### Scenario 3: just keep a few renderer methods + +If your plugin is a completely different thing from a regular course you are most likely on your own. Your format may use a completely different set of renderer methods or output classes and you already have your own template structure. + +In that case, you may keep it that way. However, there are a few things you could add to your plugin in order to make it compatible with the new course output. Take in mind that the new course editor expects some conventions about renderer methods that should be easy to incorporate into your plugin. + +The first thing you should add is all the section and activities data attributes (see [course elements data attributes](#course-elements-data-attributes). The new course editor did not use CSS classes anymore but data attributes. If your format should be able to interact with the standard editor those attributes are necessary. + +Secondly, the new frontend JS modules use renderer methods to refresh a full section or an activity. If your format wants to keep this feature you should implement two methods on your renderer class: + +- **course_section_updated:** to render a single section. +- **course_section_updated_cm_item:** to render a single course module item. Note that this method does not render an activity card only but also the full course item. In a regular course, this also includes the "li" element. + +And third, consider using "local/content" as your main course template, "output\local\content" as your main output class or, if you don't use output classes, use the render_content renderer method to print a full course. Those are the expected names to render the full course. For now, they are only used in your plugin "format.php" file but nobody can guarantee this will continue this way in the future. + +## The course editor structure + +The core_courseformat provides several JavaScript modules that will be enabled when a teacher edits the course. Those libraries use a reactive pattern to keep the course updated when some edit action is executed. + +The following diagram represents the data flow of the new architecture: + +![Course editor workflow](./_files/course_editor_workflow.png) + +### Enabling the course editor + +To enable the course editor in your format you should add the following method to your format base class (in your course/format/pluginname/lib.php): + +```php +/** + * Enable the component based content. + */ +public function supports_components() { + return true; +} +``` + +### Course elements data attributes. + +The course editor modules use data attributes to find the course elements in the page. If your plugin alters the default templates you should keep those attributes in your HTML structure. + +The following table describes the data attributes: + +| Concept | Required data attributes | +|---------|------------------------| +| **Section** | `data-for="section"`
`data-id={SECTION.ID}`
`data-number={SECTION.NUM}` | +| **Section header** | `data-for="section_title"`
`data-id={SECTION.ID}
data-number={SECTION.NUM}` | +| **Course module item (activity)** | `data-for="cmitem"`
`data-id={CM.ID}` | +| **Course sections list** | `data-for="course_sectionlist"` | +| **Section course modules list** | `data-for="cmlist"` | +| **Course module action link** | `data-action={ACTIONNAME}`
`data-id={CM.ID}` | +| **Section action link** | `data-action={ACTIONNAME}`
`data-id={SECTION.ID}` | +| **Section info** | `data-for="sectioninfo"` | diff --git a/versioned_docs/version-4.1/apis/plugintypes/format/migration.md b/versioned_docs/version-4.1/apis/plugintypes/format/migration.md new file mode 100644 index 0000000000..2469a16131 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/format/migration.md @@ -0,0 +1,284 @@ +--- +title: Migrating 3.11 formats +tags: + - Plugins + - Format +--- + + + + + + +import { getExample } from '@site/src/moodleBridge'; + +The new course editor introduced n Moodle 4.0 reimplements most of the previous webservices, AMD modules, and internal logic of the course rendering. However, all formats since 3.11 will use the previous libraries by default until its final deprecation in Moodle 4.3. This document collects the main adaptations any 3.11 course format will require to continue working when this happens. + +## Changes summary + +The main areas affected by the new 4.0 course editor are: + +- Course format plugins are now part of the `core_courseformat` subsystem +- The old format_base class is now `core_courseformat\base` and it is mandatory for all format plugins to extend. +- Format plugin renderer is now mandatory (and in most cases they will extend `core_courseformat\output\section_renderer`) +- The old format_section_renderer_base is now `core_courseformat\output\section_renderer` +- The `core_courseformat\output\section_renderer` class now it extends core_course_renderer directly. This means that `$this->courserenderer` attribute is deprecated. +- All renderer methods using html_writer are deprecated. All UI elements are now rendered using output classes and mustache files. Formats can provide alternative output classes and templates. +- The new editor uses data attributes instead of CSS classes to locate section and activities elements in the page HTML. All elements must add those attributes to use the new AMD modules. +- Most of the logic of the previous core_course/actions AMD module is now replaced with core_courseformat AMD modules, each one corresponding to the specific UI elements. +- Formats can override the `core_courseformat\base::uses_course_index` in their base class to enable the course index. +- Formats can override the `core_courseformat\base::supports_components` in their base class to use the new course editor library instead of the legacy one. +- The course frontend uses a reactive state to maintain the UI elements updated. Format plugins can create new reactive AMD modules to interact with that state or extend the core_courseformat state classes to extend the data stored in that state. +- Course formats now can implement the `core_courseformat\base::delete_format_data` hook to clean data when the course is deleted. +- The course `format_base` class now provides a method `show_editor` to know if the user is editing or not the course depending on the page editing and the user capabilities. This method should be used instead of the previous `$PAGE->user_is_editing() && has_capability('moodle/course:manageactivities', $coursecontext)`. If you need to check for different capabilities, you can pass an array of them. If not specified, defaults to only `moodle:course/manageactivities`. + +## Moodle 3.11 vs 4.0 course editor architecture + +The 4.0 course editor follows a completely new pattern that is not compatible with the previous one. That new pattern is called **reactive components**. + +To ensure all 3.11 formats are still usable in 4.0 the new architecture is opt-in, meaning that in order to use the new libraries old formats must indicate to the system that they are compatible (overriding the `core_courseformat\base::supports_components`). + +The following table compares some of the main changes: + +| Moodle 3.11 | Moodle 4.0 | +|---- | ---- | +| AMD module "core_course/actions" is responsible for capturing the course edition actions, sending them to the correct course webservice, and replacing the returned HTML into the proper page elements. | Al the functionalities from the original actions are now divided into several AMD modules:
  • core_courseformat/local/courseeditor: coordinates all the UI elements via a data structure called "reactive state data" (or simply, "state data").
  • core_courseformat/local/courseeditor/mutations: centralizes all the backend calls and applies the results to the reactive state data before the UI components update the interface.
  • core_courseformat/local/content/actions: captures the user clicks on specific action links and displays modals to get more information from the user if necessary (for example the destination position in a move activity action).
  • core_courseformat/content/\*: ADM modules responsible for keeping some part of the UI aligned with the reactive state data.
  • core_courseformat/courseeditor/\*: other modules and helpers

NOTE: some user actions like hiding and showing sections/activities are not yet migrated to the new architecture.

NOTE: updating a full section or activity HTML is still handled by the core_course/actions module. However, now both methods are public to the module and they are used by the new course editor.

| +| course/dndupload.js file is responsible for handling any file dropping on the course page. | Moodle 4.0 still uses the course/dndupload.js to handle files dropped into the course area. | +| The webservice core_course_edit_section is responsible for updating a course section and returning the section header and the activities list HTML. |

Except for showing and hiding a section (not migrated yet) the rest of the section actions are now handled by the new `core_courseformat_update_course` webservice.

The new webservice has the same parameters for both section and activity actions and always returns a standardized data structure to interact with the frontend reactive state data. This means that it does not return HTML fragments anymore.

| +| The webservice core_course_edit_module is responsible for updating activity and returning the activity HTML or other page fragments. |

Except for showing and hiding an activity (not migrated yet) the rest of the activity actions are now handled by the new `core_courseformat_update_course` webservice.

The new webservice has the same parameters for both section and activity actions and always returns a standardized data structure to interact with the frontend reactive state data. This means that it does not return HTML fragments anymore.

| +| All frontend JS logic is responsible for interacting with the backend and updating all the necessary frontend elements depending on the return value. |

The JS logic is now distributed into several modules called "components". Each module can only interact with a specific page element and watch a data structure called reactive state date (or simply "state data").

The main points of the new component structure are:

  1. The initial state data is loaded using the `core_courseformat_get_state` webservice
  2. All components are registered into the core_courseformat/courseeditor page instance.
  3. When the state data changes, the course editor instance triggers the component's watchers methods to update the UI.
  4. Components are not able to alter the state data. If a component captures an action that requires some state change, it asks the course editor to perform a state mutation.
| +| Formats can alter the page HTML by overriding format_section_renderer_base methods |

Most renderer methods are now deprecated and have been migrated to output classes and mustache templates. There are only a few rendered methods formats that can override.

Now format plugins can override output classes by simply creating the equivalent format_pluginname/output/courseformat/* class. For example, core_courseformat\output\local\content can be overridden by creating a format_pluginname\output\courseformat\content class.

Important note: the new mustache structure uses partials and blocks to include sub-templates. This opens the door to a future frontend course rendering but it also makes the template overriding a bit more complex. See the "overriding templates" section for more information.

| +|

Format renderer is optional. If none is provided the course format renderer_base is used.

Inside a format rendered the `$this->courserenderer` attribute is used to access the course renderer methods.

| Format renderer is mandatory. The base core_courseformat\output\section_renderer now extends the core_course_renderer class and `$this->courserenderer` is deprecated. | +| The page elements are located using css class names such as "li.activity", ".actions", or "li.section". | All page elements are now located using data attributes. See [Course elements data attributes](./index.md#course-elements-data-attributes) for more information. | +| The course is rendered using print_single_section_page or print_multiple_section_page depending on the number of sections to render. |

The course format base instance contains all the necessary data to determine the way a course is rendered. To Specify a single section page formats should use `$format->set_section_number` method before rendering the course.

Once the format instance setup is finished, the course is rendered using the `content` output class. See migrating old renderer methods to outputs section for more information.

| +| To know if the user is editing the course, the format should check for `$PAGE->user_is_editing()` to know if edit is enabled and also `has_capability('moodle/course:update', $coursecontext)` to know if the user has the required capabilities. | The format base class have a method `$format->show_editor()` that do all the user and page validations to know if the current course display has to include editor options or not. | + +The following diagram represents the data flow of the new architecture: + +![Course editor workflow](./_files/course_editor_workflow.png) + +## First steps to migrate a 3.11 course format to 4.0 + +From 4.0 the course/format folder has its own subsystem called core_courseformat. This subsystem contains all the course rendering logic (only some minor elements are still in the original location for retro compatibility until Moodle 4.3). + +To ensure your format plugin is integrated with the new subsystem: + +- The main format_pluginname class should extend `core_courseformat\base` instead of the old format_base one. +- Your plugin **must provide a renderer class**. In most cases this class **will extend `core_courseformat\output\section_renderer`**. +- Any output class your plugin needs to override should be located in format_pluginname/classes/output/courseformat + +In summary, the first two things you need to adapt to your format are: + +### Point 1: create a renderer class + +Now renderer classes are mandatory for course format plugins. Here there are two scenarios: + +Scenario 1: if you plugin already has one you should replace the old "extends format_section_renderer_base" by "extends core_courseformat\output\section_renderer". + +Scenario 2: If your plugin does not have a renderer, create a file **course/format/pluginname/classes/output/renderer.php** with a content like: + +
+ View example +
+ +import Renderer from '!!raw-loader!./_examples/output/renderer.php'; +const RendererProps = { + examplePurpose: 'Output renderer', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/output/renderer.php', +}; + +
{getExample(RendererProps, Renderer)}
+ +
+
+ +### Point 2: fix your base class + +All Moodle 3.11 formats have a base format class extending format_base class from core_course. In Moodle 4.0 this class has been moved to `core_courseformat\base`. This means that you should replace the existing extends to avoid the deprecation message. + +For compatibility reasons, the base class does not use a namespace and should be located in your plugin "lib.php" file. This could change after Moodle 4.3 when most old course rendering methods will be removed from core. + +## The new output architecture + +When the debug messages are enabled, all 3.11 format plugins will show several deprecation messages. Most of them are due to the fact that almost all previous renderer methods are now deprecated. The full list can be found in the **course/upgrade.txt** file. + +Until 3.11 all course page elements are rendered using html_writer inside renderer methods. This made the UI hard to maintain because most of the methods are referring to the standard course structure (section_right_content, section_left_content, start_section_list, end_section_list…) instead of generic concepts like sections, activities, or activity menu. + +With the new architecture, almost all UI elements are rendered using: + +- An **output class** that generates the data to render. For example, course/format/classes/output/local/content/section.php +- A **mustache template** to render output class data. For example, course/format/templates/local/content/section.mustache +- An optional **reactive component** to update the frontend when the reactive state data changes: For example, course/format/amd/src/local/content/section.js + +The following diagram represents the new output structure compared to the 3.11 one: + +![Output classes structure](./_files/course_format_output.png) + +## Migrating old renderer methods to outputs + +The process of migrating a renderer method to output can be complex depending on the element your format overrides. For these reasons, Moodle 3.11 formats will remain using the old renderer methods until they explicitly use the new outputs. + +### Step 1: start using output components and renderers + +As in Moodle 3.11 formats, all the course view is initialized and rendered in the "course/format/pluginname/format.php" file. However, the way the outputs are initialized is quite different from the previous version. + +In Moodle 3.11 the format.php process was something like: + +1. Do some param validations +2. Get the course format instance using `course_get_format` or `core_courseformat\base::instance` +3. Get the format/course renderer using: `$PAGE->get_renderer('format_pluginname')`; +4. Use the renderer `print_single_section_page` or `print_multiple_section_page` depending on the section param. + +The problems with that approach were: + +- There are two main renderer methods which are almost the same +- The auxiliary renderer methods require a big amount of params even if they are not needed for the method because they need to provide those parameters to all the child methods. + +To avoid this situation now the format base class instance is used as a single exchange param to all output classes. Once the format instance is initialized every output class can get all necessary information from it. + +The new workflow in 4.0 is: + +
+ View example +
+ +import Format from '!!raw-loader!./_examples/format.php'; +const FormatProps = { + examplePurpose: 'Format course display', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/format.php', +}; + +
{getExample(FormatProps, Format)}
+ +
+
+ +Some important notes about the code: + +- The rendered class now can be obtained using `$format->get_renderer` +- Format plugins can override any core_courseformat output class (see sections below for more details). To get the correct output you need to use `$format->get_output_classname` method. +- As you may notice, the output class is rendered directly using the render method, not the `render_from_template` one. This is possible because all output classes implement the new Moodle 4.0 `named_templatable` interface. + +### Step 2: override any output class to alter the template data to your plugin needs + +Instead of having several renderer methods on a single file, the core_courseformat subsystem splits the output logic through several small classes, each one for a specific UI component. Format plugins can easily override specific classes to alter the template data. + +The course format base class has a special method called get_output_classname that returns the overridden class name if available in the format plugin, or the core one if not. In order to detect the format classes, your plugin must place the overridden one in your format_pluginname\output\courseformat\... + +For example, if a format plugin wants to add new options to the section action menu it should override the core_courseformat\output\local\content\section\controlmenu. To do so the plugin class should be format_topics\output\courseformat\content\section\controlmenu. You can find an example of an overridden output in the "course/format/topics/classes/output/courseformat/content/section/controlmenu.php" file. + +### Step 3: create the basic mustache structure + +Unlike output classes, mustache files cannot be extended nor overridden. To be able to alter specific mustaches your plugin must provide a minimum template structure. Furthermore, your plugin must provide some overridden output classes providing the alternative mustache templates location. + +In moodle 3.11 the course render uses html_writer to generate the course view, where each renderer method has both data collection and HTML generating inside. This is not the case in Moodle 4.0. From now on, the full output data is collected via output classes before rendering all the nested mustache templates. + +The course format subsystem requires a minimum of 3 mustaches to render a course: + +- **local/content**: the full course template +- **local/content/section**: a section template. Used to refresh a section via ajax +- **local/content/section/cmitem**: an activity item template: Used to refresh an activity via ajax + +The first thing your plugin needs is to create that structure and link it to the output components. Follow the guide on the [create format plugin page](./index.md#creating-the-basic-output-structure) to know how to create the basic structure. + +### Step 4: create your own custom mustache blocks + +Once your plugin has the basic mustache structure, you can provide extra mustache blocks to override parts of the page. Follow the [Override mustache blocks](./index.md#override-mustache-blocks) on the Creating a course format page to know how to do it. + +## Enabling course index in your format + +If your course format plugin uses a sections-activity structure it is possible to enable the course index. Add the course index in your format is as easy as overriding a method on your format base class: + +
+ View example +
+ +import BaseCourseIndex from '!!raw-loader!./_examples/lib_course_index.php'; +const BaseCourseIndexProps = { + examplePurpose: 'Format base class with course index enabled', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/lib.php', +}; + +
{getExample(BaseCourseIndexProps, BaseCourseIndex)}
+ +
+
+ +It is important to note that the course index drawer is only available in Boost based themes, Classic based themes won't display it. + +See the course index section in the create format plugin page for more information. + +## Enabling reactive components + +Moodle 4.0 introduced a new reactive course editor for the frontend. However, the new modules are not compatible with the previous YUI ones. To prevent errors in the 3.11 formats the new libraries are opt-in, meaning plugins must adapt their code before using it. + +Step 1: add data attributes to the HTML elements +The previous YUI editor uses CSS classes to identify sections, activities, and section headers. Nowadays, the use of CSS classes beyond styling is discouraged so the new library uses data attributes to identify the main course page elements. + +To adapt your plugin to the new editor you must add the proper data attributes. The following table explains the new selectors: + +| Concept | 3.11 equivalent | 4.0 data attributes | +| ---- | ---- | ---- | +| Section | `li.section#section-{SECTION.NUM}` | `data-for="section"`
`data-id={SECTION.ID}`
`data-number={SECTION.NUM}` | +| Section header | `#sectionid-{SECTION.ID}-title` | `data-for="section_title"`
`data-id={SECTION.ID}`
`data-number={SECTION.NUM}` | +| Course module item (activity) | `li.activity#module-{CM.ID}` | `data-for="cmitem"`
`data-id={CM.ID}` | +| Course sections list | `.course-content>ul` | `data-for="course_sectionlist"`
| `Section course modules list | Li.section .content .section` | `data-for="cmlist"`
| +| Course module action link | `a.cm-edit-action` | `data-action={ACTIONNAME}`
`data-id={CM.ID}` | +| Section action link | `.section_action_menu` | `data-action={ACTIONNAME}`
`data-id={SECTION.ID}` +| Section info | `.section_availability` | `data-for="sectioninfo"` | + +### Step 2: enable supports components feature + +Once your plugin has all the necessary data attributes you can disable the old YUI editor and enable the new reactive one by adding this method to your plugin base class: + +
+ View example +
+ +import BaseComponents from '!!raw-loader!./_examples/lib_components.php'; +const BaseComponentsProps = { + examplePurpose: 'Format base class with components enabled', + plugintype: 'format', + pluginname: 'pluginname', + filepath: '/lib.php', +}; + +
{getExample(BaseComponentsProps, BaseComponents)}
+ +
+
+ +### Step 3: check the reactive components are enabled. + +In principle, if you create the basic mustache structure as described in the previous chapter the course editor should work as expected. However, to check if they are working properly: + +1. Enable developer debug level on your site and access a course as an administrator +2. Wait for the page to load and find in the debug footer the "Reactive instances" section. +3. Click on the button "CourseEditorXXXX" (where XXXX is the current course ID) +4. Once the panel opens, click on the "Highlight OFF" button (it will change to "Highlight ON") +5. Go to the top of the page and check the course content has several thick blue borders. + +If the border appears around all the course content elements (sections, section headers and activities) means that the course editor has registered all the course content as a reactive component. + +If you don't have a "CourseEditorXXXX" button or the content elements don't get highlighted means that, most probably, you override the main content mustache in a peculiar way and removed the JS initialization. + +To re-introduce the JS initialization you should edit the plugin's "content.mustache" file with the following: + +- Add `id="{{uniqid}}-course-format"` to the course content div element. +- At the end of the template add: + +```handlebars +{{#js}} +require(['core_courseformat/local/content'], function(component) { + component.init('{{uniqid}}-course-format', {}, {{sectionreturn}}); +}); +{{/js}} +``` + +By doing that the content main reactive component should be initialized and if you enable the highlighting the content should have a blue border. If some if the inner elements does not have a blue border when highlight is ON means that some data attribute is missing. Check the step 1 of this chapter for more information. diff --git a/versioned_docs/version-4.1/apis/plugintypes/index.md b/versioned_docs/version-4.1/apis/plugintypes/index.md new file mode 100644 index 0000000000..16b5b828b8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/index.md @@ -0,0 +1,142 @@ +--- +title: Plugin types +tags: + - Plugins + - core + - API +--- + +Moodle is a powerful, and very extensible, Learning Management System. One of its core tenets is its extensibility, and this is primarily achieved through the development of plugins. + +A wider range of plugin types are available and these should be selected depending on your needs. + +## Things you can find in all plugins + +Although there are many different types of plugin, there are some things that work the same way in all plugin types. Please see the [Plugin files](./commonfiles) documentation that describes common files which are found in many plugin types. + +## Naming conventions + +Plugins typically have at least two names: + +- The friendly name, shown to users, and +- A machine name used internally. + +The machine name must meet the following rules: + +- It must start with a lowercase latin letter +- It may contain only lowercase latin letters, numbers, and underscores +- It must end with a lowercase latin letter, or a number +- The hyphen, and minus character `-` are not allowed + +If a plugin does not meet these requirements then it will be silently ignored. + +:::tip + +Plugin name validation takes place in `core_component::is_valid_plugin_name()` and the following regular expression is used: + +``` +/^[a-z](?:[a-z0-9_](?!__))*[](a-z0-9)+$/ +``` + +::: + +:::danger Activity module exception + +The underscore character is not supported in activity modules for legacy reasons. + +::: + + + +| Plugin type | Component name ([Frankenstyle](/general/development/policies/codingstyle/frankenstyle)) | Moodle path | Description | Moodle versions | +| --- | --- | --- | --- | --- | +| [Activity modules](./mod/index.mdx) | mod | /mod | Activity modules are essential types of plugins in Moodle as they provide activities in courses. For example: Forum, Quiz and Assignment. | 1.0+ | +| [Antivirus plugins](./antivirus/index.mdx) | antivirus | /lib/antivirus | Antivirus scanner plugins provide functionality for virus scanning user uploaded files using third-party virus scanning tools in Moodle. For example: ClamAV. | 3.1+ | +| [Assignment submission plugins](./assign/submission.md) | assignsubmission | /mod/assign/submission | Different forms of assignment submissions | 2.3+ | +| [Assignment feedback plugins](./assign/feedback.md) | assignfeedback | /mod/assign/feedback | Different forms of assignment feedbacks | 2.3+ | +| [Book tools](./mod_book/index.md) | booktool | /mod/book/tool | Small information-displays or tools that can be moved around pages | 2.1+ | +| [Course Custom fields](./customfield/index.md) | customfield | /customfield/field | Custom field types, used in Custom course fields | 3.7+ | +| [Database fields](./mod_data/fields.md) | datafield | /mod/data/field | Different types of data that may be added to the Database activity module | 1.6+ | +| [Database presets](./mod_data/presets.md) | datapreset | /mod/data/preset | Pre-defined templates for the Database activity module | 1.6+ | +| [LTI sources](https://docs.moodle.org/dev/External_tool_source) | ltisource | /mod/lti/source | LTI providers can be added to external tools easily through the external tools interface see [Documentation on External Tools](https://docs.moodle.org/en/External_tool). This type of plugin is specific to LTI providers that need a plugin that can register custom handlers to process LTI messages | 2.7+ | +| [File Converters](./fileconverter/index.md) | fileconverter | /files/converter | Allow conversion between different types of user-submitted file. For example from .doc to PDF. | 3.2+ | +| [LTI services](https://docs.moodle.org/dev/LTI_services) | ltiservice | /mod/lti/service | Allows the implementation of LTI services as described by the IMS LTI specification | 2.8+ | +| [Machine learning backends](./mlbackend/index.md) | mlbackend | /lib/mlbackend | Prediction processors for analytics API | 3.4+ | +| [Forum reports](./mod_forum/index.md) | forumreport | /mod/forum/report | Display various reports in the forum activity | 3.8+ | +| [Quiz reports](https://docs.moodle.org/dev/Quiz_reports) | quiz | /mod/quiz/report | Display and analyse the results of quizzes, or just plug miscellaneous behaviour into the quiz module | 1.1+ | +| [Quiz access rules](https://docs.moodle.org/dev/Quiz_access_rules) | quizaccess | /mod/quiz/accessrule | Add conditions to when or where quizzes can be attempted, for example only from some IP addresses, or student must enter a password first | 2.2+ | +| [SCORM reports](https://docs.moodle.org/dev/SCORM_reports) | scormreport | /mod/scorm/report | Analysis of SCORM attempts | 2.2+ | +| [Workshop grading strategies](https://docs.moodle.org/dev/Workshop_grading_strategies) | workshopform | /mod/workshop/form | Define the type of the grading form and implement the calculation of the grade for submission in the [Workshop](https://docs.moodle.org/dev/Workshop) module | 2.0+ | +| [Workshop allocation methods](https://docs.moodle.org/dev/Workshop_allocation_methods) | workshopallocation | /mod/workshop/allocation | Define ways how submissions are assigned for assessment in the [Workshop](https://docs.moodle.org/dev/Workshop) module | 2.0+ | +| [Workshop evaluation methods](https://docs.moodle.org/dev/Workshop_evaluation_methods) | workshopeval | /mod/workshop/eval | Implement the calculation of the grade for assessment (grading grade) in the [Workshop](https://docs.moodle.org/dev/Workshop) module | 2.0+ | +| [Blocks](./blocks/index.md) | block | /blocks | Small information-displays or tools that can be moved around pages | 2.0+ | +| [Question types](https://docs.moodle.org/dev/Question_types) | qtype | /question/type | Different types of question (for example multiple-choice, drag-and-drop) that can be used in quizzes and other activities | 1.6+ | +| [Question behaviours](https://docs.moodle.org/dev/Question_behaviours) | qbehaviour | /question/behaviour | Control how student interact with questions during an attempt | 2.1+ | +| [Question import/export formats](https://docs.moodle.org/dev/Question_formats) | qformat | /question/format | Import and export question definitions to/from the question bank | 1.6+ | +| [Text filters](./filter/index.md) | filter | /filter | Automatically convert, highlight, and transmogrify text posted into Moodle. | 1.4+ | +| [Editors](https://docs.moodle.org/dev/Editors) | editor | /lib/editor | Alternative text editors for editing content | 2.0+ | +| [Atto editor plugins](https://docs.moodle.org/dev/Atto) | atto | /lib/editor/atto/plugins | Extra functionality for the Atto text editor | 2.7+ | +| [TinyMCE editor plugins](./tiny/legacy.md) | tinymce | /lib/editor/tinymce/plugins | Extra functionality for the TinyMCE text editor. | 2.4+ | +| [Enrolment plugins](./enrol/index.md) | enrol | /enrol | Ways to control who is enrolled in courses | 2.0+ | +| [Authentication plugins](https://docs.moodle.org/dev/Authentication_plugins) | auth | /auth | Allows connection to external sources of authentication | 2.0+ | +| [Admin tools](https://docs.moodle.org/dev/Admin_tools) | tool | /admin/tool | Provides utility scripts useful for various site administration and maintenance tasks | 2.2+ | +| [Log stores](./logstore/index.md) | logstore | /admin/tool/log/store | Event logs storage back-ends | 2.7+ | +| [Availability conditions](./availability/index.md) | availability | /availability/condition | Conditions to restrict user access to activities and sections. | 2.7+ | +| [Calendar types](https://docs.moodle.org/dev/Calendar_types) | calendartype | /calendar/type | Defines how dates are displayed throughout Moodle | 2.6+ | +| [Messaging consumers](https://docs.moodle.org/dev/Messaging_consumers) | message | /message/output | Represent various targets where messages and notifications can be sent to (email, sms, jabber, ...) | 2.0+ | +| [Course formats](./format/index.md) | format | /course/format | Different ways of laying out the activities and blocks in a course | 1.3+ | +| [Data formats](https://docs.moodle.org/dev/Data_formats) | dataformat | /dataformat | Formats for data exporting and downloading | 3.1+ | +| [User profile fields](https://docs.moodle.org/dev/User_profile_fields) | profilefield | /user/profile/field | Add new types of data to user profiles | 1.9+ | +| [Reports](https://docs.moodle.org/dev/Reports) | report | /report | Provides useful views of data in a Moodle site for admins and teachers | 2.2+ | +| [Course reports](https://docs.moodle.org/dev/Course_reports) | coursereport | /course/report | Reports of activity within the course | Up to 2.1 (for 2.2+ see [Reports](https://docs.moodle.org/dev/Reports)) | +| [Gradebook export](https://docs.moodle.org/dev/Gradebook_export) | gradeexport | /grade/export | Export grades in various formats | 1.9+ | +| [Gradebook import](https://docs.moodle.org/dev/Gradebook_import) | gradeimport | /grade/import | Import grades in various formats | 1.9+ | +| [Gradebook reports](https://docs.moodle.org/dev/Gradebook_reports) | gradereport | /grade/report | Display/edit grades in various layouts and reports | 1.9+ | +| [Advanced grading methods](https://docs.moodle.org/dev/Grading_methods) | gradingform | /grade/grading/form | Interfaces for actually performing grading in activity modules (for example Rubrics) | 2.2+ | +| [MNet services](https://docs.moodle.org/dev/MNet_services) | mnetservice | /mnet/service | Allows to implement remote services for the [MNet](https://docs.moodle.org/dev/MNet) environment (deprecated, use web services instead) | 2.0+ | +| [Webservice protocols](https://docs.moodle.org/dev/Webservice_protocols) | webservice | /webservice | Define new protocols for web service communication (such as SOAP, XML-RPC, JSON, REST ...) | 2.0+ | +| [Repository plugins](./repository/index.md) | repository | /repository | Connect to external sources of files to use in Moodle | 2.0+ | +| [Portfolio plugins](https://docs.moodle.org/dev/Portfolio_plugins) | portfolio | /portfolio | Connect external portfolio services as destinations for users to store Moodle content | 1.9+ | +| [Search engines](https://docs.moodle.org/dev/Search_engines) | search | /search/engine | Search engine backends to index Moodle's contents. | 3.1+ | +| [Media players](https://docs.moodle.org/dev/Media_players) | media | /media/player | Pluggable media players | 3.2+ | +| [Plagiarism plugins](https://docs.moodle.org/dev/Plagiarism_plugins) | plagiarism | /plagiarism | Define external services to process submitted files and content | 2.0+ | +| [Cache store](https://docs.moodle.org/dev/Cache_store) | cachestore | /cache/stores | Cache storage back-ends. | 2.4+ | +| [Cache locks](https://docs.moodle.org/dev/Cache_locks) | cachelock | /cache/locks | Cache lock implementations. | 2.4+ | +| [Themes](https://docs.moodle.org/dev/Themes) | theme | /theme | Change the look of Moodle by changing the the HTML and the CSS. | 2.0+ | +| [Local plugins](./local/index.mdx) | local | /local | Generic plugins for local customisations | 2.0+ | +| [Content bank content types](https://docs.moodle.org/dev/Content_bank_content_types) | contenttype | /contentbank/contenttype | Content types to upload, create or edit in the content bank and use all over the Moodle site | 3.9+ | +| [H5P libraries](https://docs.moodle.org/dev/H5P_libraries) | h5plib | /h5p/h5plib | Plugin type for the particular versions of the H5P integration library. | 3.9+ | +| [Question bank plugins](./qbank/index.md) | qbank | /question/bank | Plugin type for extending question bank functionality. | 4.0+ | + +
+ Obtaining the list of plugin types known to your Moodle + +You can get an exact list of valid plugin types for your Moodle version using the following example: + +```php title="/plugintypes.php" +get_plugin_types() as $type => $dir) { + $dir = substr($dir, strlen($CFG->dirroot)); + printf( + "%-20s %-50s %s" . PHP_EOL, + $type, + $pluginman->plugintype_name_plural($type), + $dir) + ; +} +``` + +
+ +## See also + +- [Guidelines for contributing code](https://docs.moodle.org/dev/Guidelines_for_contributed_code) +- [Core APIs](../../apis.md) +- [Frankenstyle](/general/development/policies/codingstyle/frankenstyle) +- [Moodle Plugins directory](http://moodle.org/plugins) +- [Tutorial](https://docs.moodle.org/dev/Tutorial) to help you learn how to write plugins for Moodle from start to finish, while showing you how to navigate the most important developer documentation along the way. diff --git a/versioned_docs/version-4.1/apis/plugintypes/local/index.mdx b/versioned_docs/version-4.1/apis/plugintypes/local/index.mdx new file mode 100644 index 0000000000..403785d5f8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/local/index.mdx @@ -0,0 +1,152 @@ +--- +title: Local plugins +tags: + - Plugins +--- + +import { + getExample, + getFileNameWithComponentPath, + CodeBlock, +} from '@site/src/moodleBridge'; + +import { + Lib, + SettingsPHP, +} from '../../_files'; + +The recommended way to add new functionality to Moodle is to create a new standard plugin (for example, activity, block, authentication, enrol). The `local` plugin-type is mostly suitable for things that do not fit into these standard plugin types. + +Local plugins are used in cases when no standard plugin is suitable. Examples of these situations include: + +- event consumers communicating with external systems +- custom definitions of web services and external functions +- applications that extend moodle at the system level (for example hub server, amos server) +- custom admin settings +- extending the navigation block with custom menus +- new database tables used in core hacks (**strongly discouraged**) +- new capability definitions used in core hacks (**strongly discouraged**) + +## List of differences from normal plugins: + +Local plugins have several important differences from the standard plugin types, including: + +- they are always executed last during install, and upgrade. This is guaranteed by their order in `get_plugin_types()`. +- they are _expected_ to use event handlers. Event subscriptions are intended for communication from core to plugins only, making local plugins the ideal candidate for them. +- they can add admin settings to any settings page. They are loaded last when constructing admin tree to enable this. +- they _do not need_ to have any UI. Other plugin types are usually visible somewhere within the interface. + +## File structure + +Local plugins support the [standard plugin files](../commonfiles) supported by other plugin types. + +## Examples + +The following examples show some ways in which you can use a local plugin. + +### Adding an element to the settings menu + +A local plugin can extend or modify the settings navigation by defining a function named `local_[pluginname]_extend_settings_navigation` in its `lib.php`. This is called when Moodle builds the settings block. For example: + +export const settingsNavigationExample = ` +function local_[pluginname]_extend_settings_navigation($settingsnav, $context) { + global $CFG, $PAGE;\n +\ + // Only add this settings item on non-site course pages. + if (!$PAGE->course or $PAGE->course->id == 1) { + return; + }\n +\ + // Only let users with the appropriate capability see this settings item. + if (!has_capability('moodle/backup:backupcourse', context_course::instance($PAGE->course->id))) { + return; + }\n +\ + if ($settingnode = $settingsnav->find('courseadmin', navigation_node::TYPE_COURSE)) { + $strfoo = get_string('foo', 'local_[pluginname]'); + $url = new moodle_url('/local/[pluginname]/foo.php', array('id' => $PAGE->course->id)); + $foonode = navigation_node::create( + $strfoo, + $url, + navigation_node::NODETYPE_LEAF, + '[pluginname]', + '[pluginname]', + new pix_icon('t/addcontact', $strfoo) + ); + if ($PAGE->url->compare($url, URL_MATCH_BASE)) { + $foonode->make_active(); + } + $settingnode->add_node($foonode); + } +} +`; + + + +### Removing the "Site Home" link from the navigation menu + +A plugin can modify existing navigation, and settings navigation, components from within the `local_[pluginname]_extend_navigation()` function, for example: + +export const modifyNavigationExample = ` +function local_[pluginname]_extend_navigation(global_navigation $navigation) { + if ($home = $navigation->find('home', global_navigation::TYPE_SETTING)) { + $home->remove(); + } +} +`; + + + +### Adding Site Wide Settings For Your Local Plugin + +export const siteWideSettingsExample = ` +// Ensure the configurations for this site are set +if ($hassiteconfig) {\n +\ + // Create the new settings page + // - in a local plugin this is not defined as standard, so normal $settings->methods will throw an error as + // $settings will be null + $settings = new admin_settingpage('local_[pluginname]', 'Your Settings Page Title');\n +\ + // Create + $ADMIN->add('localplugins', $settings);\n +\ + // Add a setting field to the settings for this page + $settings->add(new admin_setting_configtext( + // This is the reference you will use to your configuration + 'local_[pluginname]/apikey',\n +\ + // This is the friendly title for the config, which will be displayed + 'External API: Key',\n +\ + // This is helper text for this config field + 'This is the key used to access the External API',\n +\ + // This is the default value + 'No Key Defined',\n +\ + // This is the type of Parameter this config is + PARAM_TEXT + )); +} +`; + + diff --git a/versioned_docs/version-4.1/apis/plugintypes/logstore/index.md b/versioned_docs/version-4.1/apis/plugintypes/logstore/index.md new file mode 100644 index 0000000000..3449ea77ac --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/logstore/index.md @@ -0,0 +1,14 @@ +--- +title: Logstore plugins +tags: + - Logging + - Events + - logstore + - Plugin + - Plugintype +documentationDraft: true +--- + +Moodle supports the ability to define a custom log storage system using the `logstore` plugin type. This hasn't been documented yet - perhaps you are able to help us. + + diff --git a/versioned_docs/version-4.1/apis/plugintypes/mlbackend/index.md b/versioned_docs/version-4.1/apis/plugintypes/mlbackend/index.md new file mode 100644 index 0000000000..27de3c670a --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mlbackend/index.md @@ -0,0 +1,212 @@ +--- +title: Machine learning backends +tags: + - Analytics + - API + - plugintype +--- + + + + +Machine learning backends process the datasets generated from the indicators and targets calculated by the [Analytics API](../../subsystems/analytics/index.md). They are used for machine learning training, prediction and models evaluation. + +:::tip + +We strongly recommend that you read the [Analytics API](../../subsystems/analytics/index.md) documentation to help understand the core concepts, how they are implemented in Moodle, and how machine learning backend plugins fit into the analytics API. + +::: + +The communication between machine learning backends and Moodle is through files because the code that will process the dataset can be written in PHP, in Python, in other languages or even use cloud services. This needs to be scalable so they are expected to be able to manage big files and train algorithms reading input files in batches if necessary. + +## Backends included in Moodle core + +### PHP + +The **PHP backend** is the default predictions processor as it is written in PHP and does not have any external dependencies. It is using logistic regression. + +### Python + +The **Python backend** requires *python* binary (either python 2 or python 3) and [moodlemlbackend python package](https://pypi.python.org/pypi?name=moodlemlbackend&version=0.0.5&:action=display) which is maintained by Moodle HQ. + +The Python version and libraries versions used are **very important**. We recommend using Python 3.7 for mlbackend 3.x versions. + +The Python backend is based on [Google's tensorflow library](https://www.tensorflow.org/) and uses a feed-forward neural network with 1 single hidden layer. + +The *moodlemlbackend* package does store model performance information that can be visualised using [tensorboard](https://www.tensorflow.org/get_started/summaries_and_tensorboard). Information generated during models evaluation is available through the models management page, under each model *Actions > Log* menu. + +:::tip + +We recommend use of the **Python** backend as it is able to predict more accurately than the PHP backend and it is faster. + +::: + +:::info + +You can [view the source](https://github.com/moodlehq/moodle-mlbackend-python) of the _moodlemlbackend_ library that Moodle uses. + +::: + +## File structure + +Machine learning backends are located in the `lib/mlbackend` directory. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +
+ View an example directory layout for the `mlbackend_python` plugin. + +```console +lib/mlbackend/python +├── classes +│   ├── privacy +│   │   └── provider.php +│   └── processor.php +├── lang +│   └── en +│   └── mlbackend_python.php +├── phpunit.xml +├── settings.php +├── tests +│   └── processor_test.php +├── upgrade.txt +└── version.php +``` + +
+ +Some of the important files for the mlbackend plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +## Interfaces + +A summary of these interfaces purpose: + +- Evaluate a provided prediction model +- Train machine learning algorithms with the existing site data +- Predict targets based on previously trained algorithms + +### Predictor + +This is the basic interface to be implemented by machine learning backends. Two main types are, *classifiers* and *regressors*. We provide the *Regressor* interface but it is not currently implemented by core Machine learning backends. Both of these are supervised algorithms. Each type includes methods to train, predict and evaluate datasets. + +You can use **is_ready** to check that the backend is available. + +```php +/** + * Is it ready to predict? + * + * @return bool + */ +public function is_ready(); +``` + +**clear_model** and **delete_output_dir** purpose is to clean up stuff created by the machine learning backend. + +```php +/** + * Delete all stored information of the current model id. + * + * This method is called when there are important changes to a model, + * all previous training algorithms using that version of the model + * should be deleted. + * + * @param string $uniqueid The site model unique id string + * @param string $modelversionoutputdir The output dir of this model version + * @return null + */ +public function clear_model($uniqueid, $modelversionoutputdir); + +/** + * Delete the output directory. + * + * This method is called when a model is completely deleted. + * + * @param string $modeloutputdir The model directory id (parent of all model versions subdirectories). + * @param string $uniqueid The site model unique id string + * @return null + */ +public function delete_output_dir($modeloutputdir, $uniqueid); +``` + +### Classifier + +A [classifier](https://en.wikipedia.org/wiki/Statistical_classification) sorts input into two or more categories, based on analysis of the indicators. This is frequently used in binary predictions, e.g. course completion vs. dropout. This machine learning algorithm is "supervised": It requires a training data set of elements whose classification is known (e.g. courses in the past with a clear definition of whether the student has dropped out or not). This is an interface to be implemented by machine learning backends that support classification. It extends the *Predictor* interface. + +Both these methods and *Predictor* methods should be implemented. + +```php +/** + * Train this processor classification model using the provided supervised learning dataset. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ +public function train_classification($uniqueid, \stored_file $dataset, $outputdir); + +/** + * Classifies the provided dataset samples. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ +public function classify($uniqueid, \stored_file $dataset, $outputdir); + +/** + * Evaluates this processor classification model using the provided supervised learning dataset. + * + * @param string $uniqueid + * @param float $maxdeviation + * @param int $niterations + * @param \stored_file $dataset + * @param string $outputdir + * @param string $trainedmodeldir + * @return \stdClass + */ +public function evaluate_classification($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir); +``` + +### Regressor + +A [regressor](https://en.wikipedia.org/wiki/Regression_analysis) predicts the value of an outcome (or dependent) variable based on analysis of the indicators. This value is linear, such as a final grade in a course or the likelihood a student is to pass a course. This machine learning algorithm is "supervised": It requires a training data set of elements whose classification is known (e.g. courses in the past with a clear definition of whether the student has dropped out or not). This is an interface to be implemented by machine learning backends that support regression. It extends *Predictor* interface. + +Both these methods and *Predictor* methods should be implemented. + +```php +/** + * Train this processor regression model using the provided supervised learning dataset. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ +public function train_regression($uniqueid, \stored_file $dataset, $outputdir); + +/** + * Estimates linear values for the provided dataset samples. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param mixed $outputdir + * @return void + */ +public function estimate($uniqueid, \stored_file $dataset, $outputdir); + + +/** + * Evaluates this processor regression model using the provided supervised learning dataset. + * + * @param string $uniqueid + * @param float $maxdeviation + * @param int $niterations + * @param \stored_file $dataset + * @param string $outputdir + * @param string $trainedmodeldir + * @return \stdClass + */ +public function evaluate_regression($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir); +``` diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png b/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png new file mode 100644 index 0000000000..2d2b39bc1a Binary files /dev/null and b/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-info.png differ diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png b/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png new file mode 100644 index 0000000000..0b6147facc Binary files /dev/null and b/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-recommend.png differ diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png b/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png new file mode 100644 index 0000000000..b3aeb20f1d Binary files /dev/null and b/versioned_docs/version-4.1/apis/plugintypes/mod/_activitymodule/activity-chooser-starred.png differ diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/index-php.mdx b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/index-php.mdx new file mode 100644 index 0000000000..1ab22546c9 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/index-php.mdx @@ -0,0 +1,2 @@ + +The `index.php` should be used to list all instances of an activity that the current user has access to in the specified course. diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/index-php.tsx b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/index-php.tsx new file mode 100644 index 0000000000..caebf44fb2 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/index-php.tsx @@ -0,0 +1,52 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { Props } from '../../../../_utils'; +import DefaultDescription from './index-php.mdx'; + +const defaultExample = `require_once('../../config.php'); + +// The \`id\` parameter is the course id. +$id = required_param('id', PARAM_INT); + +// Fetch the requested course. +$course = $DB->get_record('course', ['id'=> $id], '*', MUST_EXIST); + +// Require that the user is logged into the course. +require_course_login($course); + +$modinfo = get_fast_modinfo($course); + +foreach ($modinfo->get_instances_of('[modinfo]') as $instanceid => $cm) { + // Display information about your activity. +} +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.mdx b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.mdx new file mode 100644 index 0000000000..c2bdec2fd8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.mdx @@ -0,0 +1,16 @@ + +This file is used when adding/editing a module to a course. It contains the elements that will be displayed on the form responsible for creating/installing an instance of your module. The class in the file should be called `mod_[modname]_mod_form`. + +:::warning + +The `mod_[modname]_mod_form` is a current exception to the class autoloading rules. + +This will be addressed in [MDL-74472](https://tracker.moodle.org/browse/MDL-74472). + +::: + +:::info + +The following example gives a sample implementation of the creation form. It does **not** contain the full file. + +::: diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.tsx b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.tsx new file mode 100644 index 0000000000..d47149d31a --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.tsx @@ -0,0 +1,64 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { Props } from '../../../../_utils'; +import DefaultDescription from './mod_form-php.mdx'; + +const defaultExample = `require_once($CFG->dirroot.'/course/moodleform_mod.php'); +require_once($CFG->dirroot.'/mod/certificate/lib.php'); + +class mod_certificate_mod_form extends moodleform_mod { + + function definition() { + global $CFG, $DB, $OUTPUT; + + $mform =& $this->_form; + + $mform->addElement('text', 'name', get_string('certificatename', 'certificate'), ['size'=>'64']); + $mform->setType('name', PARAM_TEXT); + $mform->addRule('name', null, 'required', null, 'client'); + + $ynoptions = [ + 0 => get_string('no'), + 1 => get_string('yes'), + ]; + $mform->addElement('select', 'usecode', get_string('usecode', 'certificate'), $ynoptions); + $mform->setDefault('usecode', 0); + $mform->addHelpButton('usecode', 'usecode', 'certificate'); + + $this->standard_coursemodule_elements(); + + $this->add_action_buttons(); + } + + } +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.txt b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.txt new file mode 100644 index 0000000000..b19839ef3f --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/mod_form-php.txt @@ -0,0 +1,31 @@ +if (!defined('MOODLE_INTERNAL')) { + die('Direct access to this script is forbidden.'); // It must be included from a Moodle page +} + +require_once($CFG->dirroot.'/course/moodleform_mod.php'); +require_once($CFG->dirroot.'/mod/certificate/lib.php'); + +class mod_certificate_mod_form extends moodleform_mod { + + function definition() { + global $CFG, $DB, $OUTPUT; + + $mform =& $this->_form; + + $mform->addElement('text', 'name', get_string('certificatename', 'certificate'), ['size'=>'64']); + $mform->setType('name', PARAM_TEXT); + $mform->addRule('name', null, 'required', null, 'client'); + + $ynoptions = [ + 0 => get_string('no'), + 1 => get_string('yes'), + ]; + $mform->addElement('select', 'usecode', get_string('usecode', 'certificate'), $ynoptions); + $mform->setDefault('usecode', 0); + $mform->addHelpButton('usecode', 'usecode', 'certificate'); + + $this->standard_coursemodule_elements(); + + $this->add_action_buttons(); + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/view-php.mdx b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/view-php.mdx new file mode 100644 index 0000000000..96567c5f4a --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/view-php.mdx @@ -0,0 +1,2 @@ + +Moodle will automatically generate links to view the activity using the `/view.php` page and passing in an `id` value. The `id` passed is the course module ID, which can be used to fetch all remaining data for the activity instance. diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/_files/view-php.tsx b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/view-php.tsx new file mode 100644 index 0000000000..28a608e8c3 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/_files/view-php.tsx @@ -0,0 +1,40 @@ +/** + * Copyright (c) Moodle Pty Ltd. + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ +import React from 'react'; +import { ComponentFileSummary } from '../../../../_utils'; +import type { Props } from '../../../../_utils'; +import DefaultDescription from './view-php.mdx'; + +const defaultExample = ` +require('../../config.php'); + +$id = required_param('id', PARAM_INT); +[$course, $cm] = get_course_and_cm_from_cmid($id, '[modname]'); +$instance = $DB->get_record('[modname]', ['id'=> $cm->instance], '*', MUST_EXIST); +`; + +export default (initialProps: Props): ComponentFileSummary => ( + +); diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/activitymodule.md b/versioned_docs/version-4.1/apis/plugintypes/mod/activitymodule.md new file mode 100644 index 0000000000..17a2a7510c --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/activitymodule.md @@ -0,0 +1,77 @@ +--- +title: Activity chooser +tags: +- MUA Project +documentationDraft: true +--- + +:::caution + +This documentation is from the project kick-off and has not been updated since the project completed. + +::: + +Through our road-map creation process of looking at highly voted tracker issues and relevant forum posts, as well as MUA interaction, an update to the activity chooser to simplify and make less intimidating, was chosen. + +[MDL-57828](https://tracker.moodle.org/browse/MDL-57828) was created and worked on, but unfortunately stalled, and did not complete its way through the integration process. There is a [substantial forum post](https://moodle.org/mod/forum/discuss.php?d=346664), with many suggestions and current pain points with the activity chooser. The MUA created a proposal issue ([MDL-61511](https://tracker.moodle.org/browse/MDL-61511)) to tackle the same issue. + +We have recently been analysing how course creation is achieved. Two main ideas were tested with focus groups to try and find the best away to approach course creation. With this information we are confident that we have a user focused design that will improve the activity chooser for everyone. + +We would like to invite everyone to express their opinion on this improvement. Course creation is a Moodle activity that is fundamental to teaching a course online, and we would like to ensure that the process is as easy and intuitive as possible. + +## Features + +The following are changes that we are planning on making in this project. We have a demo that can be viewed and interacted with. +[Invisio mockup of the activity chooser](https://projects.invisionapp.com/share/SVSREPYNBYG#/screens/388682478). + +Our current work can be viewed at [activity chooser prototype](https://activitychooser.prototype.moodledemo.net/). Please take a look. + +### Larger display area + +The activity chooser will be wider and have the activities in a grid format. This allows for more activities to be seen at once. + +### Activities and resources are now merged + +Our research found that the distinction been activities and resources was not useful to teachers and so now these two categories have been merged together. + +### Starred / Favourites tab + +The user can now select activities to be added to the Starred tab. The starred tab is shown by default to users when pulling up the activity chooser. + +![The starred tab](./_activitymodule/activity-chooser-starred.png) + +### Recommended tab + +Site administrators will now be able to set a selection of activities as recommended. These recommended activities will show up in a tab in the activity chooser for the course creator to view. If no recommendations are made then this tab will not be displayed. + +![The recommended tab](./_activitymodule/activity-chooser-recommend.png) + +### Smart search bar + +To help find activities from the activity chooser, we will be adding a search bar, that will search through both the names of the activities, and also the information text, to try and find relevant activities that the user may want. + +### Other activity types + +Other activity types such as LTI will be able to be added to the activity chooser for the user to select. + +### Activity information hidden + +The information about an activity will be accessible through the 'i' icon. Clicking the link will show additional information about the activity. This will free up space for other activities to be shown rather than always taking up half of the activity chooser. + +![Additional information about an activity](./_activitymodule/activity-chooser-info.png) + +## Third party plugin developers + +Course module plugins can add items to the activity chooser by implementing the `{plugin}_get_content_items()` callback in their plugin lib (lib.php). This callback replaces the now deprecated `{plugin}_get_shortcuts()` method. + +In order for activity starring and recommendations to work, each content_item has an ID which is subject to some additional rules. Each ID: + +- Must be unique to your component. +- Must not change. +- Must be of type integer. + +See `lti_get_course_content_items()` for an example implementation in core. + +Additionally, for recommendations to be made, plugins must implement the `{plugin}_get_all_content_items()` callback in their lib.php. This method must return a list of all content items that can be added across all courses. + +Developers who are currently using the deprecated `{plugin}_get_shortcuts()` callback should implement the new callback in their plugins as soon as possible. Whilst legacy items are still included (in cases where the new callback has yet to be implemented in the plugin), these items can not be starred, nor recommended. Eventually all support for the deprecated method will be removed, as per normal deprecation policy. diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod/index.mdx b/versioned_docs/version-4.1/apis/plugintypes/mod/index.mdx new file mode 100644 index 0000000000..6c5bc81718 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod/index.mdx @@ -0,0 +1,297 @@ +--- +title: Activity modules +toc_max_heading_level: 4 +tags: + - API + - Activity + - Module + - mod +--- + +import { + BackupDir, + DbAccessPHP, + DbEventsPHP, + DbInstallXML, + DbMobilePHP, + DbUpgradePHP, + Lang, + Lib, + VersionPHP, +} from '../../_files'; + +import ModFormPHP from './_files/mod_form-php'; +import IndexPHP from './_files/index-php'; +import ViewPHP from './_files/view-php'; + +Activity modules are a fundamental course feature and are usually the primary delivery method for learning content in Moodle. + +The plugintype of an Activity module is `mod`, and the frankenstyle name of a plugin is therefore `mod_[modname]`. + +All activity module plugins are located in the `/mod/` folder of Moodle. + +:::note + +The term `[modname]` is used as a placeholder in this documentation and should be replaced with the name of your activity module. + +::: + +## Folder layout + +Activity modules reside in the `/mod` directory. + +Each module is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +Below is an example of the file structure for the `folder` plugin. + +
+ View an example directory layout for the `folder` plugin. +
+ +```console +. +├── backup +│   ├── moodle1 +│   │   └── lib.php +│   └── moodle2 +│   ├── backup_folder_activity_task.class.php +│   ├── backup_folder_stepslib.php +│   ├── restore_folder_activity_task.class.php +│   └── restore_folder_stepslib.php +├── classes +│   ├── analytics +│   │   └── indicator +│   │   ├── activity_base.php +│   │   ├── cognitive_depth.php +│   │   └── social_breadth.php +│   ├── content +│   │   └── exporter.php +│   ├── event +│   │   ├── all_files_downloaded.php +│   │   ├── course_module_instance_list_viewed.php +│   │   ├── course_module_viewed.php +│   │   └── folder_updated.php +│   ├── external.php +│   ├── privacy +│   │   └── provider.php +│   └── search +│   └── activity.php +├── db +│   ├── access.php +│   ├── install.php +│   ├── install.xml +│   ├── log.php +│   ├── services.php +│   └── upgrade.php +├── download_folder.php +├── edit.php +├── edit_form.php +├── index.php +├── lang +│   └── en +│   └── folder.php +├── lib.php +├── locallib.php +├── mod_form.php +├── module.js +├── phpunit.xml +├── pix +│   ├── icon.png +│   └── icon.svg +├── readme.txt +├── renderer.php +├── settings.php +├── styles.css +├── tests +│   ├── backup +│   │   └── restore_date_test.php +│   ├── behat +│   │   └── folder_activity_completion.feature +│   ├── event +│   │   └── events_test.php +│   ├── externallib_test.php +│   ├── generator +│   │   └── lib.php +│   ├── generator_test.php +│   ├── lib_test.php +│   ├── phpunit.xml +│   └── search +│   └── search_test.php +├── version.php +└── view.php +``` + +
+
+ +## Standard Files and their Functions + +There are several files that are crucial to Moodle. These files are used to install your module and then integrate it into Moodle. Each file has a particular function, some of the files are optional, and are only created if you want to use the functionality it offers. Below are the list of most commonly used files. + +### Backup Folder + + + +### `access.php` - Capability defaults + +export const accessExample = ` +$capabilities = [ + 'mod/[modname]:addinstance' => [ + 'riskbitmask' => RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW, + ], + 'clonepermissionsfrom' => 'moodle/course:manageactivities', + ], + 'mod/[modname]:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'guest' => CAP_ALLOW, + 'student' => CAP_ALLOW, + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW, + ], + ], +]; +`; + + + +For activities the following capabilities are _required_: + +- `mod/[modname]:addinstance`: Controls whether a user may create a new instance of the activity +- `mod/[modname]:view`: Controls whether a user may view an instance of the activity + +The example below shows the recommended configuration for the `addinstance` and `view` capabilities. + +This configuration will allow: + +- editing teachers and managers to create new instances, but not non-editing teachers. +- all roles to view the activity. + +:::important + +Granting the view capability to archetypes like `guest` does not allow any user to view all activities. Users are still subject to standard access controls like course enrolment. + +::: + +For further information on what each attribute in that capabilities array means visit [NEWMODULE Adding capabilities](https://docs.moodle.org/dev/NEWMODULE_Adding_capabilities). + +
+ } +/> + +### `events.php` - Event observers + +< DbEventsPHP /> + +### `install.xml` - Database installation + + + +Moodle requires that you create a table for your plugin whose name exactly matches the plugin name. For example, the `certificate` activity module _must_ have a database table named `certificate`. Certain fields within this table are +also _required_: + +| Field name | Properties | Keys / Indexes | Notes | +| --- | --- | --- | --- | +| `id` | `INT(10), auto sequence` | primary key for the table | | +| `course` | `INT(10)` | foreign key to the `course` table | | +| `name` | `CHAR(255)` | | Holds the user-specified name of the activity instance | +| `timemodified` | `INT(10)` | | The timestamp of when the activity was last modified | +| `intro` | `TEXT` | | A standard field to hold the user-defined activity description (see `FEATURE_MOD_INTRO`) | +| `introformat` | `INT(4)` | | A standard field to hold the format of the field | + + + } +/> + +### `upgrade.php` - Upgrade steps + + + +### `mobile.php` - Moodle Mobile Remote Add-ons + + + +### `/lang/en/mod_[modname].php` - Language string definitions + + + +### `lib.php` - Library functions + + + +For an Activity, you _must_ define the following three functions, which are described below: + +```php title="mod/[modname]/lib.php" +function [modname]_add_instance($instancedata, $mform = null): int; +function [modname]_update_instance($instancedata, $mform): bool; +function [modname]_delete_instance($id): bool; +``` + +- The `[modname]_add_instance()` function is called when the activity creation form is submitted. This function is only called when adding an activity and should contain any logic required to add the activity. +- The `[modname]_update_instance()` function is called when the activity editing form is submitted. +- The `[modname]_delete_instance()` function is called when the activity deletion is confirmed. It is responsible for removing all data associated with the instance. + +:::note + +The `lib.php` file is one of the older parts of Moodle and functionality is gradually being migrated to class-based function calls. + +::: + + + } +/> + +### `mod_form.php` - Instance create/edit form + + + +### `index.php` - Instance list + + + +### `view.php` - View an activity + + + +### `version.php` + + + +## See also + +- [Tutorial](https://docs.moodle.org/dev/Tutorial) +- [Module visibility and display](https://docs.moodle.org/dev/Module_visibility_and_display) diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod_book/index.md b/versioned_docs/version-4.1/apis/plugintypes/mod_book/index.md new file mode 100644 index 0000000000..a28f112e96 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod_book/index.md @@ -0,0 +1,10 @@ +--- +title: Book activity sub-plugins +tags: + - Book + - Subplugin + - Plugintype +documentationDraft: true +--- + +The `mod_book` activity can be extended using the tool sub-plugin type. This hasn't been documented yet - perhaps you are able to help us. diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod_data/fields.md b/versioned_docs/version-4.1/apis/plugintypes/mod_data/fields.md new file mode 100644 index 0000000000..3d1bf3e009 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod_data/fields.md @@ -0,0 +1,81 @@ +--- +title: Database fields +tags: + - mod_data + - datafield + - plugintype + - subplugin +documentationDraft: true +--- + +The [Database activity](https://docs.moodle.org/en/Database_module) included with Moodle includes support for several predefined [field types](./fields.md), including text, date, and URL. It is also possible to create new field types. For example, you might like to create: + +- Discipline-specific field types - For example "Protein PDB code": users can enter the PDB code for a protein, and then the display 3D viewer for the protein structure, or link out to molecular databases. +- Institution-specific field types - For example "library reference number": Allow users to enter a reference number which can be automatically turned into a direct link for local library services. +- Module-specific field types - For example "wiki page": users see a drop-down list containing the names of pages in your wiki, and can choose which page this particular entry refers to. + +import { ComponentFileSummary } from '../../../_utils'; + +## File structure + +Database field sub-plugins are located in the `/mod/data/field` directory. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +
+ View an example directory layout for the `datafield_number` subplugin. + +```console +mod/data/field/number +├── classes +│   └── privacy +│   └── provider.php +├── field.class.php +├── lang +│   └── en +│   └── datafield_number.php +├── mod.html +└── version.php +``` + +
+ +Some of the important files for the database field plugintype are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin. + +### Field class + + + +The field, its behaviours, and its properties, are defined in a class named `data_field_[pluginname]` located in `field.class.php`. This class must extend the `data_field_base` base class. + +:::danger Class locations + +The field definition is currently located in the `field.class.php` file and is not yet autoloaded by Moodle. + +::: + +The base class defines some simple behaviours which you can override in your plugin. The following functions are of particular interest: + +- `display_add_field($recordid = 0)` - Return some HTML for use when a user is adding/editing a record +- `display_browse_field($recordid, $template)` - Return some HTML for displaying a record +- `update_content($recordid, $value, $name = '')` - Store the data entered by a user for a record +- `get_sort_sql($fieldname)` - Specify SQL for how this field should be sorted +- `get_content_value($value)` - Useful if the info stored in the database if different from the info that ends up being presented to the user + +### Field configuration form + + + +:::danger + +The field definition is one of the older parts of Moodle and does not use best practice. + +::: diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod_data/index.md b/versioned_docs/version-4.1/apis/plugintypes/mod_data/index.md new file mode 100644 index 0000000000..bca2e13820 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod_data/index.md @@ -0,0 +1,12 @@ +--- +title: Database activity sub-plugins +tags: + - Database + - Subplugin + - Plugintype +--- + +The `mod_data` activity can be extended using two sub-plugin types, namely: + +- [Database field types](./fields.md), used to create custom field data types; and +- [Database presets](./presets.md), a legacy plugintype used to share configurations. diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod_data/presets.md b/versioned_docs/version-4.1/apis/plugintypes/mod_data/presets.md new file mode 100644 index 0000000000..38034887db --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod_data/presets.md @@ -0,0 +1,15 @@ +--- +title: Database presets +tags: + - mod_data + - datapreset + - subplugin + - deprecated +--- + + +The database preset subplugin type is no longer recommended. + +## See also + +[en](https://docs.moodle.org/en/Database_presets) diff --git a/versioned_docs/version-4.1/apis/plugintypes/mod_forum/index.md b/versioned_docs/version-4.1/apis/plugintypes/mod_forum/index.md new file mode 100644 index 0000000000..6dd3127421 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/mod_forum/index.md @@ -0,0 +1,10 @@ +--- +title: Forum activity sub-plugins +tags: + - Forum + - Subplugin + - Plugintype +documentationDraft: true +--- + +The `mod_fporum` activity can be extended using the `forumreport` sub-plugin type. This hasn't been documented yet - perhaps you are able to help us. diff --git a/versioned_docs/version-4.1/apis/plugintypes/qbank/index.md b/versioned_docs/version-4.1/apis/plugintypes/qbank/index.md new file mode 100644 index 0000000000..c34a5ff5aa --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/qbank/index.md @@ -0,0 +1,25 @@ +--- +title: Question Bank plugins +tags: + - Plugins + - Question + - qbank + - Quiz +description: Question type plugins allow you to extend the functionality of the Moodle Question bank. +documentationDraft: true +--- + +import { Since } from '@site/src/components'; + + + +Question type plugins allow you to extend the functionality of the Moodle Question bank, and support features including: + +- Table columns +- Action menu items +- Bulk actions +- Navigation node (tabs) +- Question preview additions (via callback) diff --git a/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/access.php b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/access.php new file mode 100644 index 0000000000..e66eceab49 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/access.php @@ -0,0 +1,13 @@ + +$capabilities = [ + // Ability to use the plugin. + 'repository/pluginname:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'coursecreator' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ] + ], +]; diff --git a/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/flickr_public_lib.php b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/flickr_public_lib.php new file mode 100644 index 0000000000..0cabfe38fe --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/flickr_public_lib.php @@ -0,0 +1,100 @@ +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/repository/lib.php'); + +class repository_flickr_public extends repository { + + // User specific settings. + + /** + * Options names. + * + * Tell the API that the repositories have specific settings: "email address" + * + * @return string[] of options. + */ + public static function get_instance_option_names() { + return ['email_address']; + } + + /** + * Repository configuration form. + * + * Add an "email address" text box to the create/edit repository instance Moodle form + * + * @param moodleform $mform Moodle form + */ + public static function instance_config_form($mform) { + $mform->addElement( + 'text', + 'email_address', + get_string('emailaddress', 'repository_flickr_public') + ); + $mform->addRule( + 'email_address', + get_string('required'), + 'required', + null, + 'client' + ); + } + + // Global repository plugin settings. + + /** + * Repository global settings names. + * + * We tell the API that the repositories have general settings: "api_key" + * + * @return string[] of options. + */ + public static function get_type_option_names() { + return array('api_key'); + } + + /** + * Repository global settings form. + * + * We add an "api key" text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form) + * + * @param moodleform $mform Moodle form + */ + public function type_config_form($mform) { + //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form + $api_key = get_config('flickrpublic', 'api_key'); + + $mform->addElement( + 'text', + 'api_key', + get_string('apikey', 'repository_flickr_public'), + ['value' => $api_key, 'size' => '40'] + ); + $mform->addRule( + 'api_key', + get_string('required'), + 'required', + null, + 'client' + ); + } + + // Method called when the repostiroy plugin is installed. + + /** + * Plugin init method. + * + * this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site. + */ + public static function plugin_init() { + //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly. + repository::static_function( + 'flickrpublic', + 'create', + 'flickrpublic', + 0, + context_system::instance(), + ['name' => 'default instance', 'email_address' => null], + 1 + ); + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/full_list.jsonc b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/full_list.jsonc new file mode 100644 index 0000000000..d1fae44c77 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/full_list.jsonc @@ -0,0 +1,68 @@ +// Example of a list array with all the element types. +{ + // 'path' is used to build navigation bar to show the current folder, so you need to include all parents folders + // array(array('name'=>'root','path'=>'/'), array('name'=>'subfolder', 'path'=>'/subfolder')) + // This will result in: /root/subfolder as current directory + "path": (array), // this will be used to build navigation bar, + + // 'dynload' tells file picker to fetch list dynamically. + // When user clicks the folder, it will send a ajax request to server side. + // Default value is false but note that non-Javascript file picker always acts as if dynload was set to true + "dynload": (bool), // use dynamic loading, + + // If you are using pagination, 'page' and 'pages' parameters should be set. + // It is not recommended to use pagination and subfolders at the same time, the tree view mode can not handle it correctly + "page": (int), // which page is this list, + "pages": (int), // how many pages. If number of pages is unknown but we know that the next page exists repository may return -1, + "manage": (string), // url to file manager for the external repository, if specified will display link in file picker, + "help": (string), // url to the help window, if specified will display link in file picker, + "nologin": (bool), // requires login, default false, if set to true the login link will be removed from file picker, + "norefresh": (bool), // no refresh button, default false, + "logouttext": (string), // in case of nologin=false can substitute the text 'Logout' for logout link in file picker, + "nosearch": (bool), // no search link, default false, if set to true the search link will be removed from file picker, + "issearchresult": (bool), // tells that this listing is the result of search, + // for repositories that actually upload a file: set 'upload' option to display an upload form in file picker + "upload": { // upload manager + "label": (string), // label of the form element, + "id": (string) // id of the form element, + }, + // 'list' is used by file picker to build a file/folder tree + "list": { + { // file + "title": (string), // file name, + "shorttitle": (string), // optional, if you prefer to display a short title + "date": (int), // UNIX timestamp, default value for datemodified and datecreated, + "datemodified": (int), // UNIX timestamp when the file was last modified [ 'datecreated' => (int) UNIX timestamp when the file was last created [2.3+](2.3+], + "size": (int), // file size in bytes, + "thumbnail": (string), // url to thumbnail for the file, + "thumbnail_width": (int), // the width of the thumbnail image, + "thumbnail_height": (int), // the height of the thumbnail image, + "source": (string), // plugin-dependent unique path to the file (id, url, path, etc.), + "url": (moodle_url), // the accessible url of file, + "icon": (string), // url to icon of the image (24x24px), if omitted the moodle filetype icon will be used [ 'realthumbnail' => (string) url to image preview to be lazy-loaded when scrolled to it (if it requires to be generated and can not be returned as 'thumbnail') [2.3+](2.3+], + "realicon": (string), // url to image preview in icon size (24x24) [ 'author' => (string) default value for file author, + "license": (string), // default value for license (short name, see class license_manager), + "image_height": (int), // if the file is an image, image height in pixels, null otherwise [2.3+](2.3+], + "image_width": (int) // if the file is an image, image width in pixels, null otherwise [ ), + }, + { // folder - similar to file, has also 'path' and 'children' but no 'source' or 'url' + "title": (string), // folder name, + "shorttitle": (string), // optional, if you prefer to display a short title + "path": (string), // path to this folder. In case of dynload=true (and for non-JS filepicker) the value will be passed to repository_xxx::get_listing() in order to retrieve children + "date": (int), + "datemodified": (int), + "datecreated": (int), + "thumbnail": (string), + "icon": ,// see above, + "children": [ + // presence of this attribute actually tells file picker that this is a folder. In case of dynload=true, it should be empty array + // otherwise it is a nested list of contained files and folders + ] + } + }, + // The 'object' tag can be used to embed an external web page or application within the filepicker + "object": { + "type": (string), // e.g. 'text/html', 'application/x-shockwave-flash' + "src": (string), // the website address to embed in the object + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/lib.php b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/lib.php new file mode 100644 index 0000000000..30346d3054 --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/repository/_examples/lib.php @@ -0,0 +1,64 @@ +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/repository/lib.php'); + +class repository_pluginname extends repository { + /** + * Get file listing. + * + * This is a mandatory method for any repository. + * + * See repository::get_listing() for details. + * + * @param string $encodedpath + * @param string $page + * @return array the list of files, including meta infomation + */ + public function get_listing($encodedpath = '', $page = '') { + // This methods + return array('list' => []); + } + + /** + * Is this repository used to browse moodle files? + * + * @return boolean + */ + public function has_moodle_files() { + return true; + } + + /** + * Tells how the file can be picked from this repository. + * + * @return int + */ + public function supported_returntypes() { + return FILE_INTERNAL | FILE_REFERENCE; + } + + /** + * Which return type should be selected by default. + * + * @return int + */ + public function default_returntype() { + return FILE_INTERNAL; + } + + + /** + * Optional method for searching files in the repository. + * + * @param string $search + * @param int $page + * @return array the list of found files. + */ + public function search($search, $page = 0) { + $ret = []; + $ret['nologin'] = true; + // The found files list. + $ret['list'] = []; + return $ret; + } +} diff --git a/versioned_docs/version-4.1/apis/plugintypes/repository/_files/options.png b/versioned_docs/version-4.1/apis/plugintypes/repository/_files/options.png new file mode 100644 index 0000000000..f49b9288e1 Binary files /dev/null and b/versioned_docs/version-4.1/apis/plugintypes/repository/_files/options.png differ diff --git a/versioned_docs/version-4.1/apis/plugintypes/repository/index.md b/versioned_docs/version-4.1/apis/plugintypes/repository/index.md new file mode 100644 index 0000000000..a71418fb7e --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/repository/index.md @@ -0,0 +1,965 @@ +--- +title: Repository plugins +tags: + - Repositories + - Plugins +--- + +import { + DbAccessPHP, + Lang, + Lib, + VersionPHP, +} from '../../_files'; + +import { + CodeBlock, + Tabs, + TabItem +} from '@site/src/components'; + +Repository plugin allow Moodle to bring contents into Moodle from external repositories. + +### Prerequisites + +Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker. + +The 2 different parts to write in order to implement a full repository: + +1. Administration - You can customise the way administrators and users can configure their repositories. +2. File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display. + +## File structure + +Repository plugins are located in the `/repository` directory. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +
+ View an example directory layout for the `repository_pluginname` plugin. + +```console + repository/pluginname/ + |-- db + | `-- access.php + |-- lang + | `-- en + | `-- repository_pluginname.php + |-- lib.php + |-- pix + | `-- icon.png + `-- version.php +``` + +
+ +Some of the important files for the repository plugintype are described below. See the [common plugin files](../commonfiles) documentation for details of other files which may be useful in your plugin. + +### version.php + + + +### lang/en/repository_pluginname.php + + +export const langExample = ` +$string['pluginname']= 'Example repository'; +$string['configplugin'] = 'Configuration for Example repository'; +$string['pluginname_help'] = 'A repository description'; +`; + + + +### lib.php + +import RepositoryLibExample from '!!raw-loader!./_examples/lib.php'; + + + +This file contains the main repository class definition, which must extend the core `\repository` class. By extending the base class and overriding some of the class methods, the plugin can configure standard features and behaviours. See [Administration API](#administration-apis) for more information. + + + +### db/access.php + +import RepositoryAccessExample from '!!raw-loader!./_examples/access.php'; + + + +## Repository API methods + +Repository plugins can present, store, and link files in different ways depending on the type of remote system that the repository connects to. + +Some of the key API functions are described below. + +### supported_returntypes(): int + +Return any combination of the following values: + +- `FILE_INTERNAL` - the file is stored within the Moodle file system. +- `FILE_EXTERNAL` - the file is stored in the external repository. It is not stored within the Moodle file system. When accessing the file it is returned from the remote system. +- `FILE_REFERENCE` - the file stays in the external repository but may be cached locally. In that case it should be synchronised automatically, as required, with any changes to the external original. +- `FILE_CONTROLLED_LINK` - the file remains in the external repository, and ownership of the file in the remote system is changed to the [system account in the external repository](https://docs.moodle.org//en/OAuth\_2\_services). When the file is accessed, the system account is responsible for granting access to users. + + + + +> The return types that your plugin supports will be presented as options to the user when they are adding a file from the file picker. The `FILE_EXTERNAL` option is not reflected in this list as this is an internal feature of your API. +> +> ![Supported returntypes options settings](./_files/options.png) + + + + +```php +function supported_returntypes() { + return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK; +} +``` + + + + +The return values influence the choices offered to a user when selecting a file in file picker. Consider the screenshot above, which is from the dialogue that appears directly after choosing (but before uploading) a file in mod_resource. The three options result from `FILE_INTERNAL`, `FILE_REFERENCE`, and `FILE_CONTROLLED_LINK` being present. `FILE_REFERENCE` corresponds to the "alias/shortcut" option. + +The option `FILE_EXTERNAL` is never reflected in the file picker for mod_resource, so its absence or presence in supported_returntypes() is never reflected here. However, `FILE_EXTERNAL` is the only return type supported by mod_url: For mod_url, file picker will only(!) list repositories that support `FILE_EXTERNAL`. + +This implies that a plugin that uses a file picker is able to narrow the set of supported return types. For example, assignsubmission_file disallows `FILE_EXTERNAL` and `FILE_REFERENCE`. + +In the end, which type is +used by Moodle depends on the choices made by the end user (for example inserting a link, will result in `FILE_EXTERNAL`-related functions being used, using a 'shortcut/alias' will result in the '`FILE_REFERENCE`'-related functions being used). + +### supported_filetypes() + +If your plugin only supports certian file types, then you should implement the optional `supported_filetypes()` method. + +This method is used to hide repositories when they don't support certain file types - for example, if a user is inserting a video then any repository which does not support videos will not be shown. + +Supported file types can be specified using standard mimetypes (such as `image/gif`) or file groups (such as `web_image`). For a full list of the supported mimetypes and groups, see the [`core_filetypes`](https://github.com/moodle/moodle/blob/v4.0.0/lib/classes/filetypes.php#L47) class. + + + + +```php +function supported_filetypes() { + // Allow any kind of file. + return '*'; +} +``` + + + + +```php +function supported_filetypes() { + // Example of image mimetypes. + return ['image/gif', 'image/jpeg', 'image/png']; +} +``` + + + + +```php +function supported_filetypes() { + // Example of a file group. + return ['web_image']; +} +``` + + + + +### Course and User Repository Instances. + +A system-wide instance of a repository is created when it is enabled. It is also possible to support both course, and user specific repositories.. This can be achieved by setting the `enablecourseinstances` and `enableuserinstances` options. There are three ways that this can be done: + +1. Define **$string\['enablecourseinstances'\]** and **$string\['enableuserinstances'\]** in your plugin's language file. You can check an example in the [filesystem repository](https://github.com/moodle/moodle/blob/v4.0.0/repository/filesystem/lang/en/repository_filesystem.php). +2. The plugin must provide a **get_instance_option_names** method which returns at least one instance option name. This method defined the specific instances options, if none instance atrtribute is needed, the system will not allow the plguin to define course and user instances. Note, you must not define the form fields for these options in the **type_config_form()** function. For example, [filesystem repository](https://github.com/moodle/moodle/blob/v4.0.0/repository/filesystem/lib.php#L439). +3. Several 'core' repositories use the **db/install.php** to create the original repository instance by constructing an instance of the **repository_type** class. The options can be defined in the array passed as the second parameter to the constructor. For example [Wikipedia repository](https://github.com/moodle/moodle/blob/v4.0.0/repository/wikimedia/db/install.php). + +### Developer-defined API + +These are settings that are configured for the whole Moodle site and not per instance of your plugin. All of these are optional, without them there will be no configuration options in the Site administration > Plugins > Repositories > pluginname page. + +#### get_type_option_names(): array + +This function must be declared static + +Optional. Return an array of string. These strings are setting names. These settings are shared by all instances. +Parent function returns an array with a single item - pluginname. + +
+ View example +
+ +```php +public static function get_type_option_names() { + return array_merge(parent::get_type_option_names(), ['rootpath']); +} +``` + +
+
+ +#### type_config_form($mform, $classname='repository') + +This function must be declared static + +Optional. This is for modifying the Moodle form displaying the plugin settings. The [Form Definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) documentation has details of all the types of elements you can add to the settings form. + +
+ View example +
+ +For example, to display the standard repository plugin settings along with the custom ones use: + +```php +public static function type_config_form($mform, $classname='repository') { + parent::type_config_form($mform); + + $rootpath = get_config('repository_pluginname', 'rootpath'); + $mform->addElement('text', 'rootpath', get_string('rootpath', 'repository_pluginname'), array('size' => '40')); + $mform->setDefault('rootpath', $rootpath); +} +``` + +
+
+ +#### type_form_validation($mform, $data, $errors) + +This function must be declared static + +Optional. Use this function if you need to validate some variables submitted by plugin settings form. To use it, check through the associative array of data provided ('settingname' => value) for any errors. Then push the items to $error array in the format ("fieldname" => "human readable error message") to have them highlighted in the form. + +```php +public static function type_form_validation($mform, $data, $errors) { + if (!is_dir($data['rootpath'])) { + $errors['rootpath'] = get_string('invalidrootpath', 'repository_pluginname'); + } + return $errors; +} +``` + +### Instance settings + +These functions relate to a specific instance of your plugin (for example the URL and login details to access a specific webdav repository). All of these are optional, without them, the instance settings form will only contain a single 'name' field. + +#### get_instance_option_names(): array + +This function must be declared static + +Optional. Return an array of strings. These strings are setting names. These settings are specific to an instance. + +If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker. + +Parent function returns an empty array. This is equivalent to **get_type_option_names()**, but for a specific instance. + +
+ View example +
+ +```php +public static function get_instance_option_names() { + return ['fs_path']; // From repository_filesystem +} +``` + +
+
+ +#### instance_config_form($mform) + +This function must be declared static + +Optional. This is for modifying the Moodle form displaying the settings specific to an instance. This is equivalent to **type_config_form($mform, $classname)** but for instances. The [Form Definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) documentation has details of all the types of elements you can add to the settings form. + +
+ View example +
+ +For example, to add a required text box called email_address: + +```php +public static function get_instance_option_names() { + $mform->addElement( + 'text', + 'email_address', + get_string('emailaddress', 'repository_pluginname') + ); + $mform->addRule('email_address', $strrequired, 'required', null, 'client'); +} +``` + +
+
+ +:::note + +**mform** has by default a name text box (cannot be removed). + +::: + +#### instance_form_validation($mform, $data, $errors) + +This function must be declared static + +Optional. This allows us to validate what has been submitted in the instance configuration form. This is equivalent to ''type_form_validation($mform, $data, $errors), but for instances. For example: + +```php +public static function instance_form_validation($mform, $data, $errors) { + if (empty($data['email_address'])) { + $errors['email_address'] = get_string('invalidemailsettingname', 'repository_flickr_public'); + } +} +``` + +#### Getting / updating settings + +Both global and instance settings can be retrieved, from within the plugin, via **$this->get_option('settingname')** and updated via **$this->set_option(array('settingname' => 'value'))**. + +:::note + +You cannot call **$this** from static methods. If you need access the non static variables, you may have to store the values in the **_construct()** method into private static variables. + +::: + +#### plugin_init() + +This function must be declared static. + +Optional. This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once. + +Parent function does nothing. + +### Example of using the settings + +As an example, let's create a Flickr plugin for accessing a public flickr account. The plugin will be called "Flickr Public". + +Firstly the skeleton: + +```php title="repository/flickr_public/lib.php" +). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. To do so you need to override: + +- **get_type_option_names** returing ['api_key']. +- **type_config_form** adding the api_key text input element into the form. + +At this point we have created everything necessary for the administration pages. But let's go further. It would be good if the user can enter any "Flickr public account email address" in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let's add: + +- **plugin_init** using __repository::static_function__ to create a default repository instance. + +That's all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs. + +import FlickPublicLib from '!!raw-loader!./_examples/flickr_public_lib.php'; + + + +## Repository APIs + +### Quick Start + +The File Picker uses Ajax calls to present the repository content. In order to integrate a repository with the the Ajax callbacks there are several possibilities: + +- When a plugin requires a special user login (for example OAuth) the plugin must detect user session in the `constructor()` function, and use `print_login()` if required. +- For plugins that need to connect to a remote repository the connections can be done into the `get_listing()` or `constructor()` function. +- To retrieve the file that the user selected from a remote server, the plugin must rewrite the `get_file()` method. +- To provide search feature the plugin must rewrite the `search()` method. + +All those methods are descrived below. + +### Functions you MUST override + +These functions cover the basics of initialising your plugin each time the repository is accessed and listing the files available to the user from within the plugin. + +#### __construct($respoitoryid, $context=SYSCONTEXTID, $options=array(), $readonly=0) + +Should be overridden to do any initialisation required by the repository, including: + +- logging in via optional_param, if required - see 'print_login', below +- getting any options from the database + +The possible items in the $options array are: + +- 'ajax' - bool, true if the user is using the AJAX filepicker +- mimetypes' - array of accepted mime types, or '\*' for all types + +Calling parent::\__construct($repositoryid, $context, $options, $readonly); is essential and will set up various required member variables: + +- this->id - the repository instance id (the ID of the entry in mdl_repository_instances) +- this->context - the context in which the repository instance can be found +- this->instance - the repository instance record (from mdl_repository_instances) +- this->readonly - whether or not the settings can be changed +- this->options - the above options, combined with the settings saved in the database +- this->name - as specified by $this->get_name() +- this->returntypes - as specified by $this->supported_returntypes() + +#### get_listing($path="", $page="") + +This function will return a list of files to be displayed to the user, the list must be a array. + +
+ View example +
+ +```php +/** + * Get file listing. + * + * This is a mandatory method for any repository. + * + * See repository::get_listing() for details. + * + * @param string $encodedpath + * @param string $page + * @return array the list of files, including meta infomation + */ +public function get_listing($encodedpath = '', $page = '') { + // This methods + return [ + //this will be used to build navigation bar. + 'path'=>[ + [ + 'name'=>'root' + 'path'=>'/' + ], + [ + 'name'=>'subfolder', + 'path'=>'/subfolder' + ], + ], + 'manage'=>'http://webmgr.moodle.com', + 'list'=> [ + [ + 'title'=>'filename1', + 'date'=>'1340002147', + 'size'=>'10451213', + 'source'=>'http://www.moodle.com/dl.rar', + ], + [ + 'title'=>'folder', + 'date'=>'1340002147', + 'size'=>'0', + 'children'=>[], + ], + ], + ]; +} +``` + +
+
+ +Amongst other details, this returns a **title** for each file (to be displayed in the filepicker) and the **source** for the file (which will be included in the request to 'download' the file into Moodle or to generate a link to the file). Directories return a **children** value, which is either an empty array (if 'dynload' is specified) or an array of the files and directories contained within it. + +
+ The full specification of list element +
+ +import FullList from '!!raw-loader!./_examples/full_list.jsonc'; + +{FullList} + +
+
+ +### Dynamically loading + +Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won't be listed until user click the folder in file picker treeview. + +As a plug-in developer, if you set dynload flag as **true**, you should return files and folders (set children as a null array) in current path only instead of building the whole file tree. + +The use of the **object** tag, instead of returning a **list** of files, allows you to embed an external file chooser within the repository panel. See [Repository plugins embedding external file chooser](https://docs.moodle.org/dev/Repository_plugins_embedding_external_file_chooser) for details about how to do this. + +### User login (optional) + +If our plugin allows a user to log in to a remote service, you can support this using the `print_login()` and `check_login` function, which are desribed below. + +#### print_login + +For plugins which need to support login to a remote service, the `print_login()` function can be used to return an array of the form elements needed to support the login. + +
+ View example +
+ +:::note + +It is important to note that the repository login can be called on both Ajax and non ajax requests. For this reason the `print_login()` should check for `$this->options['ajax']` to know if it should return an array or the full login HTML form. + +::: + +```php +public function print_login() { // From repository_pluginname + global $OUTPUT; + + if ($this->options['ajax']) { + $user_field = (object) [ + 'label' => get_string('username', 'repository_pluginname'), + 'id' => 'pluginname_username', + 'type' => 'text', + 'name' => 'al_username', + ]; + + $passwd_field = (object) [ + 'label' => get_string('password', 'repository_pluginname'), + 'id' => 'pluginname_password', + 'type' => 'password', + 'name' => 'al_password', + ]; + + $ret = []; + $ret['login'] = [$user_field, $passwd_field]; + return $ret; + } else { // Non-AJAX login form - directly output the form elements. + // Print the login form HTML including the input username and password fields. + $loginform = new repository_pluginname\output\login(); + echo $OUTPUT->render($loginform); + // Example of a login form: + // + // + // + // + // + } +} +``` + +
+
+ +This will help to generate a form by file picker which contains user name and password input elements. + +If your login form is static and never changes, you can add `$ret['allowcaching']) = true;` and filepicker will not send the request to the server every time user opens the login/search form. + +For plugins that do not fully process the login via a popup window, the submitted details can be retrieved, from within the `__construct` function, via `$submitted = optional_param('fieldname', [PARAM_INT/PARAM_TEXT)`. + +
+ View example +
+ +```php title="lib/alfresco/lib.php" +public function __construct($repositoryid, $context = SYSCONTEXTID, $options = []) { + global $SESSION; + + /* Skipping code that is not relevant to user login */ + + $this->alfresco = new Alfresco_Repository($this->options['alfresco_url']); + $this->username = optional_param('al_username', '', PARAM_RAW); + $this->password = optional_param('al_password', '', PARAM_RAW); + try{ + // deal with user logging in. + if (empty($SESSION->{$this->sessname}) && !empty($this->username) && !empty($this->password)) { + $this->ticket = $this->alfresco->authenticate($this->username, $this->password); + $SESSION->{$this->sessname} = $this->ticket; + } else { + if (!empty($SESSION->{$this->sessname})) { + $this->ticket = $SESSION->{$this->sessname}; + } + } + $this->user_session = $this->alfresco->createSession($this->ticket); + $this->store = new SpacesStore($this->user_session); + } catch (Exception $e) { + $this->logout(); + } + $this->current_node = null; + + /* Skipping code that is not relevant to user login */ + +} +``` + +
+
+ +Many types include a single element of type 'popup' with the param 'url' pointing at the URL used to authenticate the repo instance. + +
+ View example +
+ +```php title="Code taken from repository_boxnet" +public function print_login() { + $ticket = $this->boxclient->getTicket(); + if ($this->options['ajax']) { + $loginbtn = (object)[ + 'type' => 'popup', + 'url' => ' https://www.box.com/api/1.0/auth/' . $ticket->get_oauth_tokens(), + ]; + $result = []; + $result['login'] = [$loginbtn]; + return $result; + } else { + // Print the login form HTML including the input username, password and ticket fields. + $loginform = new repository_boxnet\output\login($ticket); + echo $OUTPUT->render($loginform); + // Example of a login form: + // + // + // + // + // + // + } +} +``` + +
+
+ +#### check_login(): bool + +This function will return a boolean value to tell Moodle whether the user has logged in. +By default, this function will return true. + +```php +public function check_login(): bool { + global $SESSION; + return !empty($SESSION->{$this->sessname}); +} +``` + +#### logout + +When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here. After this the code should return something suitable to display to the user (usually the results of calling **$this->print_login()**): + +```php title="lib/alfresco/lib.php" +public function logout() { + global $SESSION; + unset($SESSION->{$this->sessname}); + return $this->print_login(); +} +``` + +### Transferring files to Moodle (optional) + +These functions all relate to transferring the files into Moodle, once they have been chosen in the filepicker. All of them are optional and have default implementations which are often suitable to use as they are. + +#### get_file_reference($source) + +This function takes $source as in user input, parses and cleans it (recommended to call clean_param()). It prepares the reference to the file in repository-specific format that would be passed on to methods get_file(), get_link(), get_moodle_file(), get_file_by_reference() and/or stored in DB in case of creating a shortcut to file. For the most of repositories it is just clean $source value. For has_moodle_files-repositories this function also changes encoding. + +#### get_file($url, $filename = "") + +For FILE_INTERNAL or FILE_REFERENCE this function is called at the point when the user has clicked on the file and then on 'select this file' to add it to the filemanager / editor element. It does the actual transfer of the file from the repository and onto the Moodle server. The default implementation is to download the $url via CURL. The $url parameter is the $reference returned by get_file_reference (above, but usually the same as the 'source' returned by 'get_listing'). The $filename should usually be processed by $path = $this->prepare_file($filename), giving the full 'path' where the file should be saved locally. This function then returns an array, containing: + +- path - the local path where the file was saved +- url - the $url param passed into the function + + + + +```php +public function get_file($url, $filename = '') { +// Default implementation from the base 'repository' class + $path = $this->prepare_file($filename); // Generate a unique temporary filename + $curlobject = new curl(); + $result = $curlobject->download_one($url, null, ['filepath' => $path, 'timeout' => self::GETFILE_TIMEOUT]); + if ($result !== true) { + throw new moodle_exception('errorwhiledownload', 'repository', '', $result); + } + return ['path'=>$path, 'url'=>$url]; +} +``` + + + + +Slightly extended version taken from repository_equella + +```php +public function get_file($reference, $filename = '') { + global $USER; + // Replace the line below by any method your plugin have to check a reference. + $details = example_external_server::get_details_by_reference($reference->reference)); + if (!isset($details->url) || !($url = $this->appendtoken($details->url))) { + // Occurs when the user isn't known.. + return null; + } + $path = $this->prepare_file($filename); + $cookiepathname = $this->prepare_file($USER->id. '_'. uniqid('', true). '.cookie'); + $curlobject = new curl(['cookie'=>$cookiepathname]); + $result = $curlobject->download_one( + $url, + null, + ['filepath' => $path, 'followlocation' => true, 'timeout' => self::GETFILE_TIMEOUT] + ); + // Delete cookie jar. + if (file_exists($cookiepathname)) { + unlink($cookiepathname); + } + if ($result !== true) { + throw new moodle_exception('errorwhiledownload', 'repository', '', $result); + } + return ['path'=>$path, 'url'=>$url]; +} +``` + + + + +#### get_link($url) + +Used with `FILE_EXTERNAL` to convert a reference (from 'get_file_reference', but ultimately from the output of 'get_listing') into a URL that can be used directly by the end-user's browser. Usually just returns the original $url, but may need further transformation based on the internal implementation of the repository plugin. + +#### get_file_source_info($source) + +Takes the 'source' field from 'get_listing' (as returned by the user's browser) and returns the value to be stored in files.source field in DB (regardless whether file is picked as a copy or by reference). It indicates where the file came from. It is advised to include either full URL here or indication of the repository. +Examples: 'Dropbox: /filename.jpg', 'http://fullurl.com/path/file', etc. + +This value will be used to display warning message if reference can not be restored from backup. Also it can (although not has to) be used in get_reference_details() to produce the human-readable reference source in the fileinfo dialogue in the file manager. + +### Search functions (optional) + +These functions allow you to implement search functionality within your repository. + +#### print_search + +When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form. + +A custom search form must include the following: + +- A text field element named **s**, this is where users will type in their search criteria +- A hidden element named **repo_id** and the value must be the id of the repository instance +- A hidden element named **ctx_id** and the value must be the context id of the repository instance +- A hidden element named **sesskey** and the value must be the session key + +
+ View example +
+ +```php title="The default implementation in class 'repository'" +public function print_search() { + global $PAGE; + $renderer = $PAGE->get_renderer('core', 'files'); + return $renderer->repository_default_searchform(); + // The default search HTML from repository/renderer.php: + // ; +} +``` + +
+
+ +#### search($search_text, $page = 0) + +Return the results of doing the search. Any additional parameters from the search form can be retrieved by $param = optional_param('paramname', [PARAM_INT / PARAM_TEXT);. + +The return should return an array containing: + +- list - with the same layout as the 'list' element in 'get_listing' + +
+ View example +
+ +```php title="Example from repoistory_googledocs" +public function search($search_text, $page = 0) { + $gdocs = new google_docs($this->googleoauth); + return [ + 'dynload' => true, + 'list' => $gdocs->get_file_list($search_text), + ]; +} +``` + +
+
+ +#### global_search() + +Return true if should be included in a search throughout all repositories (currently not available via the UI) + +### Repository support for returning file as alias/shortcut + +It is possible to link to the file from external (or internal) repository by reference. In UI it is called "create alias/shortcut". This creates a row in {files} table but the contents of the file is not stored. Although it may be cached by repository if developer wants to. + +Make sure that function supported_returntypes() returns FILE_REFERENCE among other types. + +Note that external file is synchronised by moodle when UI wants to show the file size. + +#### get_reference_file_lifetime() + +Return minimum number of seconds before checking for changes to the file (default implementation = 1 day) + +```php +public function get_reference_file_lifetime($ref) { + return DAYSECS; // One day, 60 * 60 * 24 seconds. +} +``` + +#### sync_individual_file(stored_file $storedfile) + +Called after the file has reached the 'lifetime' specified above to see if it should now be synchronised (default implementation is to return true) + +```php +public function sync_individual_file(stored_file $storedfile) { + return true; +} +``` + +#### get_reference_details($reference, $filestatus = 0) + +Returns human-readable information about where the original file is stored (to be displayed in the filepicker properties box). It is usually prefixed with repository name and semicolon (for example 'Myrepository: ). `$reference` is the 'source' output by `get_listing`. `$filestatus` can be either 0 (OK - default) or 666 (source file missing). + +
+ View example +
+ +```php title="lib.php" +public function get_reference_details($reference, $filestatus = 0) { + if (!$filestatus) { + // Replace the line below by any method your plugin have to check a reference. + $details = example_external_server::get_details_by_reference($reference); + return $this->get_name() . ': ' . $details->filename; + } else { + return get_string('lostsource', 'repository', ''); + } +} +``` + +
+
+ +#### get_file_by_reference($reference) + +Returns up-to-date information about the original file, only called when the 'lifetime' is reached and 'sync_individual_file' returns true. + +- For image files - download the file and return either $ret->filepath (full path on the server), $ret->handle (open handle to the file) or $ret->content (raw data from the file) to allow the file to be saved into the Moodle filesystem and the thumbnail to be updated +- For non-image files - avoid downloading the file (if possible) and just return $ret->filesize to update that information +- For missing / inaccessible files - return null + Remember this function may be called quite a lot, as the filemanager often wants to know the filesize. + +
+ View example +
+ +```php title="/lib.php" +public function get_file_by_reference($reference) { + global $USER; + // Replace the line below by any method your plugin have to check a reference. + $details = example_external_server::get_details_by_reference($reference->reference)); + if (!isset($details->url) || !($url = $this->appendtoken($details->url))) { + // Occurs when the user isn't known. + return null; + } + + // Download the file details. + $return = null; + $cookiepathname = $this->prepare_file($USER->id . '_' . uniqid('', true) . '.cookie'); + $headparams = ['followlocation' => true, 'timeout' => self::SYNCFILE_TIMEOUT]; + $curlobject = new curl(['cookie' => $cookiepathname]); + + if (file_extension_in_typegroup($ref->filename, 'web_image')) { + // The file is an image - download and return the file path. + $path = $this->prepare_file(''); + $result = $curlobject->download_one($url, null, $headparams); + if ($result === true) { + $return = (object) ['filepath' => $path]; + } + } else { + // The file is not an image - just get the file details. + + $result = $curlobject->head($url, $headparams); + } + + // Delete cookie jar. + if (file_exists($cookiepathname)) { + unlink($cookiepathname); + } + + $this->connection_result($ccurlobject->get_errno()); + $curlinfo = $ccurlobject->get_info(); + if ($return === null && isset($curlinfo['http_code']('list'])) && + $curlinfo['http_code']== 200 && + array_key_exists('download_content_length', $curlinfo) && + $curlinfo['download_content_length']('http_code']) >= 0) { + // We received a correct header and at least can tell the file size. + $return = (object) ['filesize' => $curlinfo['download_content_length']]; + } + return $return; +} +``` + +
+
+ +#### send_file($storedfile, $lifetime=86400, $filter=0, $forcedownload=false, array $options = null) + +Send the requested file back to the user's browser. The 'reference' for the file can be found via $storedfile->get_reference(). If the file is not found / no longer exists, the function 'send_file_not_found()' should be used. Otherwise the file should be output directly, via the most appropriate method: + +- Use a 'Location: ' header to redirect to the external URL +- Download the file and cache within the Moodle filesystem (possibly using '$this->import_external_file_contents()'), then call 'send_stored_file'. + +:::note + +It is up to the repository developer to decide whether to actually download the file or to return a locally cached copy instead. + +::: + +
+ View example +
+ +```php title="/lib.php" +public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + // Replace the line below by any method your plugin have to check a reference. + $details = example_external_server::get_details_by_reference($stored_file->get_reference())); + $url = $this->appendtoken($details->url); + if ($url) { + header('Location: ' . $url); + } else { + send_file_not_found(); + } +} +``` + +
+
+ +An example of caching files within the Moodle filesystem can be found in repository_dropbox. diff --git a/versioned_docs/version-4.1/apis/plugintypes/tiny/index.md b/versioned_docs/version-4.1/apis/plugintypes/tiny/index.md new file mode 100644 index 0000000000..5c4d31c46e --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/tiny/index.md @@ -0,0 +1,586 @@ +--- +title: TinyMCE Editor Plugins +tags: + - editor_tiny + - tiny + - Editor + - Text editor + - HTML editor + - WYSIWYG + - TinyMCE +--- + +Moodle includes the TinyMCE text editor as standard from Moodle 4.1, and it can be installed from the plugins database for Moodle versions 3.11, and 4.0. + +The `editor_tiny` editor supports the inclusion of subplugins, which have the namespace `tiny_[pluginname]`. + +import { + Lang, +} from '../../_files'; + +## File structure + +TinyMCE subplugins are located in the `/lib/editor/tiny/plugins` directory. A plugin should not include any custom files outside of its own plugin folder. + +Each plugin is in a separate subdirectory and consists of a number of _mandatory files_ and any other files the developer is going to use. + +:::important + +Some of the important files are described below. See the [common plugin files](../../commonfiles/index.mdx) documentation for details of other files which may be useful in your plugin. + +::: + +
+ The directory layout for the `tiny_example` plugin. + +```console +lib/editor/tiny/plugins/example +├── amd +│   ├── build +│   │   ├── commands.min.js +│   │   ├── commands.min.js.map +│   │   ├── common.min.js +│   │   ├── common.min.js.map +│   │   ├── configuration.min.js +│   │   ├── configuration.min.js.map +│   │   ├── options.min.js +│   │   ├── options.min.js.map +│   │   ├── plugin.min.js +│   │   └── plugin.min.js.map +│   └── src +│   ├── commands.js +│   ├── common.js +│   ├── configuration.js +│   ├── options.js +│   └── plugin.js +├── classes +│   ├── plugininfo.php +│   └── privacy +│   └── provider.php +├── lang +│   └── en +│   └── tiny_example.php +├── settings.php +└── version.php +``` + +
+ +:::info + +You will notice that the JavaScript is broken down into a number of source files. + +This separation is optional, but fits a convention demonstrate in the TinyMCE codebase, and make the code easier to read and understand. + +::: + +## Creating a new plugin + +We highly recommend using the [Plugin Skeleton Generator](https://moodle.org/plugins/tool_pluginskel) when creating a new plugin. + +For the sake of simplicity, this documentation assumes that you have created a new Plugin using the following skeleton configuration: + +```yml title="tiny_example.yml" +component: tiny_example +name: Example Plugin +release: "0.1.0" +copyright: 2022 Andrew Lyons +features: + settings: true +privacy: + haspersonaldata: false + uselegacypolyfill: false +tiny_features: + buttons: + - name: startdemo + category: content + text: Start demo + menuitems: + - name: startdemo + category: file + text: 'Start the demo' + options: + - name: myFirstProperty + type: string +``` + +### Generating the plugin skeleton + +Once you have created a plugin skeleton configuration, you can generate your plugin using the `cli/generate.php` command: + +```console +php admin/tool/pluginskel/cli/generate.php tiny_example.yml +``` + +This will generate a working skeleton file for your plugin. + +:::note Compiled JavaScript + +The plugin skeleton only produces source files for JavaScript. You will need to run `grunt` to compile this code. + +We highly recommend using `grunt watch` during development to simplify your workflow. + +```console +cd lib/editor/tiny/plugins/example && npx grunt amd && cd - +``` + +::: + +## Key files + +There are a number of key files within the generated plugin skeleton, described below. + +### common.js + +The common.js file is used to store a set of variables used by other parts of the plugin. + +Its usage is optional, but recommended as it reduces code duplication, and the potential for typos and mistakes. It also makes it easier to refactor code later. + +
+ An example common.js file generated by the plugin skeleton generator + +This example includes: + +- the plugin name (`tiny_example/plugin`); +- an icon, whose name is `tiny_example`; +- a button for the start demo action, whose name is `tiny_example_startdemo`; and +- a menu item for the start demo action, whose name is `tiny_example_startdemo`. + +```javascript title="commons=.js" +const component = 'tiny_example'; + +export default { + component, + pluginName: `${component}/plugin`, + icon: component, + startdemoButtonName: `${component}_startdemo`, + startdemoMenuItemName: `${component}_startdemo`, +}; +``` + +
+ +Typically this file will be included in other JS files in the plugin, usually only fetching the required variables, for example: + +```javascript +import {component, pluginName} from './common'; +``` + +### plugin.js + +The plugin.js is the entrypoint to the plugin code. It is primarily responsible for registering the plugin with the TinyMCE API, and the Moodle Integration of the Editor. + +
+ An example plugin.js file generated by the plugin skeleton generator + +```javascript title="plugin.js" +import {getTinyMCE} from 'editor_tiny/loader'; +import {getPluginMetadata} from 'editor_tiny/utils'; + +import {component, pluginName} from './common'; +import {register as registerOptions} from './options'; +import {getSetup as getCommandSetup} from './commands'; +import * as Configuration from './configuration'; + +// Setup the tiny_example Plugin. +export default new Promise(async(resolve) => { + // Note: The PluginManager.add function does not support asynchronous configuration. + // Perform any asynchronous configuration here, and then call the PluginManager.add function. + const [ + tinyMCE, + pluginMetadata, + setupCommands, + ] = await Promise.all([ + getTinyMCE(), + getPluginMetadata(component, pluginName), + getCommandSetup(), + ]); + + // Reminder: Any asynchronous code must be run before this point. + tinyMCE.PluginManager.add(pluginName, (editor) => { + // Register any options that your plugin has + registerOptions(editor); + + // Setup any commands such as buttons, menu items, and so on. + setupCommands(editor); + + // Return the pluginMetadata object. This is used by TinyMCE to display a help link for your plugin. + return pluginMetadata; + }); + + resolve([pluginName, Configuration]); +}); +``` + +
+ +The plugin can be broadly broken down into several different areas: + +#### The default export + +Every plugin must return a default export containing a new Promise. + +This allows the API to load multiple plugins in parallel with minimal blocking. + +```javascript +// Imports go here. +export default new Promise(async(resolve) => { + // Configure the plugin here. + + // Resolve when the plugin has been configured. + resolve([pluginName, Configuration]); +}); +``` + +#### Preparation + +The TinyMCE API does not support asynchronous code in the plugin registration. Therefore any asynchronous tasks must be complete before registering the plugin with the TinyMCE API, and before resolving the Promise. + +In the following example, we fetch the tinyMCE API, a set of plugin metadata to use, and a command setup function to call later on. + +```javascript +// Note: The PluginManager.add function does not support asynchronous configuration. +// Perform any asynchronous configuration here, and then call the PluginManager.add function. +const [ + tinyMCE, + pluginMetadata, + setupCommands, +] = await Promise.all([ + getTinyMCE(), + getPluginMetadata(component, pluginName), + getCommandSetup(), +]); +``` + +#### Registration of the plugin + +Once all of the dependencies are available, we can register the plugin with the TinyMCE PluginManager API. + +In this example, we register a plugin whose name is represented as a string in the `pluginName` variable. + +Whenever a new editor instance is created, it will call the callback providing the `editor` argument. + +At the end of the plugin instantiation, it returns a `pluginMetadata` object, which contains information about the plugin displayed in the help dialogue for the plugin. + +```javascript +// Reminder: Any asynchronous code must be run before this point. +tinyMCE.PluginManager.add(pluginName, (editor) => { + // Register any options that your plugin has + registerOptions(editor); + + // Setup any commands such as buttons, menu items, and so on. + setupCommands(editor); + + // Return the pluginMetadata object. This is used by TinyMCE to display a help link for your plugin. + return pluginMetadata; +}); +``` + +In this example, the plugin describes a set of options which will be passed from the PHP description of the plugin - these are handled by the `registerOptions(editor)` call. + +It also has a set of 'commands', which are a generic term used to describe any Buttons, MenuItems, and related UI features of the editor. + +### commands.js + +TinyMCE supports a range of commands. These are further defined in the TinyMCE API: [tinymce.editor.ui.Registry](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/). + +Most plugins will make use of one or more of the following commands, but others are also available: + +- [addIcon](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addIcon) +- [addButton](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addButton) +- [addToggleButton](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addToggleButton) +- [addMenuItem](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addmenuItem) +- [addToggleMenuItem](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor.ui.registry/#addmenuItem) + +Plugins may use any parts of the TinyMCE API that they need. + +
+ An example commands.js file generated by the plugin skeleton generator + +```javascript tilte="commands.js" +import {getButtonImage} from 'editor_tiny/utils'; +import {get_string as getString} from 'core/str'; +import { + component, + startdemoButtonName, + startdemoMenuItemName, + icon, +} from './common'; + +/** + * Handle the action for your plugin. + * @param {TinyMCE.editor} editor The tinyMCE editor instance. + */ +const handleAction = (editor) => { + // TODO Handle the action. + window.console.log(editor); +}; + +export const getSetup = async() => { + const [ + startdemoButtonNameTitle, + startdemoMenuItemNameTitle, + buttonImage, + ] = await Promise.all([ + getString('button_startdemo', component), + getString('menuitem_startdemo', component), + getButtonImage('icon', component), + ]); + + return (editor) => { + // Register the Moodle SVG as an icon suitable for use as a TinyMCE toolbar button. + editor.ui.registry.addIcon(icon, buttonImage.html); + + // Register the startdemo Toolbar Button. + editor.ui.registry.addButton(startdemoButtonName, { + icon, + tooltip: startdemoButtonNameTitle, + onAction: () => handleAction(editor), + }); + + // Add the startdemo Menu Item. + // This allows it to be added to a standard menu, or a context menu. + editor.ui.registry.addMenuItem(startdemoMenuItemName, { + icon, + text: startdemoMenuItemNameTitle, + onAction: () => handleAction(editor), + }); + }; +}; +``` + +
+ +:::important A note about synchronicity + +The TinyMCE `PluginManager.add` function requires all code to be called synchronously - that is to say that all Promises must be resolved before it is called. + +See more information on the Editor instance in the [tinymce.Editor](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor/) API documentation. + +::: + +#### `handleAction(editor)` + +The handleAction function is an example of one way in which the various buttons and menu items can handle their activation. + +The action passes a reference to the _instance_ of the TinyMCE editor in the `editor` variable. + +It should be possible to interact with all required parts fo the TinyMCE API using this value. + +#### `getSetup()` + +`getSetup()` function in the example above is an asynchronous function which returns a synchronous function. + +This is important because the TinyMCE PluginManager API requires all code to be synchronous and already exist. + +In this example strings are fetched fro the button and menu titles, and the icon is fetched using a Mustache Template. All of these functions return a Promise and therefore we must wait for them to resolve before returning the function which uses them. + +#### The curried setup function + +The `getSetup()` function returns a new function which is called from `plugin.js` during the instantiation of _each editor instance_. If you have five editors on a page, then this function is called five times - once per editor instance. + +This function is passed a partially-configured [tinymce.Editor](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editor/) instance on which it can call the registry commands to define the various buttons. + +### Plugin options - `options.js` and `plugininfo.php` + +There are often times that you will want to pass options, or values stored in Moodle's PHP API, to the JavaScript API in your plugin. TinyMCE has a detailed API to support parsing and validation of per-instance options which make this relatively easy. + +To help make this easier, several helpers exist. + +On the PHP side of the API, the `plugininfo.php` file can implement an optional `plugin_with_configuration` interface and define a `get_plugin_configuration_for_context` function: + +```php title="lib/editor/tiny/plugins/example/classes/plugininfo.php" + 'TODO Calculate your values here', + ]; + } +} +``` + +The values passed in may come from location including as site configuration values (that is `get_config()`), values based on capabilities, or values based on the `$options` passed in. + +On the JavaScript side of the API, an `options.js` file is used to handle parsing, validation, and fetching of the values. + +
+ +A complete example of the options.js implementation + +```javascript title="lib/editor/tiny/plugins/example/amd/src/options.js" +import {getPluginOptionName} from 'editor_tiny/options'; +import {pluginName} from './common'; + +// Helper variables for the option names. +const myFirstPropertyName = getPluginOptionName(pluginName, 'myFirstProperty'); + +/** + * Options registration function. + * + * @param {tinyMCE} editor + */ +export const register = (editor) => { + const registerOption = editor.options.register; + + // For each option, register it with the editor. + // Valid type are defined in https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editoroptions/ + registerOption(myFirstPropertyName, { + processor: 'number', + }); +}; + +/** + * Fetch the myFirstProperty value for this editor instance. + * + * @param {tinyMCE} editor The editor instance to fetch the value for + * @returns {object} The value of the myFirstProperty option + */ +export const getMyFirstProperty = (editor) => editor.options.get(myFirstPropertyName); +``` + +
+ +After being passed from the PHP API, the Moodle integration names properties according to the plugin from which they originate. In order to fetch the value out, you must use the `getPluginOptionName()` function to translate this value. + +```javascript title="Getting the namespaced option name" +const myFirstPropertyName = getPluginOptionName(pluginName, 'myFirstProperty'); +``` + +Before being set, or fetched back out, each property must be registered with the TinyMCE API. This is done for each _instance_ of the editor: + +```javascript title="Registering the option" +export const register = (editor) => { + const registerOption = editor.options.register; + + // For each option, register it with the editor. + // Valid type are defined in https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editoroptions/ + registerOption(myFirstPropertyName, { + processor: 'string', + }); +}; +``` + +:::info + +See the [tinymce.EditorOptions](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.editoroptions/) API documentation for further details on the `register` method. + +::: + +Finally, it is recommended that you then create helpers to fetch the values back out. + +```javascript title="Retrieving the stored value" +export const getMyFirstProperty = (editor) => editor.options.get(myFirstPropertyName); +``` + +:::info + +Editor options are specific to an _instance_ of the editor, therefore the `editor` must be passed as an argument. + +::: + +You may have multiple helpers, for example you may have a helper to process the value and return a boolean state of the value. + +```javascript title="Processing an option" +export const hasMyFirstProperty = (editor) => editor.options.isSet(myFirstPropertyName); +``` + +### Editor configuration - `configuration.js` + +The TinyMCE Editor only allows for very minimal configuration by administrators. This is a deliberate decision made to reduce the complexity of the User Interface, and to encourage consistency. + +To support this, for a plugin to place commands (that is buttons, and menu items) in the User Interface, they must modify the TinyMCE configuration. + +The TinyMCE [Menus](https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/#menu) and [Toolbar](https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/) configuration describes the expected format of this configuration. + +In order to make your configuration changes you must create a `Configuration` object, which contains a `configure()` function. This value should be resolved at the end of your `plugin.js` file, for example: + +```javascript title="Defining a Configuration" +import * as Configuration from './configuration'; + +export default new Promise(async(resolve) => { + // ... Plugin code goes here. + + resolve([pluginName, Configuration]); +}) +``` + +The Moodle TinyMCE Integration will call `Configuration.configure` after the initial editor configuration has been put in place. + +The `configure` function is passed the current configuration and must return an object which is merged into the configuration. + +For example, to add an item to the 'content' toolbar region, you would define your `configure` function return an Object containing the `toolbar` key: + +```javascript title="configuration.js" +export const configure = (instanceConfig) => { + return { + toolbar: addToolbarButtons(instanceConfig.toolbar, 'content', buttonName), + }; +}; +``` + +This is used to replace any matching keys in the existing `instanceConfig`. + +:::note + +The above example makes use of a helper from the `editor_tiny/utils` module, which contains a number of useful helpers. + +::: + +
+ +A complete example of configuring a menu and toolbar for tiny_example + +```javascript title="configuration.js" + +import { + startdemoButtonName, + startdemoMenuItemName, +} from './common'; + +import { + addMenubarItem, + addToolbarButtons, +} from 'editor_tiny/utils'; + +const getToolbarConfiguration = (instanceConfig) => { + let toolbar = instanceConfig.toolbar; + toolbar = addToolbarButtons(toolbar, 'content', [ + startdemoButtonName, + ]); + + return toolbar; +}; + +const getMenuConfiguration = (instanceConfig) => { + let menu = instanceConfig.menu; + menu = addMenubarItem(menu, 'file', [ + startdemoMenuItemName, + ].join(' ')); + + return menu; +}; + +export const configure = (instanceConfig) => { + return { + toolbar: getToolbarConfiguration(instanceConfig), + menu: getMenuConfiguration(instanceConfig), + }; +}; +``` + +
diff --git a/versioned_docs/version-4.1/apis/plugintypes/tiny/legacy.md b/versioned_docs/version-4.1/apis/plugintypes/tiny/legacy.md new file mode 100644 index 0000000000..5b8d44550e --- /dev/null +++ b/versioned_docs/version-4.1/apis/plugintypes/tiny/legacy.md @@ -0,0 +1,11 @@ +--- +title: Legacy TinyMCE Editor plugins +tags: + - TinyMCE + - Plugin + - Plugintype + - Editor +documentationDraft: true +--- + +The TinyMCE editor can be extended using the `tinymce` plugin type. This hasn't been documented yet - perhaps you are able to help us. diff --git a/versioned_docs/version-4.1/apis/subsystems/_category_.yml b/versioned_docs/version-4.1/apis/subsystems/_category_.yml new file mode 100644 index 0000000000..5586419cca --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/_category_.yml @@ -0,0 +1,3 @@ +label: Subsystems +link: + type: generated-index diff --git a/versioned_docs/version-4.1/apis/subsystems/_roles/Moodle-contexts-1.8.png b/versioned_docs/version-4.1/apis/subsystems/_roles/Moodle-contexts-1.8.png new file mode 100644 index 0000000000..18e8fc4001 Binary files /dev/null and b/versioned_docs/version-4.1/apis/subsystems/_roles/Moodle-contexts-1.8.png differ diff --git a/versioned_docs/version-4.1/apis/subsystems/access.md b/versioned_docs/version-4.1/apis/subsystems/access.md new file mode 100644 index 0000000000..0e963f0716 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/access.md @@ -0,0 +1,237 @@ +--- +title: Access API +tags: + - Access +--- + + + +import AcademyLink from '@site/src/components/AcademyLink'; + +The Access API gives you functions so you can determine what the current user is allowed to do. It also allows plugins to extend Moodle with new capabilities. + +## Overview + +Moodle uses a role-based access control model. Entities are represented by contexts which are arranged into a tree-like hierarchy known as the context tree. + +The following context types are available: + +| Context name | Represents | Immediate contents | Notes | +| --- | --- | --- | --- | +| `context_system` | The site as a whole | user, course category, module, and block | The System context is root context in the tree. There is only one System context | +| `context_user` | An individual user | block | Each user has their own, unique, context | +| `context_coursecat` | A single course category | course category, course, block | | +| `context_course` | A single course | module, block | | +| `context_module` | An activity | block | | +| `context_block` | A block | none | | + +A Role is a set of capability definitions, where each capability represents something that the user is able to do. Roles are defined at the top most +context in the context tree, the System context. + +Roles can be overridden by contexts further down the tree. + +User access is calculated from the combination of roles which are assigned to each user. + +All users that did not log-in yet automatically get the default role defined in `$CFG->notloggedinroleid`, it is not possible to assign any other role to this non-existent user id. There is one special guest user account that is used when user logs in using the guest login button or when guest auto-login is enabled. Again you can not assign any roles to the guest account directly, this account gets the `$CFG->guestroleid` automatically. All other authenticated users get the default user role specified in `$CFG->defaultuserroleid` and in the frontpage context the role specified in `$CFG->defaultfrontpageroleid`. + + + +## How to define new capabilities in plugins + +Capabilities are defined by `$capabilities` array defined in `db/access.php` files. The name of the capability consists of `plugintype/pluginname:capabilityname`. + +For example: + +```php title="mod/folder/db/access.php" +$capabilities = [ + 'mod/folder:managefiles' => [ + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'editingteacher' => CAP_ALLOW, + ], + ], +]; +``` + +Where the meaning of array keys is: + +| Field | Description | +| --- | --- | +| `riskbitmask` | associated risks. These are explained on [Hardening new Roles system](./roles.md). | +| `captype` | _read_ or _write_ capability type, for security reasons system prevents all write capabilities for guest account and not-logged-in users | +| `contextlevel` | specified as context level constant. Declares the typical context level where this capability is checked. This capability can be checked with contexts that are at a lower level (e.g. `moodle/site:accessallgroups` | could be checked with CONTEXT_MODULE). | +| `archetypes` | specifies defaults for roles with standard archetypes, this is used in installs, upgrades and when resetting roles (it is recommended to use only CAP_ALLOW here). Archetypes are defined in mdl_role table. See also [Role archetypes](https://docs.moodle.org/dev/Role_archetypes). | +| `clonepermissionsfrom` | when you are adding a new capability, you can tell Moodle to copy the permissions for each role from the current settings for another capability. This may give better defaults than just using archetypes for administrators who have heavily customised their roles configuration. The full syntax is: `clonepermissionsfrom` => `moodle/quiz:attempt` | + +It is necessary to bump up plugin version number after any change in db/access.php, so that the upgrade scripts can make the necessary changes to the database. To run the upgrade scripts, log in to Moodle as administrator, navigate to the site home page, and follow the instructions. (If you need to test the upgrade script without changing the plugin version, it is also possible to set back the version number in the mdl_block or mdl_modules table in the database.) + +The capability names are defined in plugin language files, the name of the string consists of "pluginname:capabilityname", in the example above it would be: + +```php title="mod/folder/lang/en/folder.php" +$string['folder:managefiles'] = 'Manage files in folder module'; +``` + +### Deprecating a capability + +When a capability is no longer needed or is replaced by another, it should be deprecated. The timeline for deprecation should follow the normal [Deprecation](/general/development/policies/deprecation) process. + +To mark a capability as deprecated, edit the access.php containing the capability, remove it from the `$capabilities` array, and add it to the `$deprecatedcapabilities` array in this file. + +Entries in `$deprecatedcapabilities` can have a `replacement` key indicating a new or existing capability that replaces the deprecated one. If this is specified, any checks to the deprecated capability will check the replacement capability instead. A debugging message will always be output at `DEBUG_DEVELOPER` level if a deprecated capability is checked. + +`$deprecatedcapaibilities` can also define an optional `message` explaining the deprecation. + +The following example demonstrates an access.php file where a capability has been deprecated and replaced with another. + +```php title="mod/folder/db/access.php" +$capabilities = [ + 'mod/folder:newmanagefiles' => [ + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'editingteacher' => CAP_ALLOW, + ], + ], +]; + +$deprecatedcapabilities = [ + 'mod/folder:managefiles' => [ + 'replacement' => 'mod/folder:newmanagefiles', + 'message' => 'This was replaced with another capability' + ], +]; +``` + +## Useful functions and classes + +### Context fetching + +In plugins context instances are usually only instantiated because they are instantiated and deleted automatically by the system. + +Fetching by object id: + +```php +$systemcontext = context_system::instance(); +$usercontext = context_user::instance($user->id); +$categorycontext = context_coursecat::instance($category->id); +$coursecontext = context_course::instance($course->id); +$contextmodule = context_module::instance($cm->id); +$contextblock = context_block::instance($this->instance->id); +``` + +Fetching by context id: + +```php +$context = context::instance_by_id($contextid); +``` + +Notes: + +- by default exception is thrown if context can not be created +- deleted users do not have contexts any more + +### Determining that a user has a given capability + +When implementing access control always ask "Does the user have capability to do something?". It is incorrect to ask "Does the user have a role somewhere?". + +#### has_capability() + +`has_capability()` is the most important function: + +```php +function has_capability( + string $capability, + context $context, + object $user = null, + bool $doanything = true +): bool; +``` + +Check whether a user has a particular capability in a given context. For example: + +```php +$context = context_module::instance($cm->id); +if (has_capability('mod/folder:managefiles', $context)) { + // Do or display something. +} +``` + +By default checks the capabilities of the current user, but you can pass a different user id. By default will return true for admin users, it is not recommended to use false here. + +#### require_capability() + +Function require_capability() is very similar, it is throwing access control exception if user does not have the capability. + +```php +function require_capability($capability, context $context, $userid = null, $doanything = true, $errormessage = 'nopermissions', $stringfile = _) { +``` + +### Enrolment functions + +See [Enrolment API](./enrol.md). + +### Other related functions + +```php +function require_login($courseorid = null, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false) +function require_course_login($courseorid, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false) +function get_users_by_capability(context $context, $capability, $fields = _, $sort = _, $limitfrom = _, $limitnum = _, + $groups = _, $exceptions = _, $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) +function isguestuser($user = null) +function isloggedin() +function is_siteadmin($user_or_id = null) +function is_guest(context $context, $user = null) +function is_viewing(context $context, $user = null, $withcapability = _) +``` + +#### `require_login()` + +Each plugin script should include require_login() or require_course_login() after setting up PAGE->url. + +This function does following: + +- it verifies that user is logged in before accessing any course or activities (not-logged-in users can not enter any courses). +- user is logged in as gu +- verify access to hidden courses and activities +- if an activity is specified, verify any [availability restrictions](./availability/index.md) for the activity +- verify that user is either enrolled or has capability 'moodle/course:view' or some enrol plugin gives them temporary guest access +- logs access to courses + +#### `require_course_login()` + +This function is supposed to be used only in activities that want to allow read access to content on the frontpage without logging-in. For example view resource files, reading of glossary entries, etc. + +#### `isguestuser()`, `isloggedin()` and `is_siteadmin()` + +These function were previously needed for limiting of access of special accounts. It is usually not necessary any more, because any *write* or *risky* capabilities are now automatically prevented in has_capability(). + +It is strongly discouraged to use is_siteadmin() in activity modules, please use standard capabilities and enrolment status instead. + +#### `is_guest()`, `is_viewing()` and `is_enrolled()` + +In order to access course data one of these functions must return true for user: + +- `is_enrolled()` - user has active record in user_enrolments table +- `is_viewing()` - user has 'moodle/course:view' capability (may access course, but is not considered to be participant) +- `is_guest()` - user was given temporary guest access by some enrolment plugin + +#### `get_users_by_capability()` + +This method returns list of users with given capability, it ignores enrolment status and should be used only above the course context. + +## See also + +- [API guides](../../apis.md) +- [Roles](./roles.md) +- [Role archetypes](https://docs.moodle.org/dev/Role_archetypes) +- [Hardening new Roles system](./roles.md) +- [Roles and modules](https://docs.moodle.org/dev/Roles_and_modules) +- [NEWMODULE Adding capabilities](https://docs.moodle.org/dev/NEWMODULE_Adding_capabilities) +- [New permissions evaluation in 2.0](https://docs.moodle.org/dev/New_permissions_evaluation_in_2.0) +- [(Forums) How to check if current user is student?](https://moodle.org/mod/forum/discuss.php?d=257611) diff --git a/versioned_docs/version-4.1/apis/subsystems/admin/index.md b/versioned_docs/version-4.1/apis/subsystems/admin/index.md new file mode 100644 index 0000000000..07097b678a --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/admin/index.md @@ -0,0 +1,224 @@ +--- +title: Admin settings +tags: [] +--- + + + + + + + + + + + + + + + + + +Moodle's configuration is stored in a mixture of the config, config_plugins, and a few other tables. These settings are edited through the administration screens, which can be accessed by going to the `.../admin/index.php` URL on your moodle site, or using the Administration block that appears to administrators of the Moodle front page. This page explains how the code for displaying and editing of these settings works. + +## Where to find the code + +This is explained further below, but in summary: + +- The library code is all in `lib/adminlib.php`. +- The definition of all the parts of the admin tree is in admin/settings/* some of which call out to plugins to see if they have settings they want to add. +- The editing and saving of settings is managed by `admin/settings.php`, `admin/upgradesettings.php`, and `admin/search.php`. +- The administration blocks that appear on the front page and on most of the admin screens is in `blocks/admin_tree` and `blocks/admin_bookmarks`. + +For further details refer the code. + +## The building blocks + +All the settings are arranged into a tree structure. This tree structure is represented in memory as a tree of PHP objects. + +At the root of the tree is an **admin_root** object. + +That has children that are **admin_category**s. + +Admin categories contain other categories, **admin_settingpage**s, and **admin_externalpage**s. + +Settings pages contain individual **admin_setting**s. + +admin_setting is a base class with lots of subclasses like **admin_setting_configtext**, **admin_setting_configcheckbox**, and so on. If you need to, you can create new subclasses. + +External pages are for things that do not fit into the normal settings structure. For example the global assign roles page, or the page for managing activity modules. + +## How the tree is built + +When Moodle needs the admin tree, it calls admin_get_root in `lib/adminlib.php`, which + +1. Creates a global $ADMIN object which is an instance of admin_root. +2. Does `require_once admin/settings/top.php`, which adds the top level categories to $ADMIN. +3. Does `require_once` on all the other files in `admin/settings` to add more specific settings pages and the settings themselves. Some of these settings files additionally make calls out to various types of plugins. For example: + - `admin/settings/plugins.php` gives activity modules, blocks, question types, ... a chance to add admin settings. +4. Adds the admin reports to the tree. + +As an optimisation, before building each bit of the tree, some capability checks are performed, and bits of the tree are skipped if the current user does not have permission to access them. + +## Settings file example + +This is an example of a settings.php file provided by a local_helloworld plugin. The file declares a single checkbox configuration variable called "showinnavigation". + +```php +. + +/** + * Adds admin settings for the plugin. + * + * @package local_helloworld + * @category admin + * @copyright 2020 Your Name + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +if ($hassiteconfig) { + $ADMIN->add('localplugins', new admin_category('local_helloworld_settings', new lang_string('pluginname', 'local_helloworld'))); + $settingspage = new admin_settingpage('managelocalhelloworld', new lang_string('manage', 'local_helloworld')); + + if ($ADMIN->fulltree) { + $settingspage->add(new admin_setting_configcheckbox( + 'local_helloworld/showinnavigation', + new lang_string('showinnavigation', 'local_helloworld'), + new lang_string('showinnavigation_desc', 'local_helloworld'), + 1 + )); + } + + $ADMIN->add('localplugins', $settingspage); +} +``` + +Few things to highlight: + +- Note the variable $hassiteconfig which can be used as a quick way to check for the moodle/site:config permission. This variable is set by the top-level admin tree population scripts. +- We always add the custom admin_settingpage to the tree, but the actual settings are added to that page only when $ADMIN->fulltree is set. This is to improve performance when the caller does not need the actual settings but only the administration pages structure. +- The lang_string class instances are used as proxy objects to represent strings. This has performance benefits as there is no need to evaluate all strings in the admin settings tree unless they are actually displayed. + +## Individual settings + +Let us look at a simple example: [mod/lesson/settings.php](https://github.com/moodle/moodle/blob/master/mod/lesson/settings.php). This is included by admin/settings/plugins.php, which has already created $settings, which is an admin_settingpage that we can add to. The file contains lots of lines that look a bit like: + +```php +$settings->add(new admin_setting_configtext('mod_lesson/mediawidth', get_string('mediawidth', 'lesson'), + get_string('configmediawidth', 'lesson'), 640, PARAM_INT)); +``` + +What this means is that to our settings page, we are adding a text setting. To understand this in more detail, we need to know what arguments the constructor for this admin_setting_configtext takes. The signature of the constructor is: + +```php +public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) +``` + +- $name here is 'mod_lesson/mediawidth'. The slash is important here. It indicates that this setting is owned by the mod_lesson plugin and should be stored in the config_plugins table. Settings that do not have this slash would be stored in the global config table and also exposes them via $CFG properties. Plugin-scope settings can be obtained only via get_config() call. You may notice some older plugins such as the Forum module or many blocks, still store their settings in the global config table. That is strongly discouraged for new plugins. +- $visiblename is set to a string get_string('mediawidth', 'lesson'), this is the label that is put in front of setting on the admin screen. +- $description is set to another string, this is a short bit of text displayed underneath the setting to explain it further. Older plugins used strings prefixed with "config" for this purpose. The current best practice is to use "_desc" prefix for them - see [String API#Help strings](https://docs.moodle.org/dev/String_API#Help_strings) +- $defaultsetting is the default value for this setting. This value is used when Moodle is installed. For simple settings like checkboxes and text fields, this is a simple value. For some more complicated settings, this is an array. +- $paramtype is one of the constants describing the type of the input text. The inserted value will be sanitised according to the declared type. +- $size allows to specify custom size of the field in the user interface + +Let us now look at a more complicated example, from mod/quiz/settingstree.php: + +```php + $quizsettings->add(new admin_setting_text_with_advanced('quiz/timelimit', get_string('timelimit', 'quiz'), get_string('timelimit_desc', 'quiz'), + ['value' => '0', 'fix' => false], PARAM_INT)); +``` + +*Note:* the naming convention used here is slightly outdated; new activity modules should use 'mod_mymodule/setting' instead of 'mymodule/setting' as identifier. + +This example shows a $defaultsetting that is an array. + +Normally, if you want a particular sort of setting, the easiest way is to look around the admin screens of your Moodle site, and find a setting like the one you want. Then go and copy the code and edit it. Therefore, we do not include a complete list of setting types here. + +## Locked and advanced settings for activity modules + +Admin settings are often used to define the defaults for activity settings. There is a simple way to enable admins make activity settings as "locked" (cannot be changed from the default) or "advanced" (deprecated, used before the change to "short forms"). + +When creating the admin setting (e.g. in mod/assign/settings.php), create the setting like this: + +```php +$name = new lang_string('teamsubmission', 'mod_assign'); +$description = new lang_string('teamsubmission_help', 'mod_assign'); +$setting = new admin_setting_configcheckbox('assign/teamsubmission', + $name, + $description, + 1); +$setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false); +$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); +$settings->add($setting); +``` + +To add "locked" and "advanced" check boxes to the admin setting. + +And finally, when creating the activity form (e.g. in mod/assign/mod_form.php), call this: + +```php +$this->apply_admin_defaults(); +``` + +at the end of the constructor to automatically set the default and lock the form element if required. + +Note: Locking an admin setting **will not force the value on existing settings*. Activity settings that are locked will need to be manually updated if they differ from the locked default value. + +## Callbacks after a setting has been updated + +A typical example is purging a cache after a setting has changed: + +```php +$setting = new admin_setting_configcheckbox(......); +$setting->set_updatedcallback('theme_reset_all_caches'); +``` + +## External pages + +admin_externalpages represent screens of settings that do not fall into the standard pattern of admin_settings. The admin_externalpage object in the settings tree holds the URL of a PHP page that controls various settings. + +In that PHP page, near the start you need to call the function admin_externalpage_setup($pagename). Although earlier versions of Moodle required you to then use admin_externalpage_print_header() and admin_externalpage_print_footer() functions instead of the usual print_header and print_footer functions, this is no longer necessary as of Moodle 2.0 - just use $OUTPUT->header() and $OUTPUT->footer() as per usual (don't forget to echo them). This ensures that your page appears with the administration blocks and appropriate navigation. + +Note that if your external page relies on additional optional or required params, you may need to use the optional arguments to admin_externalpage_print_header to ensure that the Blocks editing on/off button works. + +Note that there are some subclasses of admin_externalpage, for example admin_page_managemods. In a lot of cases, these subclasses only exist to override the search method so this page can be found by appropriate searches. + +Once again, to understand this in more depth, your best approach is to look at how some of the external pages in Moodle work. + +### When to use an admin_settings vs admin_externalpages + +The short answer is wherever possible always try to use admin settings rather than a custom page which uses formslib for anything related to admin settings. If you need something custom investigate a custom admin_setting class before a custom external page. There are a number of reasons, some around usability but mostly related to security: + +- **Searching** - Admin settings can be searched for using the admin search, while only the page title of the external page is searchable. In particular it is very useful to search directly for the key name of a config item, but also the settings current value and it's help text. Also the admin settings tree is a public API and there are 3rd party plugins which leverage this for various purposes, so your settings will be invisible. +- **Manage from CLI** - All admin settings in core and plugins can be managed via admin/cli/cfg.php, you don't get this for free if you store settings manually in a custom table. +- **Caching** - All admin settings in core and plugins are cached in the MUC and you don't get this for free if you store settings manually in a custom table. +- **Forced settings** - Normal admin settings can be forced in config.php and this is shown as forced in the GUI. Unless you re-implement this forced logic admins will get the false impression they are saving a setting when they are won't and it will cause a lot of confusion. +- **Forced passwords** - Password admin settings can be forced in config.php and these are not only locked but hidden from the admin (since Moodle 3.9). You would have to, and should, re-implement this logic on top of the 'forced' logic above. +- **Executable paths** - A special case of forced settings is paths to executables which should ideally be set in config.php with $CFG->preventexecpath = true; so they cannot be set in the GUI, even if they aren't in config.php. The admin_setting_configexecutable class handles all this logic for you. +- **Config log** - When an admin setting is changed, the before and after values are appended to the config log and visible in the config changes report. If you ever called set_config on directly on behalf of a human you should call add_to_config_log as well. It is really important to have a strong audit trail of who did what. For normal actions by anyone this should be in the moodle log, for admin settings it should be in the config log. + +An OK rule of thumb is: You can use an external page, which might use formslib, when the settings you are changing are in a custom table and not in the config tables via set_config. But you should seriously consider if and why you need a custom table first. If you are writing custom formslib elements it is usually just as easy to write a custom admin_setting instead. + +## See also + +- [adding settings for activity modules](https://docs.moodle.org/dev/Modules) +- [adding admin reports to the tree](https://docs.moodle.org/dev/Admin_reports#How_your_report_gets_included_in_the_admin_tree) +- configuration of repository plugins +- [configuration for filter](../../plugintypes/filter/index.md#adding-a-settings-screen) +- [Other developer documentation](https://docs.moodle.org/dev/Developer_documentation) diff --git a/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Analytics_API_classes_diagram_(summary).svg b/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Analytics_API_classes_diagram_(summary).svg new file mode 100644 index 0000000000..e6bbbfdd35 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Analytics_API_classes_diagram_(summary).svg @@ -0,0 +1,4 @@ + + + + diff --git a/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Inspire_API_components.png b/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Inspire_API_components.png new file mode 100644 index 0000000000..976d1d72cb Binary files /dev/null and b/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Inspire_API_components.png differ diff --git a/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Inspire_data_flow.png b/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Inspire_data_flow.png new file mode 100644 index 0000000000..915a745aee Binary files /dev/null and b/versioned_docs/version-4.1/apis/subsystems/analytics/_index/Inspire_data_flow.png differ diff --git a/versioned_docs/version-4.1/apis/subsystems/analytics/index.md b/versioned_docs/version-4.1/apis/subsystems/analytics/index.md new file mode 100644 index 0000000000..892d09a567 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/analytics/index.md @@ -0,0 +1,814 @@ +--- +title: Analytics API +description: The Analytics API allows managers to use predictions to detect trends and predict student behaviour +tags: + - Analytics + - API +--- + +import { Since } from '@site/src/components'; + + + +The Moodle Analytics API allows Moodle site managers to define _prediction models_ that combine _indicators_ and a _target_. + +The _target_ is the event we want to predict. The _indicators_ are what we think will lead to an accurate prediction of the target. + +Moodle is able to evaluate these models and, if the prediction accuracy is high enough, Moodle internally trains a machine learning algorithm by using calculations based on the defined indicators within the site data. Once new data that matches the criteria defined by the model is available, Moodle starts predicting the probability that the target event will occur. Targets are free to define what actions will be performed for each prediction, from sending messages or feeding reports to building new adaptive learning activities. + +:::note Example + +An example of a model you may be interested in is the detection of [students who are at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out). + +Possible _indicators_ for this include: + +- a lack of participation in previous activities +- poor grades in previous activities + +The _target_ would be whether the student is able to complete the course or not. + +Moodle uses these indicators and the target for each student in a finished course to predict which students are at risk of dropping out in ongoing courses. + +::: + +## Summary + +### API components + +This diagram shows the main components of the analytics API and the interactions between them. + +![Analytics API components and their interactions](./_index/Inspire_API_components.png) + +### Data flow + +The diagram below shows the different stages data goes through, from the data a Moodle site contains to actionable insights. + +![Data flow at different stages](./_index/Inspire_data_flow.png) + +### API classes diagram + +This is a summary of the API classes and their relationships. It groups the different parts of the framework that can be extended by 3rd parties to create your own prediction models. + +![API Class Structure](./_index/Analytics_API_classes_diagram_(summary).svg) + +## Built-in models + +People use Moodle in very different ways and even courses on the same site can vary significantly. Moodle core only includes models that have been proven to be good at predicting in a wide range of sites and courses. Moodle provides two built-in models: + +- [Students at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out) +- [No teaching](https://docs.moodle.org/en/Analytics#No_teaching) + +To diversify the samples and to cover a wider range of cases, the Moodle HQ research team is collecting anonymised Moodle site datasets from collaborating institutions and partners to train the machine learning algorithms with them. The models that Moodle is shipped with will be likely better at predicting on the sites of participating institutions, although some other datasets are used as test data for the machine learning algorithm to ensure that the models are good enough to predict accurately in any Moodle site. + +Even if the models included in Moodle core are already trained by Moodle HQ, each different site will continue training that site machine learning algorithms with its own data, which will lead to better prediction accuracy over time. + +## Concepts + +The following definitions are included for people not familiar with machine learning concepts: + +### Training + +This is the process to be run on a Moodle site before being able to predict anything. This process records the relationships found in site data from the past so the analytics system can predict what is likely to happen under the same circumstances in the future. What we train are machine learning algorithms. + +### Samples + +The machine learning backends we use to make predictions need to know what sort of patterns to look for, and where in the Moodle data to look. A sample is a set of calculations we make using a collection of Moodle site data. These samples are unrelated to testing data or phpunit data, and they are identified by an id matching the data element on which the calculations are based. The id of a sample can be any Moodle entity id: a course, a user, an enrolment, a quiz attempt, etc. and the calculations the sample contains depend on that element. Each type of Moodle entity used as a sample helps develop the predictions that involve that kind of entity. For example, samples based on Quiz attempts will help develop the potential insights that the analytics might offer that are related to the Quiz attempts by a particular group of students. See the [Analyser](#analyser) documentation for more information on how to use analyser classes to define what is a sample. + +### Prediction model + +As explained above, a prediction model is a combination of indicators and a target. System models can be viewed in **Site administration > Analytics > Analytics models**. + +The relationship between indicators and targets is stored in *analytics_models* database table. + +The class `\core_analytics\model` manages all of a model's related actions. *evaluate()*, *train()* and *predict()* forward the calculated indicators to the machine learning backends. `\core_analytics\model` delegates all heavy processing to analysers and machine learning backends. It also manages prediction models evaluation logs. + +`\core_analytics\model` class is not expected to be extended. + +#### Static models + +Some prediction models do not need a powerful machine learning algorithm behind them processing large quantities of data to make accurate predictions. There are obvious events that different stakeholders may be interested in knowing that we can easily calculate. These *Static model* predictions are directly calculated based on indicator values. They are based on the assumptions defined in the target, but they should still be based on indicators so all these indicators can still be reused across different prediction models. For this reason, static models are not editable through **Site administration > Analytics > Analytics models** user interface. + +Some examples of possible static models: + +- [Courses without teaching activity](https://docs.moodle.org/en/Analytics#No_teaching) +- Courses with students submissions requiring attention and no teachers accessing the course +- Courses that started 1 month ago and never accessed by anyone +- Students that have never logged into the system + +Moodle can already generate notifications for the examples above, but there are some benefits on doing it using the Moodle analytics API: + +- Everything is easier and faster to code from a development point of view as the analytics subsystem provides APIs for everything +- New Indicators will be part of the core indicators pool that researchers (and 3rd party developers in general) can reuse in their own models +- Existing core indicators can be reused as well (the same indicators used for insights that depend on machine learning backends) +- Notifications are displayed using the core insights system, which is also responsible of sending the notifications and all related actions. +- The Analytics API tracks user actions after viewing the predictions, so we can know if insights result in actions, which insights are not useful, etc. User responses to insights could themselves be defined as an indicator. + +### Analyser + +Analysers are responsible for creating the dataset files that will be sent to the machine learning processors. They are coded as PHP classes. Moodle core includes some analysers that you can use in your models. + +The base class `\core_analytics\local\analyser\base` does most of the work. It contains a key abstract method, *get_all_samples()*. This method is what defines the sample unique identifier across the site. Analyser classes are also responsible of including all site data related to that sample id; this data will be used when indicators are calculated. e.g. A sample id *user enrolment* would include data about the *course*, the course *context* and the *user*. Samples are nothing by themselves, just a list of ids with related data. They are used in calculations once they are combined with the target and the indicator classes. + +Other analyser class responsibilities: + +- Define the context of the predictions +- Discard invalid data +- Filter out already trained samples +- Include the time factor (time range processors, explained below) +- Forward calculations to indicators and target classes +- Record all calculations in a file +- Record all analysed sample ids in the database + +If you are introducing a new analyser, there is an important non-obvious fact you should know about: for scalability reasons, all calculations at course level are executed in per-course basis and the resulting datasets are merged together once all site courses analysis is complete. This is for performance reasons: depending on the sites' size it could take hours to complete the analysis of the entire site. This is a good way to break the process up into pieces. When coding a new analyser you need to decide if you want to extend `\core_analytics\local\analyser\by_course` (your analyser will process a list of courses), `\core_analytics\local\analyser\site-wide` (your analyser will receive just one analysable element, the site) or create your own analyser for activities, categories or any other Moodle entity. + +### Target + +Targets are the key element that defines the model. As a PHP class, targets represent the event the model is attempting to predict (the [dependent variable in supervised learning](https://en.wikipedia.org/wiki/Dependent_and_independent_variables)). They also define the actions to perform depending on the received predictions. + +Targets depend on analysers, because analysers provide them with the samples they need. Analysers are separate entities from targets because analysers can be reused across different targets. Each target needs to specify which analyser it is using. Here are a few examples to clarify the difference between analysers, samples and targets: + +- **Target**: 'students at risk of dropping out'. **Analyser provides sample**: 'course enrolments' +- **Target**: 'spammer'. **Analyser provides sample**: 'site users' +- **Target**: 'ineffective course'. **Analyser provides sample**: 'courses' +- **Target**: 'difficulties to pass a specific quiz'. **Analyser provides sample**: 'quiz attempts in a specific quiz' + +A callback defined by the target will be executed once new predictions start coming so each target have control over the prediction results. + +The API supports binary classification, multi-class classification and regression, but the machine learning backends included in core do not yet support multi-class classification or regression, so only binary classifications will be initially fully supported. See [MDL-59044](https://tracker.moodle.org/browse/MDL-59044) and [MDL-60523](https://tracker.moodle.org/browse/MDL-60523) for more information. + +Although there is no technical restriction against using core targets in your own models, in most cases each model will implement a new target. One possible case in which targets might be reused would be to create a new model using the same target and a different sets of indicators, for A/B testing + +#### Insights + +Another aspect controlled by targets is insight generation. Insights represent predictions made about a specific element of the sample within the context of the analyser model. This context will be used to notify users with the `moodle/analytics:listinsights` capability (the teacher role by default) about new insights being available. These users will receive a notification with a link to the predictions page where all predictions of that context are listed. + +A set of suggested actions will be available for each prediction. In cases like *[Students at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out)* the actions can be things like sending a message to the student, viewing the student's course activity report, etc. + +### Indicator + +Indicator PHP classes are responsible for calculating indicators (predictor value or [independent variable in supervised learning](https://en.wikipedia.org/wiki/Dependent_and_independent_variables)) using the provided sample. Moodle core includes a set of indicators that can be used in your models without additional PHP coding (unless you want to extend their functionality). + +Indicators are not limited to a single analyser like targets are. This makes indicators easier to reuse in different models. Indicators specify a minimum set of data they need to perform the calculation. The indicator developer should also make an effort to imagine how the indicator will work when different analysers are used. For example an indicator named *Posts in any forum* could be initially coded for a *Shy students in a course* target; this target would use *course enrolments* analyser, so the indicator developer knows that a *course* and an *enrolment* will be provided by that analyser, but this indicator can be easily coded so the indicator can be reused by other analysers like *courses* or *users'. In this case the developer can chose to require *course* **or** *user*, and the name of the indicator would change according to that. For example, *User posts in any forum* could be used in a user-based model like *Inactive users* and in any other model where the analyser provides *user* data; *Posts in any of the course forums* could be used in a course-based model like *Low participation courses.'' + +The calculated value can go from -1 (minimum) to 1 (maximum). This requirement prevents the creation of "raw number" indicators like *absolute number of write actions,* because we must limit the calculation to a range, e.g. -1 = 0 actions, -0.33 = some basic activity, 0.33 = activity, 1 = plenty of activity. Raw counts of an event like "posts to a forum" must be calculated in a proportion of an expected number of posts. There are several ways of doing this. One is to define a minimum desired number of events, e.g. 3 posts in a forum represents "some" activity, 6 posts represents adequate activity, and 10 or more posts represents the maximum expected activity. Another way is to compare the number of events per individual user to the mean or median value of events by all users in the same context, using statistical values. For example, a value of 0 would represent that the student posted the same number of posts as the mean of all student posts in that context; a value of -1 would indicate that the student is 2 or 3 standard deviations below the mean, and a +1 would indicate that the student is 2 or 3 standard deviations above the mean. + +:::danger Comparative rankings + +This kind of comparative calculation has implications to pedagogy: it suggests that there is a ranking of students from best to worst, rather than a defined standard all students can reach. Please be aware of this when considering how indicators are presented to users. + +::: + +### Analysis intervals + +Analysis intervals define when the system will calculate predictions and the portion of activity logs that will be considered for those predictions. They are coded as PHP classes and Moodle core includes some analysis intervals you can use in your models. + +:::info Time-splitting methods + +Analysis intervals were previously known as Time-splitting methods. + +::: + +In some cases the time factor is not important and we just want to classify a sample. This is relatively simple. Things get more complicated when we want to predict what will happen in future. For example, predictions about [Students at risk of dropping out](https://docs.moodle.org/en/Students_at_risk_of_dropping_out) are not useful once the course is over or when it is too late for any intervention. + +Calculations involving time ranges can be a challenging aspect of some prediction models. Indicators need to be designed with this in mind and we need to include time-dependent indicators within the calculated indicators so machine learning algorithms are smart enough to avoid mixing calculations belonging to the beginning of the course with calculations belonging to the end of the course. + +There are many different ways to split up a course into time ranges: in weeks, quarters, 8 parts, ten parts (tenths), ranges with longer periods at the beginning and shorter periods at the end... And the ranges can be accumulative (each one inclusive from the beginning of the course) or only from the start of the time range. + +Many of the analysis intervals included in Moodle assume that there is a fixed start and end date for each course, so the course can be divided into segments of equal length. This allows courses of different lengths to be included in the same prediction model, but makes these analysis intervals useless for courses without fixed start or end dates, e.g. self-paced courses. These courses might instead use fixed time lengths such as weeks to define the boundaries of prediction calculations. + +### Machine learning backends + +Documentation available in [Machine learning backends](../../plugintypes/mlbackend/index.md). + +## Design + +The system is designed as a Moodle subsystem and API. It lives in `analytics/`. All analytics base classes are located here. + +[Machine learning backends](../../plugintypes/mlbackend/index.md) is a new Moodle plugin type. They are stored in `lib/mlbackend`. + +Uses of the analytics API are located in different Moodle components, being core (`lib/classes/analytics`) the component that hosts general purpose uses of the API. + +### Interfaces + +This API aims to be as extendable as possible. Any moodle component, including third party plugins, is able to define indicators, targets, analysers and time splitting methods. Analytics API will be able to find them as long as they follow the namespace conventions described below. + +An example of a possible extension would be a plugin with indicators that fetch student academic records from the Universities' student information system; the site admin could build a new model on top of the built-in 'students at risk of drop out detection' adding the SIS indicators to improve the model accuracy or for research purposes. + +:::note Machine learning backends + +This section does not include Machine learning backend interfaces. See [Machine learning backends](../../plugintypes/mlbackend/index.md#interfaces) for more information on these. + +::: + +#### Analysable (core_analytics\analysable) + +Analysables are those elements in Moodle that contain samples. In most of the cases an analysable will be a course, although it can also be the site or any other Moodle element, e.g. an activity. Moodle core includes two analysers `\core_analytics\course` and `\core_analytics\site`. + +They list of methods that need to be implemented is quite simple and does not require much explanation. + +:::danger + +Analysable elements should be lazily loaded, otherwise you may encounter PHP memory issues. The reason is that analysers load all analysable elements in the site to calculate which ones are going to be calculated next (skipping the ones processed recently and stuff like that). + +See `core_analytics\course` as an example of how this can be achieved. + +::: + +Methods to implement: + +```php +/** + * The analysable unique identifier in the site. + * + * @return int. + */ +public function get_id(); + +/** + * The analysable human readable name + * + * @return string + */ +public function get_name(); + +/** + * The analysable context. + * + * @return \context + */ +public function get_context(); +``` + +`get_start` and `get_end` define the start and end times that indicators will use for their calculations. + +```php +/** + * The start of the analysable if there is one. + * + * @return int|false + */ +public function get_start(); + +/** + * The end of the analysable if there is one. + * + * @return int|false + */ +public function get_end(); +``` + +#### Analyser (core_analytics\local\analyser\base) + +The get_analysables() method has been deprecated in favour of a new `get_analysables_iterator()` for performance reasons. This method returns the whole list of analysable elements in the site. Each model will later be able to discard analysables that do not match their expectations. *e.g. if your model is only interested in quizzes with a time close the analyser will return all quizzes, your model will exclude the ones without a time close. This approach is supposed to make analysers more reusable.* + +```php +/** + * Returns the list of analysable elements available on the site. + * + * A relatively complex SQL query should be set so that we take into account which analysable elements + * have already been processed and the order in which they have been processed. Helper methods are available + * to ease to implementation of get_analysables_iterator: get_iterator_sql and order_sql. + * + * @param string|null $action 'prediction', 'training' or null if no specific action needed. + * @return \Iterator + */ +public function get_analysables_iterator(?string $action = null) +``` + +`get_all_samples` and `get_samples` should return data associated with the sample ids they provide. This is important for 2 reasons: + +- The data they provide alongside the sample origin is used to filter out indicators that are not related to what this analyser analyses. ''e.g. courses analysers do provide courses and information about courses, but not information about users, a **is user profile complete** indicator will require the user object to be available. A model using a courses analyser will not be able to use the **is user profile complete** indicator. +- The data included here is cached in PHP static vars; on one hand this reduces the amount of db queries indicators need to perform. On the other hand, if not well balanced, it can lead to PHP memory issues. + +```php +/** + * This function returns this analysable list of samples. + * + * @param \core_analytics\analysable $analysable + * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata) + */ +abstract public function get_all_samples(\core_analytics\analysable $analysable); +``` + +```php +/** + * This function returns the samples data from a list of sample ids. + * + * @param int[] $sampleids + * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata) + */ +abstract public function get_samples($sampleids); +``` + +`get_sample_analysable` method is executing during prediction: + +```php +/** + * Returns the analysable of a sample. + * + * @param int $sampleid + * @return \core_analytics\analysable + */ +abstract public function get_sample_analysable($sampleid); +``` + +The sample origin is the moodle database table that uses the sample id as primary key. + +```php +/** + * Returns the sample's origin in moodle database. + * + * @return string + */ +abstract public function get_samples_origin(); +``` + +`sample_access_context` associates a context to a `sampleid`. This is important because this sample predictions will only be available for users with `moodle/analytics:listinsights` capability in that context. + +```php +/** + * Returns the context of a sample. + * + * @param int $sampleid + * @return \context + */ +abstract public function sample_access_context($sampleid); +``` + +`sample_description` is used to display samples in *Insights* report: + +```php +/** + * Describes a sample with a description summary and a \renderable (an image for example) + * + * @param int $sampleid + * @param int $contextid + * @param array $sampledata + * @return array array(string, \renderable) + */ +abstract public function sample_description($sampleid, $contextid, $sampledata); +``` + +`processes_user_data` and `join_sample_user` methods are used by the analytics implementation of the privacy API. You only need to overwrite them if your analyser deals with user data. They are used to export and delete user data that is stored in analytics database tables: + +```php +/** + * Whether the plugin needs user data clearing or not. + * + * @return bool + */ +public function processes_user_data(); +``` + +```php +/** + * SQL JOIN from a sample to users table. + * + * More info in [https://github.com/moodle/moodle/blob/master/analytics/classes/local/analyser/base.php core_analytics\local\analyser\base]::join_sample_user + * + * @param string $sampletablealias The alias of the table with a sampleid field that will join with this SQL string + * @return string + */ +public function join_sample_user($sampletablealias); +``` + +You can overwrite a `new one_sample_per_analysable()` method if the analysables your model is using only have one sample. The insights generated by models will then include the suggested actions in the notification. + +```php +/** + * Just one sample per analysable. + * + * @return bool + */ +public static function one_sample_per_analysable() { + return true; +} +``` + +#### Indicator (core_analytics\local\indicator\base) + +Indicators should generally extend one of these 3 classes, depending on the values they can return: *core_analytics\local\indicator\binary* for **yes/no** indicators, *core_analytics\local\indicator\linear* for indicators that return linear values and *core_analytics\local\indicator\discrete* for categorised indicators. In case you want your activity module to implement a [community of inquiry](https://docs.moodle.org/en/Students_at_risk_of_dropping_out#Indicators) indicator you can extend *core_analytics\local\indicator\community_of_inquiry_indicator* look for examples in Moodle core. + +You can use `required_sample_data` to specify what your indicator needs to be calculated; you may need a *user* object, a *course*, a *grade item*... The default implementation does not require anything. Models which analysers do not return the required data will not be able to use your indicator so only list here what you really need. e.g. if you need a grade_grades record mark it as required, but there is no need to require the *user* object and the *course* as well because you can obtain them from the grade_grades item. It is very likely that the analyser will provide them as well because the principle they follow is to include as much related data as possible but do not flag related objects as required because an analyser may, for example, chose to not include the *user* object because it is too big and sites can have memory problems. + +```php +/** + * Allows indicators to specify data they need. + * + * e.g. A model using courses as samples will not provide users data, but an indicator like + * "user is hungry" needs user data. + * + * @return null|string[] Name of the required elements (use the database tablename) + */ +public static function required_sample_data() { + return null; +} +``` + +A single method must be implemented, `calculate_sample`. Most indicators make use of `$starttime` and `$endtime` to restrict the time period they consider for their calculations (for example read actions during `$starttime - $endtime` period) but some indicators may not need to apply any restriction (e.g. does this user have a user picture and profile description?) `self::MIN_VALUE` is `-1` and `self::MAX_VALUE` is `1`. We do not recommend changing these values. + +```php +/** + * Calculates the sample. + * + * Return a value from self::MIN_VALUE to self::MAX_VALUE or null if the indicator can not be calculated for this sample. + * + * @param int $sampleid + * @param string $sampleorigin + * @param integer $starttime Limit the calculation to this timestart + * @param integer $endtime Limit the calculation to this timeend + * @return float|null + */ +abstract protected function calculate_sample($sampleid, $sampleorigin, $starttime, $endtime); +``` + +:::danger Performance + +Performance here is critical as it runs once for each sample and for each range in the time-splitting method; some tips: + +- To avoid performance issues or repeated db queries analyser classes provide information about the samples that you can use for your calculations to save some database queries. You can retrieve information about a sample with `$user = $this->retrieve('user', $sampleid)`. *retrieve()* will return false if the requested data is not available. +- You can also overwrite *fill_per_analysable_caches* method if necessary (keep in mind though that PHP memory is not unlimited). +- Indicator instances are reset for each analysable and time range that is processed. This helps keeping the memory usage acceptably low and prevents hard-to-trace caching bugs. + +::: + +#### Target (core_analytics\local\target\base) + +Targets must extend `\core_analytics\local\target\base` or its main child class `\core_analytics\local\target\binary`. Even if Moodle core includes `\core_analytics\local\target\discrete` and `\core_analytics\local\target\linear` Moodle 3.4 machine learning backends only support binary classifications. So unless you are using your own machine learning backend you need to extend `\core_analytics\local\target\binary`. Technically targets could be reused between models although it is not very recommendable and you should focus instead in having a single model with a single set of indicators that work together towards predicting accurately. The only valid use case I can think of for models in production is using different time-splitting methods for it although, again, the proper way to solve this is by using a single time-splitting method specific for your needs. + +The first thing a target must define is the analyser class that it will use. The analyser class is specified in `get_analyser_class`. + +```php +/** + * Returns the analyser class that should be used along with this target. + * + * @return string The full class name as a string + */ +abstract public function get_analyser_class(); +``` + +`is_valid_analysable` and `is_valid_sample` are used to discard elements that are not valid for your target. + +```php +/** + * Allows the target to verify that the analysable is a good candidate. + * + * This method can be used as a quick way to discard invalid analysables. + * e.g. Imagine that your analysable don't have students and you need them. + * + * @param \core_analytics\analysable $analysable + * @param bool $fortraining + * @return true|string + */ +public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true); +``` + +```php +/** + * Is this sample from the $analysable valid? + * + * @param int $sampleid + * @param \core_analytics\analysable $analysable + * @param bool $fortraining + * @return bool + */ +public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true); +``` + +`calculate_sample` is the method that calculates the target value. + +```php +/** + * Calculates this target for the provided samples. + * + * In case there are no values to return or the provided sample is not applicable just return null. + * + * @param int $sampleid + * @param \core_analytics\analysable $analysable + * @param int|false $starttime Limit calculations to start time + * @param int|false $endtime Limit calculations to end time + * @return float|null + */ +protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false); +``` + +You may wish to add to the list of **actions** that will be offered to the recipient of an insight: + +```php +/** + * Suggested actions for a user. + * + * @param \core_analytics\prediction $prediction + * @param bool $includedetailsaction + * @param bool $isinsightuser + * @return \core_analytics\prediction_action[] + */ +public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false, + $isinsightuser = false) +``` + +You may override the users who will receive **insights notifications**: + +```php +/** + * Returns the list of users that will receive insights notifications. + * + * Feel free to overwrite if you need to but keep in mind that moodle/analytics:listinsights + * or moodle/analytics:listowninsights capability is required to access the list of insights. + * + * @param \context $context + * @return array + */ +public function get_insights_users(\context $context) +``` + +You can implement a `always_update_analysis_time()` method so analysable elements' `timeanalysed` is only updated when analysable elements have been successfully evaluated. It is useful for lightweight targets. + +```php +/** + * Update the last analysis time on analysable processed or always. + * + * If you overwrite this method to return false the last analysis time + * will only be recorded in DB when the element successfully analysed. You can + * safely return false for lightweight targets. + * + * @return bool + */ +public function always_update_analysis_time() +``` + +If you choose to implement `ignored_predicted_classes`, it must be public: + +```php + +/** + * Returns the predicted classes that will be ignored. + * + * @return array + */ +public function ignored_predicted_classes() +``` + +You can override the default message provided in the insight with `get_insight_subject()`: + +```php +/** + * The insight notification subject. + * + * This is just a default message, you should overwrite it for a custom insight message. + * + * @param int $modelid + * @param \context $context + * @return string + */ +public function get_insight_subject(int $modelid, \context $context) +``` + +You can also override the URL to the insight with get_insight_context_url(): + +```php +/** + * URL to the insight. + * + * @param int $modelid + * @param \context $context + * @return \moodle_url + */ +public function get_insight_context_url($modelid, $context) +``` + +#### Analysis interval (core_analytics\local\time_splitting\base) + +Analysis intervals are used to define when the analytics API will train the predictions processor and when it will generate predictions. As explained above in the [Analysis intervals](./index.md#analysis-intervals) documentation, they define time ranges based on analysable elements start and end timestamps. + +The base class is `\core_analytics\local\time_splitting\base`; if what you are after is to split the analysable duration in equal parts or in cumulative parts you can extend `\core_analytics\local\time_splitting\equal_parts` or `\core_analytics\local\time_splitting\accumulative_parts` instead. + +`define_ranges` is the main method to implement and its values mostly depend on the current analysable element (available in `$this->analysable`). An array of time ranges should be returned, each of these ranges should contain 3 attributes: A start time ('start') and an end time ('end') that will be passed to indicators so they can limit the amount of activity logs they read; the 3rd attribute is 'time', which value will determine when the range will be executed. + +```php +/** + * Define the time splitting methods ranges. + * + * 'time' value defines when predictions are executed, their values will be compared with + * the current time in ready_to_predict + * + * @return array('start' => time(), 'end' => time(), 'time' => time()) + */ +protected function define_ranges(); +``` + +A name and description should also be specified: + +```php +/** + * Returns a lang_string object representing the name for the time splitting method. + * + * Used as column identificator. + * + * If there is a corresponding '_help' string this will be shown as well. + * + * @return \lang_string + */ +public static function get_name() : \lang_string; +``` + + + +Analysis intervals can now override the following: + +`valid_for_evaluation()`. +You can return false if the time-splitting method can not be used to evaluate prediction models or if it does not make sense to evaluate prediction models with it, as for example upcoming_periodic children classes. + +`include_range_info_in_training_data()` and `get_training_ranges()`. +They can be used to create time splitting methods with a pre-defined number of ranges. + +The following cannot be overwritten: +`\core_analytics\local\time_splitting\base::append_rangeindex` and `\core_analytics\local\time_splitting\base::infer_sample_info` `\core_analytics\local\analyser\base::get_most_recent_prediction_range` have been moved to `\core_analytics\local\time_splitting\base::get_most_recent_prediction_range` and it is not overridable by time splitting methods. + +#### Calculable (core_analytics\calculable) + +Leaving this interface for the end because it is already implemented by `\core_analytics\local\indicator\base` and `\core_analytics\local\target\base` but you can still code targets or indicators from the `\core_analytics\calculable` base if you need more control. + +Both indicators and targets must implement this interface. It defines the data element to be used in calculations, whether as independent (indicator) or dependent (target) variables. + +## How to create a model + +New models can be created and implemented in php, and can be packaged as a part of a Moodle plugin for distribution. An example of model components and models are provided at https://github.com/dmonllao/moodle-local_testanalytics. + +:::info Third-party plugin developers + +Third party plugin developers can add their new elements (for example [targets](https://docs.moodle.org/en/Learning_analytics_targets), [indicators](https://docs.moodle.org/en/Learning_analytics_indicators), and so on) to the documentation pages provided for those components. These documentation pages are linked from the web UI for editing models. + +::: + +### Define the problem + +Start by defining what you want to predict (the target) and the subjects of these predictions (the samples). You can find the descriptions of these concepts above. The API can be used for all kinds of models, though if you want to predict something like "student success," this definition should probably have some basis in pedagogy. For example, the included model [Students at risk of dropping out](https://docs.moodle.org/34/en/Students_at_risk_of_dropping_out) is based on the Community of Inquiry theoretical framework, and attempts to predict that students will complete a course based on indicators designed to represent the three components of the CoI framework (teaching presence, social presence, and cognitive presence). Start by being clear about how the target will be defined. It must be trained using known examples. This means that if, for example, you want to predict the final grade of a course per student, the courses being used to train the model must include accurate final grades. + +### How many predictions for each sample? + +The next decision should be how many predictions you want to get for each sample (e.g. just one prediction before the course starts or a prediction every week). A single prediction for each sample is simpler than multiple predictions at different points in time in terms of how deep into the API you will need to go to code it. + +These are not absolute statements, but in general: + +- If you want a single prediction for each sample at a specific point in time you can reuse a site-wide analyser or define your own and control samples validity through your target's *is_valid_sample()* method. +- If you want multiple predictions at different points in time for each sample reuse an analysable element or define your own (extending `\core_analytics\analysable`) and reuse or define your own analyser to retrieve these analysable elements. You can control analysers validity through your target's *is_valid_analysable()* method. +- If you want predictions at activity level use a "by_course" analyser as otherwise you may have scalability problems (imagine storing in memory calculations for each grade_grades record in your site, though processing elements by courses help as we clean memory after each course is processed) + +This decision is important because: + +- Analysis intervals are applied to analysable time start and time end (e.g. **Quarters** can split the duration of a course in 4 parts). +- Prediction results are grouped by analysable in the admin interface to list predictions. +- By default, insights are notified to users with `moodle/analytics:listinsights` capability at analysable level (though this is only a default behaviour you can overwrite in your target). + +:::note + +Several of the existing analysis intervals are proportional to the length of the course (for example quarters, tenths, and so on). This allows courses with different lengths to be included in the same sample, but requires courses to have defined start and end dates. + +Other analysis intervals such as "Upcoming 3 days" are also available, which do not depend on the defined length of the course. These would be more appropriate for self-paced courses without fixed start and end dates. + +::: + +You do not need to require a single analysis interval at this stage, and they can be changed whenever the model is trained. You do need to define whether the model will make a single prediction or multiple predictions per analysable. + +### Create the target + +As specified in the [Target](./index.md#target-core_analyticslocaltargetbase) section. + +### Create the model + +To add a new model to the system, it must be defined in a PHP file. Plugins and core subsystems declare default prediction models by describing them in their db/analytics.php file. Models should not be created manually via the db/install.php file any more. (It is also possible to execute the necessary commands in a standalone PHP file that references the Moodle config.php.) + +To create the model, specify at least its target and, optionally, a set of indicators and a time splitting method: + +```php +// Instantiate the target: classify users as spammers +$target = \core_analytics\manager::get_target('\mod_yours\analytics\target\spammer_users'); + +// Instantiate indicators: two different indicators that predict that the user is a spammer +$indicator1 = \core_analytics\manager::get_indicator( + '\mod_yours\analytics\indicator\posts_straight_after_new_account_created' +); +$indicator2 = \core_analytics\manager::get_indicator( + '\mod_yours\analytics\indicator\posts_contain_important_viagra' +); +$indicators = [ + $indicator1->get_id() => $indicator1, + $indicator2->get_id() => $indicator2, +]; + +// Create the model. +// Note that the 3rd and 4rd arguments (the time splitting method and the predictions processor) +// are optional. +// The 4th argument is available from Moodle 3.6 onwards. +$model = \core_analytics\model::create( + $target, + $indicators, + '\core\analytics\time_splitting\single_range', + '\mlbackend_python\processor' +); +``` + +Models are disabled by default because you may be interested in evaluating how good the model is at predicting before enabling them. You can enable models using Moodle UI or the analytics API: + +```php +$model->enable(); +``` + +### Indicators + +You already know the analyser your target needs (the analyser is what provides samples) and, more or less, what time splitting method may be better for you. You can now select a list of indicators that you think will lead to accurate predictions. You may need to create your own indicators specific to the target you want to predict. + +You can use "'Site administration > Analytics > Analytics models"' to see the list of available indicators and add some of them to your model. + +## API usage examples + +This is a list of prediction models and how they could be coded using the Analytics API: + +[Students at risk of dropping out](https://docs.moodle.org/34/en/Students_at_risk_of_dropping_out) (based on student's activity, included in [Moodle 3.4](https://docs.moodle.org/34/en/Analytics)) + +- **Time splitting:** quarters, quarters accumulative, deciles, deciles accumulative... +- **Analyser samples:** student enrolments (analysable elements are courses) +- `target::is_valid_analysable` + - For prediction = ongoing courses + - For training = finished courses with activity +- `target::is_valid_sample` = true +- **Based on assumptions (static)**: no, predictions should be based on finished courses data + +Not engaging course contents (based on the course contents) + +- **Time splitting:** single range +- **Analyser samples:** courses (the analysable elements is the site) +- `target::is_valid_analysable` = true +- `target::is_valid_sample` + - For prediction = the course is close to the start date + - For training = no training +- **Based on assumptions (static)**: yes, a simple look at the course activities should be enough (e.g. are the course activities engaging?) + +No teaching (courses close to the start date without a teacher assigned, included in Moodle 3.4) + +- **Time splitting:** single range +- **Analyser samples:** courses (the analysable elements is the site) it would also work using course as analysable +- `target::is_valid_analysable` = true +- `target::is_valid_sample` + - For prediction = course close to the start date + - For training = no training +- **Based on assumptions (static)**: yes, just check if there are teachers + +Late assignment submissions (based on student's activity) + +- **Time splitting:** close to the analysable end date (1 week before, X days before...) +- **Analyser samples:** assignment submissions (analysable elements are activities) +- `target::is_valid_analysable` + - For prediction = the assignment is open for submissions + - For training = past assignment due date +- `target::is_valid_sample` = true +- **Based on assumptions (static)**: no, predictions should be based on previous students activity + +Spam users (based on suspicious activity) + +- **Time splitting:** 2 parts, one after 4h since user creation and another one after 2 days (just an example) +- **Analyser samples:** users (analysable elements are users) +- `target::is_valid_analysable` = true +- `target::is_valid_sample` + - For prediction = 4h or 2 days passed since the user was created + - For training = 2 days passed since the user was created (spammer flag somewhere recorded to calculate target = 1, otherwise no spammer) +- **Based on assumptions (static)**: no, predictions should be based on users activity logs, although this could also be done as a static model + +Students having a bad time + +- **Time splitting:** quarters accumulative or deciles accumulative +- **Analyser samples:** student enrolments (analysable elements are courses) +- `target::is_valid_analysable` + - For prediction = ongoing and course activity + - For training = finished courses +- `target::is_valid_sample` = true +- **Based on assumptions (static)**: no, ideally it should be based on previous cases + +Course not engaging for students (checking student's activity) + +- **Time splitting:** quarters, quarters accumulative, deciles... +- **Analyser samples:** course (analysable elements are courses) +- `target::is_valid_analysable` = true +- `target::is_valid_sample` + - For prediction = ongoing course + - For training = finished courses +- **Based on assumptions (static)**: no, it should be based on activity logs + +Student will fail a quiz (based on things like other students quiz attempts, the student in other activities, number of attempts to pass the quiz...) + +- **Time splitting:** single range +- **Analyser samples:** student grade on an activity, aka grade_grades id (analysable elements are courses) +- `target::is_valid_analysable` + - For prediction = ongoing course with quizzes + - For training = ongoing course with quizzes +- `target::is_valid_sample` + - For prediction = more than the X% of the course students attempted the quiz and the sample (a student) has not attempted it yet + - For training = the student has attempted the quiz at least X times (X because if after X attempts has not passed counts as failed) or the student passed the quiz +- **Based on assumptions (static)**: no, it is based on previous students records + +## Clustered environments + +The `analytics/outputdir` setting can be configured by Moodle sites with multiple frontend nodes to specify a directory shared across nodes. This directory can be used by machine learning backends to store trained algorithms (for example, its internal variables weights) to use them later to get predictions. + +The Moodle cron lock will prevent multiple executions of the analytics tasks that train machine learning algorithms and get predictions from them. diff --git a/versioned_docs/version-4.1/apis/subsystems/availability/index.md b/versioned_docs/version-4.1/apis/subsystems/availability/index.md new file mode 100644 index 0000000000..edd9d191ea --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/availability/index.md @@ -0,0 +1,164 @@ +--- +title: Availability API +tags: + - Availability + - core_availability +--- + +The availability API controls access to activities and sections. For example, a teacher could restrict access so that an activity cannot be accessed until a certain date, or so that a section cannot be accessed unless users have a certain grade in a quiz. + +:::note + +In older versions of Moodle, the conditional availability system defaulted to off; and users could enable it from the advanced features page in site administration. It is enabled by default for new installs of Moodle since Moodle 3.1, but sites which have been upgraded may still have to manually enable it. + +You can still call the API functions even if the system is turned off. + +::: + +## Using the API + +In most cases you do not need to use the API directly because the course and activity API already handles it for you. For example, if you are writing a module: + +- Moodle will automatically prevent users from accessing your module if they do not meet the availability conditions (unless they have the ability to access hidden activities). This is calculated when you call `require_login` and pass your activity's information. +- Your activity's form will automatically include controls for setting availability restriction, as part of the standard form controls. + +There are two special cases in which you may need to use the API directly. + +- Checking whether the current user can access an activity +- Displaying a list of users who may be able to access the current activity + +### Check access for a user + +#### Activities + +Some availability information can be accessed from an instance of the `cm_info` class, specifically in the `uservisible` property. + +This property considers a range of factors to indicate whether the activity should be visible to that user, including: + +- whether the activity was hidden by a teacher +- whether the activity is available based on the availability API + +The `cm_info` object also includes an `availableinfo` property which provides HTML-formatted information to explain to the user why they cannot access the activity. + +```php title="Checking and displaying availability information" +$modinfo = get_fast_modinfo($course); +$cm = $modinfo->get_cm($cmid); +if ($cm->uservisible) { + // User can access the activity. +} else if ($cm->availableinfo) { + // User cannot access the activity, but on the course page they will + // see a link to it, greyed-out, with information (HTML format) from + // $cm->availableinfo about why they can't access it. +} else { + // User cannot access the activity and they will not see it at all. +} +``` + +#### Course sections + +Some availability information can be accessed from an instance of the `section_info` class, specifically in the `uservisible` property. + +This property considers a range of factors to indicate whether the activity should be visible to that user, including: + +- whether the activity was hidden by a teacher +- whether the activity is available based on the availability API + +The `section_info` object also includes an `availableinfo` property which provides HTML-formatted information to explain to the user why they cannot access the activity. + +:::warning Checking sections and activities + +The `uservisible` check for an _activity_ automatically includes all relevant checks for the section that the activity is placed in. + +You **do not** need to check visibility and availability for _both_ the section and the activity. + +::: + +#### Accessing information for a different user + +The availability information in both the `cm_info` and `section_info` classes is calculated for the current user. You can also obtain them for a different user by passing a user ID to `get_fast_modinfo`, although be aware that doing this repeatedly for different users will be slow. + +### Display a list of users who may be able to access the current activity + +Sometimes you need to display a list of users who may be able to access the current activity. + +While you could use the above approach for each user, this would be slow and also is generally not what you require. For example, if you have an activity such as an assignment which is set to be available to students until a certain date, and if you want to display a list of potential users within that activity, you probably don't want to make the list blank immediately the date occurs. + +The system divides availability conditions into two types: + +- Applied to user lists, including: + - User group + - User grouping + - User profile conditions +- Not applied to user lists, including: + - The current date + - completion + - grade + +In general, the conditions which we expect are likely to change over time (such as dates) or as a result of user actions (such as grades) are not applied to user lists. + +If you have a list of users (for example you could obtain this using one of the 'get enrolled users' functions), you can filter it to include only those users who are allowed to see an activity with this code: + +```php +$info = new \core_availability\info_module($cm); +$filtered = $info->filter_user_list($users); +``` + +:::note + +The above example does not include the `$cm->visible` setting, nor does it take into account the `viewhiddenactivities` setting. + +::: + +## Using availability conditions in other areas + +The availability API is provided for activities (course-modules) and sections. It is also possible to use it in other areas such as _within_ a module. See [Availability API for items within a module](https://docs.moodle.org/dev/Availability_API_for_items_within_a_module). + +## Programmatically setting availability conditions + +In some situations you may need to _programmatically_ configure the availability conditions for an activity - for example you may have a custom enrolment plugin which creates assessable activities according to a student information system. + +To configure the availability, you can generate a JSON structure using an instance of the `core_availability\tree` class, and setting it against the activity or section record in the database, for example: + +```php +$restriction = \core_availability\tree::get_root_json([ + \availability_group\condition::get_json($group->id), +]); +$DB->set_field( + 'course_modules', + 'availability', + json_encode($restriction), + [ + 'id' => $cmid, + ] +); +rebuild_course_cache($course->id, true); +``` + +The following code can be used to programmatically set start and end date restrictions. + +```php +use \core_availability\tree; + +$dates = []; +$dates[] = \availability_date\condition::get_json(">=", $availability['start']); +$dates[] = \availability_date\condition::get_json("<", $availability['end']); + +$showc = [true, true]; +$restrictions = tree::get_root_json($dates, tree::OP_AND, $showc); + +$DB->set_field( + 'course_modules', + 'availability', + json_encode($restrictions), + [ + 'id' => $cmid, + ] +); +rebuild_course_cache($course->id, true); +``` + +The ```$showc``` array determines if the course modules will be shown or invisible when not available. + +## See also + +- Writing [Availability condition](../../plugintypes/availability/index.md) plugins diff --git a/versioned_docs/version-4.1/apis/subsystems/backup/index.md b/versioned_docs/version-4.1/apis/subsystems/backup/index.md new file mode 100644 index 0000000000..dfdddc3a47 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/backup/index.md @@ -0,0 +1,119 @@ +--- +title: Backup API +tags: + - Subsystem + - API + - Backup +--- +The Backup API provides a way to include your plugin's in the course backup. See [Restore API](./restore.md) for the part that takes care of restoring data. + +## Overview + +This page provides just a quick reference to the backup machinery in Moodle 2.0 and higher. There is a detailed documentation available at [Backup 2.0](https://docs.moodle.org/dev/Backup_2.0) page - see especially the tutorial for plugin authors at [Backup 2.0 for developers](https://docs.moodle.org/dev/Backup_2.0_for_developers) page. + +Moodle creates backups of courses or their parts by executing so called *backup plan*. The backup plan consists of a set of *backup tasks* and finally each backup task consists of one or more *backup steps*. You as the developer of a plugin will have to implement one backup task that deals with your plugin data. Most plugins have their backup tasks consisting of a single backup step - the one that creates the XML file with data from your plugin's tables. Some activities may need two or more steps to finish their backup task though (for example the backup task of the Quiz module consists of three steps as it needs to write not just the Quiz setting itself but also the questions used by that particular quiz). + +There are subtle differences in how the backup code is organised in various supported plugin types (activity modules, blocks, themes, course reports). + +## API for activity modules + +### Files + +The files that implement the backup support in your activity module must be located in the subdirectory backup/moodle2/ in your plugin's folder. So, if you are developing the activity module called *foobar* then the backup files will be located in mod/foobar/backup/moodle2/ folder. + +The following two files are supposed to exist in that location (replace *foobar* with the name of your module): + +- **backup_foobar_activity_task.class.php**
+Provides the activity task class +- **backup_foobar_stepslib.php**
+Provides all the backup steps classes + +If your module declares its own backup setting (apart from the ones common for all activity modules provided by the core), you will also want to create the backup_foobar_settingslib.php file to provide the setting classes. However most modules do not need this feature. + +### Backup task class + +The file backup_foobar_activity_task.class.php must provide a single class called **backup_foobar_activity_task**. All activity tasks extend the backup_activity_task class. + +There are three methods that your class must define. + +- **protected function define_my_settings()**
+If your module declares own backup settings defined in the file backup_foobar_settingslib.php, add them here. Most modules just leave the method body empty. +- **protected function define_my_steps()**
+This method typically consists of one or more `$this->add_step()` calls. This is the place where you define the task as a sequence of steps to execute. +- **static public function encode_content_links($content)**
+The current instance of the activity may be referenced from other places in the course by URLs like `http://my.moodle.site/mod/foobar/view.php?id=42` Obviously, such URLs are not valid any more once the course is restored elsewhere. For this reason the backup file does not store the original URLs but encodes them into a transportable form. During the restore, the reverse process is applied and the encoded URLs are replaced with the new ones valid for the target site. + +### Backup structure step class + +The classes that represent the backup steps added in define_my_steps() are implemented in the file backup_foobar_stepslib.php. Most plugins define just a single step in the class called **backup_foobar_activity_structure_step** that extends the backup_activity_structure_step class. This class defines the structure step - that is the step where the structure of your plugin's instance data is described and linked with the data sources. + +You have to implement a single method `protected function define_structure()` in this class class. There are three main things that the method must do. + +- Create a set of backup_nested_element instances that describe the required data of your plugin +- Connect these instances into a hierarchy using their `add_child()` method +- Set data sources for the elements, using their methods like `set_source_table()` or `set_source_sql()` + +The method must return the root backup_nested_element instance processed by the `prepare_activity_structure()` method (which just wraps your structures with a common envelope). + +## API for blocks + +### Files + +The files that implement the backup support in your block must be located in the subdirectory backup/moodle2/ in your plugin's folder. So, if you are developing the block called *foobar* then the backup files will be located in blocks/foobar/backup/moodle2/ folder. + +At least the file backup_foobar_block_task.class.php is supposed to exist in that location (replace *foobar* with the name of your block). + +If your block defines its own database tables, data from which must be included in the backup, you will want to create a file backup_foobar_stepslib.php, too. Additionally, if your block declares its own backup setting, you will also want to create the backup_foobar_settingslib.php file to provide the setting classes. However most blocks do not need this feature. + +### Backup task class + +The file backup_foobar_block_task.class.php must provide a single class called **backup_foobar_block_task**. All block tasks extend the backup_block_task class. + +There are five methods that your class must define. + +- **protected function define_my_settings()**
+If your block declares own backup settings defined in the file backup_foobar_settingslib.php, add them here. Most blocks just leave the method body empty. +- **protected function define_my_steps()**
+Blocks that do not have their own database tables usually leave this method empty. Otherwise this method consists of one or more `$this->add_step()` calls where you define the task as a sequence of steps to execute. +- **public function get_fileareas()**
+Returns the array of file area names within the block context. +- **public function get_configdata_encoded_attributes()**
+Instead of using their own tables, blocks usually use the configuration tables to hold their data (see the instance_config_save() of the block class). This method returns the array of all config elements that must be processed before they are stored in the backup. This is typically used when the stored config elements holds links to embedded media. Most blocks just return empty array here. +- **static public function encode_content_links($content)**
+If the current instance of the block may be referenced from other places in the course by URLs, it must be encoded into a transportable form. Most blocks just return unmodified $content parameter. + +## API for admin tools + +The files that implement the backup support in your plugin must be located in the subdirectory *backup/moodle2/* in your plugin's folder. So, if you are developing *tool_foobar* then the backup files will be located in *admin/tool/foobar/backup/moodle2/*. + +### Task class + +The file backup_tool_foobar_plugin.class.php must provide a single class called *backup_tool_foobar_task* extending *backup_tool_plugin*. + +Here is a minimalistic task: + +```php +require_once($CFG->dirroot . '/backup/moodle2/backup_tool_plugin.class.php'); + +class backup_tool_foobar_plugin extends backup_tool_plugin { + + protected function define_course_plugin_structure() { + $this->step->log('Yay, backup!', backup::LOG_DEBUG); + return $plugin; + } + +} +``` + +## API for themes + +See [Backup 2.0 theme data](https://docs.moodle.org/dev/Backup_2.0_theme_data) + +## API for reports + +See [Backup 2.0 course report data](https://docs.moodle.org/dev/Backup_2.0_course_report_data) + +## See also + +- [Restore API](./restore.md) +- [Core APIs](../../../apis.md) diff --git a/versioned_docs/version-4.1/apis/subsystems/backup/restore.md b/versioned_docs/version-4.1/apis/subsystems/backup/restore.md new file mode 100644 index 0000000000..10539c31dc --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/backup/restore.md @@ -0,0 +1,58 @@ +--- +title: Restore API +tags: + - Subsystem + - API + - Backup +--- +The Restore API provides a way to restore your plugin's data from a backup file created in Moodle 2.0 or later. For the information on how backup files are created, see [Backup API](./index.md). For the information on how to support restoring data from backup files created in Moodle 1.x, see [Backup conversion API](https://docs.moodle.org/dev/Backup_conversion_API). + +## Overview + +This page provides just a quick reference to the restore machinery in Moodle 2.0 and higher. There is a detailed documentation available at [Backup 2.0](https://docs.moodle.org/dev/Backup_2.0) page - see especially the tutorial for plugin authors at [Restore 2.0 for developers](https://docs.moodle.org/dev/Restore_2.0_for_developers) page. + +Moodle restores data from course backups by executing so called *restore plan*. The restore plan consists of a set of *restore tasks* and finally each restore task consists of one or more *restore steps*. You as the developer of a plugin will have to implement one restore task that deals with your plugin data. Most plugins have their restore tasks consisting of a single restore step - the one that parses the plugin XML file and puts the data into its tables. + +## API for activity modules + +### Files + +The files that implement the restore support in your activity module must be located in the subdirectory `backup/moodle2/` in your plugin's folder (yes, it's the same folder where the backup related files are located). So, if you are developing the activity module called *foobar* then the restore files will be located in `mod/foobar/backup/moodle2/` folder. + +The following two files are supposed to exist in that location (replace *foobar* with the name of your module): + +- **restore_foobar_activity_task.class.php**
+Provides the activity task class +- **restore_foobar_stepslib.php**
+Provides all the restore steps classes + +(to be continued) + +## API for admin tools + +The files that implement the backup support in your plugin must be located in the subdirectory `backup/moodle2/` in your plugin's folder. So, if you are developing `tool_foobar` then the backup files will be located in `admin/tool/foobar/backup/moodle2/`. + +### Task class + +The file `backup_tool_foobar_plugin.class.php` must provide a single class called `restore_tool_foobar_task` extending `restore_tool_plugin`. + +Here is a minimalistic task: + +```php +require_once($CFG->dirroot . '/backup/moodle2/restore_tool_plugin.class.php'); + +class restore_tool_foobar_plugin extends restore_tool_plugin { + + protected function define_course_plugin_structure() { + $paths = array(); + $this->step->log('Yay, restore!', backup::LOG_DEBUG); + return $paths; + } + +} +``` + +## See also + +- [Core APIs](../../../apis.md) +- [Backup API](../backup) diff --git a/versioned_docs/version-4.1/apis/subsystems/check/index.md b/versioned_docs/version-4.1/apis/subsystems/check/index.md new file mode 100644 index 0000000000..a1062fa2c8 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/check/index.md @@ -0,0 +1,229 @@ +--- +title: Check API +tags: + - API +--- + +A _Check_ is a runtime test to make sure that something is working well. You can think of Checks as similar and complimentary to the [PHPUnit](/general/development/tools/phpunit) and [Acceptance testing](/general/development/tools/behat) but the next layer around them, and performed at run time rather than development, or build time. + +Like other forms of testing the tests themselves should be easy to read, to reason about, and to confirm as valid. + +:::note + +Many types of runtime checks cannot be unit tested, and often the checks **are** the test. + +::: + +Checks can be used for a variety of purposes including: + +- configuration checks +- security checks +- status checks +- performance checks +- health checks + +Moodle has had various types of checks and reports for a long time but they were inconsistent and not machine readable. In Moodle 3.9 they were unified under a single Check API which also enabled plugins to cleanly define their own additional checks. Some examples include: + +- a password policy plugin could add a security check +- a custom authentication plugin can add a check that the upstream identity system can be connected to +- a MUC store plugin could add a performance check +Having these centralized and with a consistent contract makes it much easier to ensure the whole system is running smoothly. This makes it possible for an external integration with monitoring systems such as Nagios / Icinga. The Check API is expose as an NPRE compliance cli script: + +```console +php admin/cli/checks.php +``` + +## Result states of a check + +| Status | Meaning | Example | +| --- | --- | --- | +| N/A | This check doesn't apply - but we may still want to expose the check | secure cookies setting is disabled because site is not https | +| Ok | A component is configured, working and fast. | ldap can bind and return value with low latency | +| Info | A component is OK, and we may want to alert the admin to something non urgent such as a deprecation, or something which needs to be checked manually. | | +| Unknown | We don't yet know the state. eg it may be very expensive so it is run using the Task API and we are waiting for the answer. NOTE: unknown is generally a bad thing and is semantically treated as an error. It is better to have a result of Unknown until the first result happens, and from then on it is Ok, or perhaps Warning or Error if the last known result is getting stale. If you are caching or showing a stale result you should expose the time of this in the result summary text. | A complex user security report is still running for the first time. | +| Warning | Something is not ideal and should be addressed, eg usability or the speed of the site may be affected, but it may self heal (eg a load spike) | auth_ldap could bind but was slower than normal | +| Error | Something is wrong with a component and a feature is not working | auth_ldap could not connect, so users cannot start new sessions | +| Critical | An error which is affecting everyone in a major way | Cannot read site data or the database, the whole site is down | + +How the various states are then leveraged is a local decision. A typical policy might be that health checks with a status of 'Error' or 'Critical' will page a system administrator 24/7, while 'Warning' only pages during business hours. + +## Check types and reports + +Checks are broken down into types, which roughly map to a step life cycle of your Moodle System + +### Environmental checks + +Available from _/admin/environment.php_, environmental checks make sure that a Moodle instance is fully configured. + +This page is a potential candidate to move to the new Check API but it slightly more complex than the other checks so hasn't been tackled yet. It would be a deeper change and this is intrinsically part of the install and upgrade system. It is not as critical to refactor as it is already possible for a plugin to declare its own checks, via either declarative [Environment checking](https://docs.moodle.org/dev/Environment_checking) or programmatically with a custom check: + +### Configuration checks + +Available from _/admin/index.php?cache=1_, the Admin notifications page performs a mixture of checks, including security, status, and performance checks. + +None of these checks are as exhaustive as the checks in the reports below. It also does additional checks including whether the web services for the Moodle Mobile App are enabled, and whether the site has been registered. + +### Security checks (security) + +Available from _/report/security/index.php_, these checks make sure that a Moodle instance is hardened correctly for you needs. + +For more information see [MDL-67776](https://tracker.moodle.org/browse/MDL-67776). + +### Status checks (status) + +Available from _/report/status/index.php_, a status check is an 'in the moment' test and covers operational tests such as 'can moodle connect to ldap'. The main core status checks are that cron is running regularly and there has been no failed tasks. + +:::danger Important + +It is critical to understand that Status checks are conceptually defined at the level off the application and not at a lower host level such as a docker container or node in a cluster. Checks should be defined so that whichever instance you ask you should get a consistent answer. DO NOT use the Status Checks to detect containers which need reaped and restarted. If you do, any status error will mean all containers will simultaneously be marked for reaping. + +An additional status check is likely the most common type of check a plugin would define. Especially a plugin that connects to a 3rd party service. If the concept of 'OK' requires some sort of threshold, eg network response within 500ms, then that threshold should be managed by the plugin and optionally exposed as a admin setting. The plugin may choose to have different thresholds for Warning / Error / Critical. When designing a new Status Check be mindful that it needs to be actionable, for instance if you are asserting that a remote domain is available and it goes down, which then alerts your infrastructure team, there isn't much they can do about it if it isn't their domain. If it is borderline then make things like this configurable so that each site has to option of tune their own policies of what should be considered an issue or not. + +::: + +For more information see [MDL-47271](https://tracker.moodle.org/browse/MDL-47271). + +### Performance checks (performance) + +Available from _/report/performance/index.php_, each check might simply check for certain settings which are known to slow things down, or it might actually do some sort of test like multiple reads and writes to the db or filesystem to get a performance metric. + +### Health checks (health) + +Available from _/admin/tool/health_, the 'health center' is currently unsupported and contains some very old code. It is conceptually similar to 'status' checks except larger more long terms issues, such as detecting corrupt records. Ideally it is improved and converted to the Check API, see https://tracker.moodle.org/browse/[MDL-67228](https://tracker.moodle.org/browse/MDL-67228) + +## Implementing a new check + +### A check class + +And make a new check class in `mod/myplugin/classes/check/foobar.php` and the only mandatory method is `get_result()`. By default it will use a set language string but you can override the `get_name()` method to reuse an existing string. + +```php title="mod/myplugin/lang/en/myplugin.php" +$string['checkfoobar'] = 'Check the foos to make sure they are barred'; +``` + +```php title="mod/myplugin/classes/check/foobar" +id = "foobar{$id}"; + } + + public function get_id(): string { + return $this->id; + } + ... +} +``` + +### Make checks as fast as practical + +As many checks will be run and compiled into a report we want the checks themselves to be simple and as fast as possible. For instance an auth_ldap check while authenticating an end user could have a timeout of 60 seconds, and the check could warn if it takes more than 2 seconds. But the check could have a hard timeout of say 5 seconds and have a result status of ERROR for 5 or more seconds. + +### Lazy loading expensive result details + +Checks can provide details on a check, such as the complete list of bad records. Generally this type of information might be expensive to produce so you can defer this lookup until get_details() is called specifically rather than setting this in the constructor. It will only be loaded on demand and shown when you drill down into the check details page. + +```php +use_editor('mytextareaid'); +``` + +## Editor options + +The use_editor function allows an options array to be supplied. + +### General options + +- `context`: set to the current context object +- `enable_filemanagement`: set false to disable the file management plugin +- `autosave`: set false to disable autosave + +### Atto-specific options + +- `toolbar`: set to override which icons appear on the toolbar (normally it uses the admin setting - this is for special cases for example if you want a minimal editor in a particular plugin). + +The following example will cause atto to show the four buttons indicated. + +```php +$attobuttons = 'style1 = bold, italic' . PHP_EOL . 'list = unorderedlist, orderedlist'; +$editor->use_editor($id, [ + 'context' => $context, + 'autosave' => false, + 'atto:toolbar' => $attobuttons +], [ + 'return_types' => FILE_EXTERNAL, +]); +``` diff --git a/versioned_docs/version-4.1/apis/subsystems/enrol.md b/versioned_docs/version-4.1/apis/subsystems/enrol.md new file mode 100644 index 0000000000..33916d0c42 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/enrol.md @@ -0,0 +1,240 @@ +--- +title: Enrolment API +tags: + - Enrolment + - Library +--- + + + +The enrolment API gives access to the enrolment methods and also to [enrolment plugins](../plugintypes/enrol/index.md) instances. + +## Difference between user enrolment and role assignment + +Users enrolled in a course have at least one record in `user_enrolments` table. This table has the relation between courses and users **through an enrolment plugin instance**. However, `user_enrolments` does not contain information about the user role in the course, only information about: + +- **Enrolment plugin instance** +- **Enrolment status** (active or suspended). +- **Enrolment Start and end dates**. + +The specific role assignments are related to the context, not only to course (as activities and other pages can use its own). The specific roles of a user are stored in the `role_assignments` table. This table stores: + +- The **user role id** in the **context** +- The **component item** that assigned the role. In the case of a regular course, the *component* is the name of the enrolment plugin and the *item id* is the specific plugin instance. + +### What is enrolment? + +Enrolled users may fully participate in a course. Active user enrolment allows user to enter course. Only enrolled users may be group members. Grades are stored only for enrolled users. + +### Unenrolment + +Unenrolment is irreversible operation that purges user participation information. Full unenrolment is suitable only if you do not need to preserve all course participation information including user grades. + +### Enrolment status + +Instead of full unenrolment it is usually better to only *suspend* user enrolment. If there are other ways to enter the course (such guest access) it is recommended to remove user roles at the same time. + +Enrolments have two states defined by two constants: + +- `ENROL_USER_ACTIVE` the enrolment is active +- `ENROL_USER_SUSPENDED` the enrolment is suspended + +### Activity participation + +Activity developers decide the enrolment related behaviour of module. + +There are some general guidelines: + +- Only users with active enrolments should receive notifications. +- Activities should display enrolled users with some capability as participants. +- By default only users with active enrolments should be displayed in reports. +- There should be option to display all enrolled users including suspended enrolments. +- For performance reasons invisible participation data should be purged on unenrolment. +- Contributions visible by other participants should be kept after unenrolment (such as forum posts). + +## API functions + +### is_enrolled() + +Use this method to determine if a user is enrolled into a course. This method returns true for students and teachers, but false for administrators and other managers. + +:::caution + +User enrolments can be either active or suspended, suspended users can not enter the course (unless there is some kind of guest access allowed or have `moodle/course:view` capability) and are usually hidden in the UI. + +::: + +```php +function is_enrolled( + context $context, + stdClass $user = null, + string $withcapability = '', + bool $onlyactive = false +) +``` + +Good example is choice module where we have one slot for each participant, people that are not enrolled are not allowed to vote `is_enrolled($context, $USER, 'mod/choice:choose')`. Another example is assignment where users need to be enrolled and have capability to submit assignments `is_enrolled($this->context, $USER, 'mod/assignment:submit')`. + +### get_enrolled_users() + +Returns the list of enrolled users. This method allows to filter the result by capability, pagination or state. + +```php +function get_enrolled_users( + context $context, + string $withcapability = '', + int $groupid = 0, + string $userfields = 'u.*', + ?string $orderby = null, + int $limitfrom = 0, + int $limitnum = 0, + bool $onlyactive = false +) +``` + +
+ View example +
+ +Get all users who are able to summit an assignment: + +```php +$submissioncandidates = get_enrolled_users($modcontext, 'mod/assignment:submit'); +``` + +
+
+ +### count_enrolled_users() + +This method is used to get the total count of enrolments of a context. As `get_enrolled_users` this methods allow several filters like capability, group id or counting only active enrollments. + +```php +function count_enrolled_users( + context $context, + string $withcapability = '', + int $groupid = 0, + bool $onlyactive = false +) +``` + +### get_enrolled_sql() + +SQL `select from get_enrolled_sql()` is often used for performance reasons as it can be used in joins to get specific information for enrolled users only. + +```php +function get_enrolled_sql( + context $context, + string $withcapability = '', + int $groupid = 0, + bool $onlyactive = false, + bool $onlysuspended, + int $enrolid = 0 +) +``` + +### enrol_get_plugin(): enrol_plugin + +Returns the enrolment plugin base class with the given name. + +
+ View example +
+ +```php +$instance = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'manual']) +$enrolplugin = enrol_get_plugin($instance->enrol); +$enrolplugin->enrol_user($instance, $user->id, $role->id, $timestart, $timeend); +``` + +:::note + +As can be seen in the example, to use the plugin enrol_user and unenrol_user methods you need to get the instance record of the plugin first. + +::: + +
+
+ +## Enrolment plugin methods + +Once you use `enrol_get_plugin` function to get the enrolment plugin instance, you can use that class to modify the enrolments. + +### $enrol_plugin->enrol_user() + +Using this method you can enrol a user into a course. + +The method takes the following parameters: + +- Enrol plugin instance record +- User id +- Role id +- Optional enrolment start and end timestamps +- Optional enrolment status (ENROL_USER_ACTIVE or ENROL_USER_SUSPENDED) +- If the enrol must try to recover the previous user enrolment grades (if any) + +```php +$enrolplugin->enrol_user($instance, $user->id, $role->id, $timestart, $timeend, ENROL_USER_ACTIVE); +``` + +### $enrol_plugin->unenrol_user() + +Unenrol a user from a course enrolment method. + +:::note + +Other enrolment methods can define other roles to the same user. + +::: + +The method takes the following parameters: + +- Enrol plugin instance record +- User id + +```php +$enrolplugin->unenrol_user($instance, $user->id); +``` + +### $enrol_plugin->update_user_enrol() + +Updates a user enrolment **status** and the **start or end dates**. + +The method takes the following parameters: + +- Enrol plugin instance record +- User id +- Optional enrolment start and end timestamps +- Optional enrolment status (ENROL_USER_ACTIVE or ENROL_USER_SUSPENDED) + +```php +$enrolplugin->update_user_enrol($instance, $user->id, $timestart, $timeend, ENROL_USER_SUSPENDED); +``` + +### $enrol_plugin->add_default_instance() + +Add a new enrolment instance to a specific course an returns the instance id. This method will create a new instance record in the `enrol` table with the default values. + +The method takes the following parameters: + +- Course id + +```php +$enrolplugin->add_default_instance($course->id); +``` + +### $enrol_plugin->delete_instance() + +Remove an enrolment instance form a course and invalidate all related user enrolments. + +The method takes the following parameters: + +- Enrol plugin instance record + +```php +$enrolplugin->delete_instance($instance); +``` + +## See also + +- [Enrolment plugins](../plugintypes/enrol/index.md) diff --git a/versioned_docs/version-4.1/apis/subsystems/favourites/index.md b/versioned_docs/version-4.1/apis/subsystems/favourites/index.md new file mode 100644 index 0000000000..4a8429939b --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/favourites/index.md @@ -0,0 +1,132 @@ +--- +title: Favourites API +tags: + - API + - core_favourites +--- + +## Overview + +### What is a favourite? + +The favourites API allows you to mark items as favourites for a given user. Marking an item as a favourite is akin to adding a web page to your browser favourites (or bookmarks), or marking someone in your contacts as a favourite. The API provides a means to create, read and delete favourite items, allowing any component to favourite arbitrary items as they see fit. + +### What can be marked as a favourite? + +Almost any 'item' can be marked as a favourite, provided it is something which can be identified by a unique integer id. + +### Identifying items + +In order to store a favourite, and be able to uniquely identify it for later retrieval, 4 fields are required. These are: **component**, **itemtype**, **itemid** and **contextid**. You will see these in a range of API calls. + +The **itemid** is a unique integer identifier of the item itself. This might be a course id, or conversation id, or the id of any entity in Moodle. In fact, it does not have to be the id of a record from the database either; it can be any arbitrary id, so long as the component storing the item knows what it represents. + +The two fields **component** and **itemtype** make up a pairing representing the *type* of each favourite. Within this pair, the **component** must be a valid [frankenstyle](/general/development/policies/codingstyle/frankenstyle) component name and is the name of the component wishing to set/unset the item as a favourite. The **itemtype** can be any identifying string, provided it is unique within the respective component. The type pairing allows us to distinguish between favourites of different types (from different areas of Moodle), which may have identical itemid values. + +The **contextid** is the id of the context in which the item is being marked as a favourite. For example, a user's course might be marked as a favourite at the course context, whereas a user's conversation with another user might be marked as a favourite at the user context. It's also possible that items of a certain *type* (remember, this is the {component, itemtype} pairing) will be marked as favourites in different contexts, based on the context of the item itself. For example, consider the case in messaging, in which we have a group conversation (one which is linked to a course group), and an individual conversation between two users. Setting the group conversation as a favourite would require the course context to be used, whereas doing the same for the individual conversation would require a user context. Which contextid to use is a decision that must be made by the component creating the favourite. + +## Using the API + +### Getting a service object + +Favourites relies on a service layer to provide functionality to consumers. Getting a service object is as simple as using the service factory methods. + +Assuming you have a user context, you can get a service scoped to a single user with: + +```php +$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); +``` + +The returned `$ufservice` is an object of type \core_favourites\local\service\user_favourite_service. + +### Creating a favourite + +Let's say we want to set a course as a favourite. Note: In core, this is done by using the favourite *type* {'core_course', 'courses'}. + +The service provides the method: + +```php +public function create_favourite( + string $component, + string $itemtype, + int $itemid, + \context $context, + int $ordering = null +): favourite; +``` + +So, assuming we have the course id and course context, we can create our favourite with: + +```php +$favourite = $ufservice->create_favourite('core_course', 'courses', $course->id, $coursecontext); +``` + +The returned $favourite is an object of type \core_favourites\local\entity\favourite. + +### Reading favourites + +There are several read actions supported by the service object. + +```php +public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) : int; +public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array; +public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool; +public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) : favourite; +``` + +### Deleting a favourite + +The service provides the method: + +```php +public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context); +``` + +So, assuming we have the course id and course context, we can remove the favourite with: + +```php +$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); +$ufservice->delete_favourite('core_course', 'courses', $course->id, $coursecontext); +``` + +### Including favourites in external queries + +Most of the time, you should ask the service to find favourite items for you. Sometimes, however, rather than fetching the favourites from the service, you'll just want to include the relevant information in those records from an existing query. You might want to do this if dealing with performance sensitive code where additional queries are undesirable. + +The service lets you do this too, by providing the method: + +```php +public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array; +``` + +which can be used in such cases. + +For example, and for simplicity, let's say we have a query returning the ids and names of all courses within a given course category: + +```php +$sql = "SELECT c.id, c.name + FROM {course} c + WHERE c.category = :category"; +$params = ['category' => 3]; + +$courses = $DB->get_records_sql($sql, $params); +``` + +we can then modify this using the get_join_sql_by_type() result to include favourite information. + +```php +$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); +list($favsql, $favparams) = $ufservice->get_join_sql_by_type('core_course', 'courses', 'favalias', 'c.id'); + +$sql = "SELECT c.id, c.name, favalias.id as favouriteid + FROM {course} c + $favsql + WHERE c.category = :category"; +$params = ['category' => 3] + $favparams; + +$courses = $DB->get_records_sql($sql, $params); +``` + +We've now included id of the favourite in the results via a LEFT JOIN, so as to preserve the original set of records. + +If you wish to select ONLY favourites, adding `"AND favouriteid IS NOT NULL"` to the query will achieve this. diff --git a/versioned_docs/version-4.1/apis/subsystems/files/browsing.md b/versioned_docs/version-4.1/apis/subsystems/files/browsing.md new file mode 100644 index 0000000000..b5c1961d2f --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/files/browsing.md @@ -0,0 +1,45 @@ +--- +title: File Browser API +tags: + - File API + - Files +--- + +The File Browser API is a supplemental API which can be used to fetch information relating to Files stored in the [Moodle File API](./index.md). + +### Fetch a series of breadcrumbs to the requested file + +This example demonstrates using the `filebrowser` API to fetch the parent folders of a file. + +```php +public function get_file_breadcrumbs(\stored_file $file): ?array { + $browser = get_file_browser(); + $context = get_system_context(); + + $fileinfo = $browser->get_file_info( + \context::instance_by_id($file->get_contextid()), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename() + ) + + if ($fileinfo) { + // Build a Breadcrumb trail + $level = $fileinfo->get_parent(); + while ($level) { + $path[] = [ + 'name' => $level->get_visible_name(), + ]; + $level = $level->get_parent(); + } + + $path = array_reverse($path); + + return $path; + } + + return null; +} +``` diff --git a/versioned_docs/version-4.1/apis/subsystems/files/converter.md b/versioned_docs/version-4.1/apis/subsystems/files/converter.md new file mode 100644 index 0000000000..d94913d117 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/files/converter.md @@ -0,0 +1,98 @@ +--- +title: File Converters +tags: + - File + - core_file + - file_converter + - API + - PDF + - Conversion + - Document +--- + +Users are able to submit a wide range of files, and it is a common requirement to convert these to alternative formats. + +The most obvious example of this in Moodle core is in the `assignfeedback_editpdf` plugin which allows for conversion from a variety of document types into PDF to facilitate annotation. + +The file converters distributed with Moodle currently are: + +- Unoconv +- Google Drive + +The file converter API allows for conversion via multiple plugins and will automatically fallback to another suitable plugin upon failure. + +The API is designed to be called asynchronously as many cloud services offering document conversion offer an asynchronous API themselves. + +## Using the file converter API + +A file conversion is performed by the `core_files\converter` API and a single conversion is represented by the `core\files\conversion` class. + +Individual file conversions should always be accessed by the `core_files\converter` API. + +A file conversion is fetched or created by calling the `start_conversion` function on the converter API and passing in an existing `stored_file` record, along with the target format. + +```php title="Starting a new conversion" +$converter = new \core_files\converter(); +$conversion = $converter->start_conversion($file, 'pdf'); +``` + +If an existing file conversion matching the file and target format exists, the conversion record for this file will be returned, otherwise a new conversion is created and returned. + +To force a fresh conversion, a third boolean parameter can be passed, though this should not normally be necessary. + +```php title="Forcing a conversion to be performed again" +$converter = new \core_files\converter(); +$conversion = $converter->start_conversion($file, 'pdf', true); +``` + +### Polling for updates on an existing conversion + +When the `start_conversion` function is called, it automatically polls for any update on the conversion so it should not normally be necessary to poll the status separately. + +It is however possible to do so: + +```php title="Polling the API for the status of a conversino" +$converter = new \core_files\converter(); +$conversion = $converter->start_conversion($file, 'pdf'); +$converter->poll_conversion($conversion); +``` + +### Checking status of a conversion + +File conversions can have one of four states: + +- `STATUS_PENDING` - The conversion has not yet started; +- `STATUS_IN_PROGRESS` - A conversion has been picked up by a file converter and is currently in progress; +- `STATUS_COMPLETE` - The conversion was successful and the converted file is available; and +- `STATUS_FAILED` - All attempts to convert the file have failed. + +The conversion API provides a way to check the status of the conversion with the `$conversion->get_status()` function: + +```php title="Fetching status" +$converter = new \core_files\converter(); +$conversion = $converter->start_conversion($file, 'pdf'); + +switch ($conversion->get_status()) { + case \core_files\conversion::STATUS_COMPLETE: + // The file is ready. Do something with it. + case \core_files\conversion::STATUS_PENDING: + case \core_files\conversion::STATUS_IN_PROGRESS: + // Conversion still ongoing. Display spinner to the user. + case \core_files\conversion::STATUS_FAILED: + // Permanent failure - handle to the user. +} +``` + +### Fetching the target file + +Following a conversion, the target file is stored as a `stored_file` record and can be fetched for consumption elsewhere in your API: + +```php title="Fetching the new file" +if ($conversion->get_status() === \core_files\conversion::STATUS_COMPLETE) { + $file = $conversion->get_destfile(); +} +``` + +## See also + +- Creating a new [file converter plugin](../../plugintypes/fileconverter/index.md) diff --git a/versioned_docs/version-4.1/apis/subsystems/files/index.md b/versioned_docs/version-4.1/apis/subsystems/files/index.md new file mode 100644 index 0000000000..805646c6cb --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/files/index.md @@ -0,0 +1,438 @@ +--- +title: File API +tags: + - File API + - Files +--- + + + +The File API is used to control, manage, and serve all files uploaded and stored within Moodle. This page covers the core File API, which is responsible for storage, retrieval, and serving of files stored in Moodle. + +The following documentation is also related: + +- The [Repository API](../../plugintypes/repository/index.md) is responsible for the code paths associated with uploading files to Moodle. This includes Repository plugins. +- [Using the File API in Moodle forms](https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms) +- Additional detail of how this API works is discussed in the [File API internals](./internals.md) + +## File areas + +Files are conceptually stored in _file areas_. A file area is uniquely identified by: + +- A `contextid`. +- A full component name (using the [Frankenstyle](/general/development/policies/codingstyle/frankenstyle)/frankenstyle) format), for example `course`, `mod_forum`, `mod_glossary`, `block_html`. +- A file area type, for example `intro` or `post`. +- A unique `itemid`. Typically if there is only one of a file area per context, then the `itemid` is `0`, whilst if there can be multiple instances of a file area within a context, then the id of the item it relates to is used. For example in the course introduction text area, there is only one course introduction per course, so the `itemid` is set to `0`, whilst in a forum each forum post is within the same context, and the `itemid` should be the id of the post that it relates to. + +:::note + +File areas are not listed separately anywhere, they are stored implicitly in the files table. + +::: + +:::important Accessing files belonging to another component + +Please note that each plugin, or subsystem should only ever access its own file areas. Any other access should be made using that components own APIs. For example a file in the `mod_assign` plugin should only access files within the `mod_assign` component, and no other component should access its files. + +::: + +### Naming file areas + +The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas where possible (for example: intro, post, attachment, description). + +## Serving files to users + +The serving of files to users is separated into two distinct areas: + +1. Generating an appropriate URL to the file; and +2. Parsing the URL to serve the file correctly. + +This allows Moodle to have a shared file serving mechanism which is common to all Moodle components. + +When serving files you _must_ implement both parts together. + +### Generating a URL to your files + +You must refer to the file with a URL that includes a file-serving script, often `pluginfile.php`. This is usually generated with the `moodle_url::make_pluginfile_url()` function. For example: + +```php title="Generating a pluginfile URL for a known file" +$url = moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename(), + false // Do not force download of the file. +); +``` + +:::note + +Note: If you do not need the `itemid`, then you _may_ pass a `null` value instead of the `itemid`. + +This will remove the `itemid` from the URL entirely - this must be considered when [serving your file to the user](#serving-your-file-to-the-user). + +::: + +The final parameter (`false` here) is `forcedownload`. + +### Serving your file to the user + +File serving is performed by a small number of file serving scripts which include: + +- `pluginfile.php`; and +- `tokenpluginfile`.php. + +These file serving scripts are responsible for authenticating the user, parsing the URL, and then passing the parameters provided in the URL to the relevant component, via a _callback_, which will then serve the file. + +The relevant component is then responsible for finding the file, performing relevant security checks, and finally serving the file to the user. + +The component callback _must_ be located in your plugin's `lib.php` file, and _must_ be named `[component]_pluginfile()`. It is passed the following values: + +- the context id; +- the component name; +- the name of the file area; +- any item id, if specified in the URL; and +- the file path and file name. + +:::info + +The complete function signature for this callback is as follows: + +```php +/** + * Serve the requested file for the [component_name] plugin. + * + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param stdClass $context the context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file not found, just send the file otherwise and do not return anything + */ +function [component_name]_pluginfile( + $course, + $cm, + $context. + string $filearea, + array $args, + bool $forcedownload, + array $options +): bool; +``` + +
+ See an example implementation of this callback for an activity module. + +```php title="mod/myplugin/lib.php" +/** + * Serve the files from the myplugin file areas. + * + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param stdClass $context the context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file not found, just send the file otherwise and do not return anything + */ +function mod_myplugin_pluginfile( + $course, + $cm, + $context, + string $filearea, + array $args, + bool $forcedownload, + array $options = [] +): bool { + global $DB; + + // Check the contextlevel is as expected - if your plugin is a block, this becomes CONTEXT_BLOCK, etc. + if ($context->contextlevel != CONTEXT_MODULE) { + return false; + } + + // Make sure the filearea is one of those used by the plugin. + if ($filearea !== 'expectedfilearea' && $filearea !== 'anotherexpectedfilearea') { + return false; + } + + // Make sure the user is logged in and has access to the module (plugins that are not course modules should leave out the 'cm' part). + require_login($course, true, $cm); + + // Check the relevant capabilities - these may vary depending on the filearea being accessed. + if (!has_capability('mod/myplugin:view', $context)) { + return false; + } + + // The args is an array containing [itemid, path]. + // Fetch the itemid from the path. + $itemid = array_shift($args); + + // The itemid can be used to check access to a record, and ensure that the + // record belongs to the specifeid context. For example: + if ($filearea === 'expectedfilearea') { + $post = $DB->get_record('myplugin_posts', ['id' => $itemid]); + if ($post->myplugin !== $context->instanceid) { + // This post does not belong to the requested context. + return false; + } + + // You may want to perform additional checks here, for example: + // - ensure that if the record relates to a grouped activity, that this + // user has access to it + // - check whether the record is hidden + // - check whether the user is allowed to see the record for some other + // reason. + + // If, for any reason, the user does not hve access, you can return + // false here. + } + + // For a plugin which does not specify the itemid, you may want to use the following to keep your code consistent: + // $itemid = null; + + // Extract the filename / filepath from the $args array. + $filename = array_pop($args); // The last item in the $args array. + if (empty($args)) { + // $args is empty => the path is '/'. + $filepath = '/'; + } else { + // $args contains the remaining elements of the filepath. + $filepath = '/' . implode('/', $args) . '/'; + } + + // Retrieve the file from the Files API. + $fs = get_file_storage(); + $file = $fs->get_file($context->id, 'mod_myplugin', $filearea, $itemid, $filepath, $filename); + if (!$file) { + // The file does not exist. + return false; + } + + // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. + send_stored_file($file, DAY_SECS, 0, $forcedownload, $options); +} +``` + +
+ +::: + +## Getting files from the user + +You will typically use the [Forms API](https://docs.moodle.org/dev/Forms_API) to accept files from users. This topic is detailed in more detail in the [Using the File API in Moodle forms](https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms) documentation. + +## Common uses of the file API + +Although you will usually interact with the File API from other related APIs including the Form, and Repository APIs, you may find that you need to interact with files directly for a range of purposes. + +### Create files + +There are several ways to created files in the Moodle file store. Each of them +requires a `fileinfo` record, which is a `stdClass` object containing all of the +relevant information that cannot be calculated automatically. A file info record +may look like the following example: + +```php title="A sample file info record" +$fileinfo = [ + 'contextid' => $context->id, // ID of the context. + 'component' => 'mod_mymodule', // Your component name. + 'filearea' => 'myarea', // Usually = table name. + 'itemid' => 0, // Usually = ID of row in table. + 'filepath' => '/', // Any path beginning and ending in /. + 'filename' => 'myfile.txt', // Any filename. +]; +``` + +#### From a file on disk + +If you need to create a file from another file elsewhere on disk, for example a file you have downloaded into a temporary folder, you can use `create_file_from_pathname`. + +```php title="Create a file from a file on disk" +$fs = get_file_storage(); + +// Create a new file containing the text 'hello world'. +$fs->create_file_from_pathname($fileinfo, $requestdir . '/helloworld.txt'); +``` + +#### From a URL + +If you need to fetch a file from a downloadable resource and store it straight into the Moodle filestore, you can use the `create_file_from_url()` function: + +```php title="Create a file from a file on disk" +$fs = get_file_storage(); + +// Create a new file containing the text 'hello world'. +$fs->create_file_from_url($fileinfo, 'https://example.com/helloworld.txt'); +``` + +#### From a string + +In some cases you may need to create a file from a string that you have generated, or retrieved in some other manner. For example, a string created from a Template, or image data which has been automatically generated. + +```php title="Create a file from a string" +$fs = get_file_storage(); + +// Create a new file containing the text 'hello world'. +$fs->create_file_from_string($fileinfo, 'hello world'); +``` + +#### From another `stored_file` + +In some situations you may need to create a new file entry based on an existing file entry. You may need to do this when copying a file between users in a group activity. + +```php title="Create a file from an existing file" +$fs = get_file_storage(); + +// Create a new file containing the text 'hello world'. +$fs->create_file_from_storedfile($fileinfo, $existingfile); +``` + +### List all files in a particular file area + +You may need to fetch a list of all files in a specific file area. You can do this using the `file_storage::get_area_files()` API, which will return array of `stored_file` objects, for example: + +```php title="Fetch a list of all files in the file area" +$fs = get_file_storage(); + +// Returns an array of `stored_file` instances. +$files = $fs->get_area_files($contextid, 'mod_myplugin', 'post', $post->id); +foreach ($files as $file) { + // ... +} +``` + +### Access the content of a file + +In some rare situations you may need to use the content of a file stored in the file storage API. The easiest way of doing so is using the `get_content(): string` API call: + +```php title="Fetch the content of a file" +// Get file +$fs = get_file_storage(); +$file = $fs->get_file(...); + +// Read contents +$contents = $file->get_content(); +``` + +In some situations you may instead need a standard PHP file _handle_ to the file. You can retrieve a file handle resource using the `get_content_file_handle(): resource` API call, for example: + +```php title="Fetch a file handle for a file" +// Get file +$fs = get_file_storage(); +$file = $fs->get_file(...); + +// Read contents +$fh = $file->get_content_file_handle(); + +while($line = fgets($fh)) { + // ... +} + +fclose($fh); +``` + +:::important + +You *cannot* write to a file handle fetched using the `get_content_file_handle()` function and you _must_ call `fclose()` on the returned handle when you have finished using it. + +::: + +### Copy a file in the File Storage API to elsewhere on disk + +As the Moodle File Storage API prevents direct access to files on the disk, if you need a local copy of the file on disk, you must copy the file to a different location. You can use the `\stored_file::copy_content_to(string $destination);` function to achieved this, for example: + +```php title="Copy a file to disk" +// Get file +$fs = get_file_storage(); +$file = $fs->get_file(...); + +$requestdir = make_request_directory(); +$file->copy_content_to("{$requestdir}/helloworld.txt"); +``` + +### Delete file + +You can easily remove a file from the File Storage API using the `\stored_file::delete()` function, for example: + +```php +$fs = get_file_storage(); + +$file = $fs->get_file(...); +$file->delete(); +``` + +### Moving and renaming files around + +In some instances you may need to move, or copy, files to other parts of the file structure. + +If a file is moving within the same context, file area, and item id, then you can use the `rename(string $path, string $filename)` function, for example: + +```php title="Move a file within the same file area" +$file->rename('/newpath/', '/newname.txt'); +``` + +If a file is to be moved to a different context, file area, or item id then you will need to create a new file from the old record, and then remove the original file, for example: + +```php title="Move a file to a different file area" +$fs = get_file_storage(); +$filerecord = [ + 'contextid' => $file->get_contextid() + 'component' => $file->get_component(), + 'filearea' => 'newfilearea', + 'itemid' => 0, + 'filepath' => '/newpath/', + 'filename' => $file->get_filename(), + 'timecreated' => time(), + 'timemodified' => time(), +]; +$fs->create_file_from_storedfile($filerecord, $file); + +// Now delete the original file. +$file->delete(); +``` + +### Convert between file formats (office documents) + +This functionality requires `unoconv` to be installed and configured on the site - so it is not available on all installations. + +```php +$fs = get_file_storage(); + +// Prepare file record object +$fileinfo = [ + 'component' => 'mod_mymodule', // Your component name. + 'filearea' => 'myarea', // Usually = table name. + 'itemid' => 0, // Usually = ID of row in table. + 'contextid' => $context->id, // ID of context. + 'filepath' => '/', // Any path beginning and ending in /. + 'filename' => 'myfile.txt', // Any filename. + ]; + +// Fetch the file from the database. +$file = $fs->get_file( + $fileinfo['contextid'], + $fileinfo['component'], + $fileinfo['filearea'], + $fileinfo['itemid'], + $fileinfo['filepath'], + $fileinfo['filename'] +); + +// Try and convert it if it exists +if ($file) { + $convertedfile = $fs->get_converted_document($file, 'pdf'); +} +``` + +## See also + +- [Core APIs](../../../apis.md) +- [File API internals](./internals.md) how the File API works internally. +- [Using the File API in Moodle forms](https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms) +- [Repository API](../../plugintypes/repository/index.md) diff --git a/versioned_docs/version-4.1/apis/subsystems/files/internals.md b/versioned_docs/version-4.1/apis/subsystems/files/internals.md new file mode 100644 index 0000000000..4dc6dd223a --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/files/internals.md @@ -0,0 +1,207 @@ +--- +title: File API internals +tags: + - Architecture + - File API + - Files + - Internals +--- + +The goals of the File API are to: + +- allow files to be stored within Moodle, as part of the content +- use a consistent and flexible approach for all file handling throughout Moodle +- give components control over which users can access a file, using capabilities and other local rules +- make it easy to determine which parts of Moodle use which files, to simplify operations like backup and restore +- track where files originally came from +- avoid redundant storage, when the same file is used twice +- fully support Unicode file names, irrespective of the capabilities of the underlying file system +- support alternative file systems, including cloud-based APIs + +## Overview + +The File API is a set of core interfaces to allow the rest of Moodle to store, serve, and manage files. It applies to all files that are part of the Moodle site's content. It is not used for internal files, such as those in the following subdirectories: + +- `dataroot` +- `temp` +- `lang` +- `cache` +- `environment` +- `filter` +- `search` +- `sessions` +- `upgradelogs` + +See the [File API](./index.md) documentation for information on using the File API. + +The API can be subdivided into the following parts: + +- [*File system*](#file-system) - Low level storage of file content, without access control +- [*File storage*](#file-storage) - Storage of file metadata +- [*File serving*](#file-serving) - Handle the retrieval and serving of files from the File storage API, including: + - serving the files on request + - performing appropriate access checks +- *File related user interfaces* - Provides the interface for uploading files, including: + - Form elements allowing users to select a file using the file picker, and have it stored within Moodle. + - UI for users to manage their files, replacing the old course files UI +- *File browsing API* - Allow code to browse and optionally manipulate the file areas, including: + - find information about available files in each area + - print links to files + - optionally move, rename, copy, delete, and perform other user-facing operations. + +## File API internals + +### File System + +The File System API allows for files to be stored in alternative underlying file systems, for example in an cloud-based API such as [Amazon S3](https://aws.amazon.com/s3). Each file is stored and retrieved using a `contenthash`. + +The file content hash is calculated by taking a SHA1 hash of the content of the file. This should be unique enough so as to allow any number of files to be uploaded to the File API without any natural collisions occurring, and allows the File system to store a single copy of a file, no matter how many times that file content is used within user-generated content. + +This means Moodle can not store two files with _different_ content and the _same_ SHA1 hash, luckily it is extremely unlikely that this would ever happen. Technically it is also possible to implement reliable collision tests (with some performance cost), though Moodle currently checks for this case using a simple file length check in addition to SHA1 hash. + +:::info + +The default file system shipped with Moodle stores all files on disk within the `moodledata` sub-directory of `$CFG->dataroot`. + +Suppose a file has a content hash of `081371cb102fa559e81993fddc230c79205232ce`, then it will be stored in on disk as `moodledata/filedir/08/13/081371cb102fa559e81993fddc230c79205232ce`. + +::: + +:::tip Validation of files + +As files in the standard disk-based file storage API are named using their SHA1 hash, there is a simple way of validating files have not become corrupted using the 'sha1sum' command available in most GNU/Linux distributions. + +Where a file is correct then the filename will match the `sha1sum` of the file. for example: + +```console + $ cd /moodlepath/moodledata/filedir/1d/df/ + $ sha1sum 1ddf5b375fcb74929cdd7efda4f47efc61414edf + 1ddf5b375fcb74929cdd7efda4f47efc61414edf 1ddf5b375fcb74929cdd7efda4f47efc61414edf +``` + +Where a file has become corrupted, these will differ: + +```console + $ cd /moodlepath/moodledata/filedir/42/32/ + $ sha1sum 42327aac8ce5741f51f42be298fa63686fe81b7a + 9442188152c02f65267103d78167d122c87002cd 42327aac8ce5741f51f42be298fa63686fe81b7a +``` + +::: + +### File Storage + +The File Storage API is provided by the `\file_storage` class, and stores all metadata relating to a file. It interacts with the File System API and the `\stored_file` class to provide all low-level storage functionality. + +### Files table + +The File system API stores all file records in the `files` database table. This table contains one entry for each usage of a file. Enough information is kept here so that the file can be fully identified and retrieved again if necessary. + +If, for example, the same image is used in a user's profile, and a forum post, then there will be two rows in this table, one for each use of the file, and Moodle will treat the two as separate files, even though the file is only stored once on disc. + +Entries with a file name of `.`represent directories. Directory entries like this are created automatically when a file is added within them. + +:::note + +The name `files` is used in the plural form, even though it goes against the [coding guidelines](https://docs.moodle.org/dev/Database) because `file` is a reserved word in some SQL dialects. + +::: + +## Implementation of basic operations + +The low level access API is defined in the `\file_storage` class, which can be obtained using the `get_file_storage()` function, for example: + +```php +$fs = get_file_storage(); +``` + +Details of common operations are documented in the [File System API](./index.md) documentation + +## File serving + +The File serving component of the File API deals with serving files to the user. This is typically in the form of browser requests. Moodle has several main files to handle serving of files. These include: + +- `draftfile.php` - the script used to serve files in a user's `draft` file area. +- `pluginfile.php` - the script typically used by a plugin to access content. +- `tokenpluginfile.php` - the script typically used by a plugin to access content when a user is not logged in. This is usually in situations where a file is referred to in an e-mail or other similar scenario. + +It is the plugins responsibility to handle: + +- access control +- optional XSS protection - student submitted files must not be served with normal headers, we have to force download instead; ideally there should be second wwwroot for serving of untrusted files +- links to these files are constructed on the fly from the relative links stored in database (see [Generating a URL to your files](./index.md#generating-a-url-to-your-files) for further information). + +:::important + +Each plugin should only ever use the File Storage API to access its __own files__. + +::: + +## File related user interfaces + +Files are typically selected by users and uploaded to Moodle using the File manager, and the file picker. + +- The **file manager** is an interface used to view, and delete existing files, and to add new files. +- The **file picker** is an interface, often accessed using the _file manager_, to select files for upload to Moodle. The file picker makes use of [file repositories](../../plugintypes/repository/index.md). + +### Form fields + +Moodle defines two form field types as part of the `formslib` integration, these are: + +- The `filepicker`; and +- the `filemanager`. + +### Integration with the HTML editor + +Each instance of an HTML editor can be told to store related files in a particular file area. + +During editing, files are stored in a draft files area in the `user` component. Then when the form is submitted they are moved into the real file area. + +## Other issues + +### Unicode support in zip format + +Zip format is an old standard for compressing files. It was created long before Unicode existed, and Unicode support was only recently added. There are several ways used for encoding of non-ASCII characters in path names, but unfortunately it is not very standardised. Most Windows packers use DOS encoding. + +#### Client software + +- Windows built-in compression - bundled with Windows, non-standard DOS encoding only +- WinZip - shareware, Unicode option (since v11.2) +- TotalCommander - shareware, single byte(DOS) encoding only +- 7-Zip - free, Unicode or DOS encoding depending on characters used in file name (since v4.58beta) +- Info-ZIP - free, uses some weird character set conversions + +#### PHP extraction + +- Info-ZIP binary execution - no Unicode support at all, mangles character sets in file names (depends on OS, see docs), files must be copied to temp directory before compression and after extraction +- PclZip PHP library - reads single byte encoded names only, problems with random problems and higher memory usage. +- Zip PHP extension - kind of works in latest PHP versions + +#### Large file support + +- PHP running under 32bit operating systems does not support files >2GB. This might be a potential problem for larger backups. + +#### Tar Alternative + +- tar with gzip compression - easy to implement in PHP + zlib extension (PclTar, Tar from PEAR or custom code) +- no problem with unicode in *nix, Windows again expects DOS encoding :-( +- seems suitable for backup/restore - yay! + +#### Summary + +1. added zip processing class that fully hides the underlying library +1. using single byte encoding "garbage in/garbage out" approach for encoding of files in zip archives; add new `zipencoding` string into lang packs (for example `cp852` DOS charset for Czech locale) and use it during extraction (we might support true unicode later when PHP Zip extension does that) + +### Tar packer + +In addition to the zip packer, a tar packer is also available. This creates +archives in a compressed tar format (similar to the file created by `tar -czf example.tar.gz mycontent`). + +The packer is currently limited to ASCII filenames and individual files are limited to 8GB each, but unlike zip there is no limit on the total filesize. It uses the old POSIX format and is compatible with GNU tar using default options. + +## See also + +- [Repository API](../../plugintypes/repository/index.md) +- [Portfolio API](https://docs.moodle.org/dev/Portfolio_API) +- [Resource module file API migration](https://docs.moodle.org/dev/Resource_module_file_API_migration) +- [MDL-14589](https://tracker.moodle.org/browse/MDL-14589) - File API Meta issue diff --git a/versioned_docs/version-4.1/apis/subsystems/form/advanced/_category_.yml b/versioned_docs/version-4.1/apis/subsystems/form/advanced/_category_.yml new file mode 100644 index 0000000000..b640140c1b --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/advanced/_category_.yml @@ -0,0 +1 @@ +label: Advanced features diff --git a/versioned_docs/version-4.1/apis/subsystems/form/advanced/advanced-elements.md b/versioned_docs/version-4.1/apis/subsystems/form/advanced/advanced-elements.md new file mode 100644 index 0000000000..88d7817303 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/advanced/advanced-elements.md @@ -0,0 +1,96 @@ +--- +title: Advanced elements +tags: + - core_form + - core + - Forms API + - Advanced +--- + +import { ValidExample, InvalidExample } from '@site/src/components'; + +Form elements can be marked as 'advanced'. This has the effect that they are hidden initially. + +To control whether an element is advanced, you can use the `setAdvanced` method on the MoodleQuickForm instance and set a specific element as being advanced, for example: + +```php title="Marking an element as advanced" +$mform->addElement( + 'select', + 'display', + get_string('displaymode', 'choice'), + $CHOICE_DISPLAY +); +$mform->setAdvanced('display'); +``` + +It is also possible to unset the advanced status of a field - the `setAdvanced` function takes a second, boolean, parameter which defaults to `true`. By passing a `false` value, the element's advanced flag will be removed, for example: + +```php title="Marking an element as advanced" +// Mark a field as not advanced after it was previously marked as advanced. +$mform->setAdvanced('display', false); +``` + +:::warning + +You should be careful about marking too many elements as advanced. + +For more information on the risks of this, see the advice in [Designing usable forms](/general/development/policies/designing-usable-forms#use-show-moreless-advanced-settings-sparingly). + +::: + +:::info Location of Show and hide links + +Whenever you mark a form element as advanced, then the _Show / hide advanced_ links are shown automatically at relevant points within the form. + +The _Show advanced_ and _Hide advanced_ links are currently displayed at the top right of all fieldsets containing advanced controls. + +::: + +### Setting a name + +When adding a header element, the second parameter to `addElement()` is a name field. You should pass a _unique_ name for each header. + +If the name is not specified, or is not unique then this can have a range of unintended impacts, including marking multiple sections of the form as advanced. It is strongly encouraged to _always_ name headers. + + + +```php title="An empty name is passed to these headings" +$mform->addElement( + 'header', + '', + get_string('miscellaneoussettings', 'form') +); +$mform->addElement( + 'select', + 'display', + get_string('displaymode', 'choice'), + $CHOICE_DISPLAY +); +$mform->setAdvanced('display'); + +// Because this section has a non-unique name (an empty string), +// the advanced flag set for the display element in the previous +// section will also apply here. +$mform->addElement( + 'header', + '', + get_string('anothersection', 'form') +); +``` + + + +## Marking an entire section as advanced + +The `setAdvanced` function can mark an entire section as advanced by specifying the name of the header at the top of the section, for example: + +```php title="Marking an entire section as advanced" +$mform->addElement( + 'header', + 'miscellaneoussettingshdr', + get_string('miscellaneoussettings', 'form') +); +$mform->setAdvanced('miscellaneoussettingshdr'); +``` + +In this example, all fields from this header until the next header will be marked as advanced. diff --git a/versioned_docs/version-4.1/apis/subsystems/form/advanced/checkbox-controller.md b/versioned_docs/version-4.1/apis/subsystems/form/advanced/checkbox-controller.md new file mode 100644 index 0000000000..f9890a3e58 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/advanced/checkbox-controller.md @@ -0,0 +1,74 @@ +--- +title: Checkbox controller +tags: + - core_form + - core + - Forms API + - Advanced +--- + +The checkbox controller allows developers to group checkboxes together and add a link or button to check, or uncheck, all of the checkboxes at once. + +You can add as many groups of checkboxes as you like, as long as they are uniquely named, where the name is an integer. + +## Checkbox groups + +When adding checkboxes, you can add them in _groups_. Each group of checkboxes must have a unique integer name, for example: + +```php title="classes/form/example_form.php" +public function definition(): void { + // These two elements are part of group 1. + $mform->addElement('advcheckbox', 'test1', 'Test 1', null, ['group' => 1]); + $mform->addElement('advcheckbox', 'test2', 'Test 2', null, ['group' => 1]); + + // Add a checkbox controller for all checkboxes in `group => 1`: + $this->add_checkbox_controller(1); + + // These two elements are part of group 3. + $mform->addElement('advcheckbox', 'test3', 'Test 3', null, ['group' => 3]); + $mform->addElement('advcheckbox', 'test4', 'Test 4', null, ['group' => 3]); + + // Add a checkbox controller for all checkboxes in `group => 3`. + // This example uses a different wording isntead of Select all/none by passing the second parameter: + $this->add_checkbox_controller( + 3, + get_string("checkall", "plugintype_pluginname") + ); +} +``` + +## API + +The checkbox controller is described by the following mform function: + +```php +moodleform::add_checkbox_controller( + $groupid = null, + string $text = null, + mixed $attributes = null, + int $originalvalue +): void; +``` + +- int *$groupid* This also serves as the checkbox group name. It must be a unique integer per collection of checkboxes. +- string *$text* (optional) Link display text. Defaults to `get_string('selectallornone', 'form')`. +- mixed *$attributes* (optional) Either a typical HTML attribute string or an associative array. +- int *$originalValue* (optional) Defaults to 0; The general original value of the checkboxes being controlled by this element. + +:::info An explanation of `$originalvalue` + +Imagine that you have 50 checkboxes in your form, which are all unchecked when the form first loads, except 5 or 6 of them. + +The logical choice here would be for the standard behaviour to check all of the checkboxes upon first click, then to uncheck them all upon the next click and so on. + +If the situation was reversed, with most of the checkboxes already checked by default, then it would make more sense to have the first action uncheck all the checkboxes. + +The `$originalvalue` parameter allows you to configure the initial value and therefore the initial behaviour. + +::: + +### Description of functionality + +The first role of the `add_checkbox_controller` method is to add a form element. Depending on whether JavaScript is supported by the browser or not, it will either output a link with onclick behaviour for instant action, or a `nosubmit` button which will reload the page and change the state of the checkboxes, but retain the rest of the data already filled in the form by the user. + +The second role is to change the state of the checkboxes. The JavaScript version simply switches all checkboxes to checked or unchecked. The state applied when the link is first clicked depends on the `$originalvalue` parameter as noted above. The non-JavaScript version behaves in exactly the same way, although a page reload is necessary. diff --git a/versioned_docs/version-4.1/apis/subsystems/form/advanced/no-submit-button.md b/versioned_docs/version-4.1/apis/subsystems/form/advanced/no-submit-button.md new file mode 100644 index 0000000000..2744b69b20 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/advanced/no-submit-button.md @@ -0,0 +1,54 @@ +--- +title: No submit button +tags: + - core_form + - core + - Forms API + - Advanced +--- + +The moodleform 'no_submit_button_pressed()' method allows you to detect if a button on your form has been pressed that is a submit button but that has been defined as a button that doesn't result in a processing of all the form data but will result in some form 'sub action' and then having the form redisplayed. This is useful for example to have an 'Add' button to add some option to a select box in the form etc. You define a button as a no submit button as in the example below (in `definition()`). This example adds a text box and a submit button in a group. + +## Form definition + +When defining your form, you will need to call the `registerNoSubmitButton()` function with the name of the submit button mark as a non-submission button, for example: + +```php +$mform->registerNoSubmitButton('addtags'); +$tags = [ + $mform->createElement('text', 'tagsadd', get_string('addtags', 'blog')), + $mform->createElement('submit', 'addtags', get_string('add')), +]; +$mform->addGroup($tags, 'tagsgroup', get_string('addtags','blog'), [' '], false); +$mform->setType('tagsadd', PARAM_NOTAGS); +``` + +## Form handling + +When handling a no-submit button press you will need to check whether any no-submit button as pressed _before_ checking for submitted data. For example: + +```php +$mform = new \plugintype_pluginname\form\my_form(); +$mform->set_data($toform); + +if ($mform->is_cancelled()){ + // If you have a cancel button on your form then you will need + // to handle the cancel action here according to the + // requirements of your plugin +} else if ($mform->no_submit_button_pressed()) { + // If you have a no-submit button on your form, then you can handle that action here. + $data = $mform->get_submitted_data(); +} else if ($fromform = $mform->data_submitted()){ + // This branch is where you process validated data. +} else { + // The form has not been submitted, or was unsuccessfully submitted. +} +``` + +:::important Modifying your form in a no-submit action + +Because the check for a no-submit button press takes place on the defined form, the form is already defined at the point that it is checked. + +If you wish to add or modify elements or options in your form, then may need to use methods on MoodleQuickForm to redefine your form elements. + +::: diff --git a/versioned_docs/version-4.1/apis/subsystems/form/advanced/repeat-elements.md b/versioned_docs/version-4.1/apis/subsystems/form/advanced/repeat-elements.md new file mode 100644 index 0000000000..0a65c88aad --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/advanced/repeat-elements.md @@ -0,0 +1,106 @@ +--- +title: Repeat elements +tags: + - core_form + - core + - Forms API + - Advanced +--- + +The Form API includes the ability to repeat a group of form elements. This is useful where you need to have an unknown quantity of item data, for example possible answers in a quiz question. + +This is achieved by adding a button to the form to handle the creation of the additional buttons using a page reload, and a zero-indexed array added to the `elementname` data returned by `get_data()`. + +## Overview + +Most of the necessary information is in the phpdoc comment for the repeat_elements() method: + +```php +/** + * Method to add a repeating group of elements to a form. + * + * @param array $elementobjs Array of elements or groups of elements that are to be repeated + * @param int $repeats no of times to repeat elements initially + * @param array $options a nested array. The first array key is the element name. + * the second array key is the type of option to set, and depend on that option, + * the value takes different forms. + * 'default' - default value to set. Can include '{no}' which is replaced by the repeat number. + * 'type' - PARAM_* type. + * 'helpbutton' - array containing the helpbutton params. + * 'disabledif' - array containing the disabledIf() arguments after the element name. + * 'rule' - array containing the addRule arguments after the element name. + * 'expanded' - whether this section of the form should be expanded by default. (Name be a header element.) + * 'advanced' - whether this element is hidden by 'Show more ...'. + * @param string $repeathiddenname name for hidden element storing no of repeats in this form + * @param string $addfieldsname name for button to add more fields + * @param int $addfieldsno how many fields to add at a time + * @param string $addstring name of button, {no} is replaced by no of blanks that will be added. + * @param bool $addbuttoninside if true, don't call closeHeaderBefore($addfieldsname). Default false. + * @return int no of repeats of element in this page + */ +``` + +## Configuration + +- The list of elements you wish to repeat is set in the `$elementobjs` array, with any options passed into the `$options` array. +A `{no}` placeholder can be placed into strings, such as the element label or default values, to represent the item number. + +:::note + +While the elements are zero-indexed, the `{no}` label is one-indexed. + +::: + +- The number of repeats to show initially can be configured using the `$repeats` parameter. +- The number of elements to add when adding more options is configured using the `$addfieldsno` parameter. +- The label used for the 'Add more' button can be set using the `$addstring` parameter. A `{no}` placeholder can be used in the string to indicate how many repeats will be added. +- The number of element repeats currently shown is stored in a hidden element, whose name can be specified using the `$repeathiddenname` parameter. + +The following example shows how `repeat_elements()` can be used within a form definition: + +```php title="definition() function" +$repeatarray = [ + $mform->createElement('text', 'option', get_string('optionno', 'choice')), + $mform->createElement('text', 'limit', get_string('limitno', 'choice')), + $mform->createElement('hidden', 'optionid', 0), +]; + + +if ($this->_instance){ + $repeatno = $DB->count_records('choice_options', ['choiceid' => $this->_instance]); + $repeatno += 2; +} else { + $repeatno = 5; +} + +$repeateloptions = [ + 'limit' => [ + 'default' => 0, + 'disabledif' => array('limitanswers', 'eq', 0), + 'rule' => 'numeric', + 'type' => PARAM_INT, + ], + 'option' => [ + 'helpbutton' => [ + 'choiceoptions', + 'choce', + ] + ] +]; + +$mform->setType('option', PARAM_CLEANHTML); +$mform->setType('optionid', PARAM_INT); + +$this->repeat_elements( + $repeatarray, + $repeatno, + $repeateloptions, + 'option_repeats', + 'option_add_fields', + 3, + null, + true +); +``` + +For other examples, have a look at the question type editing forms. They make extensive use of repeat_elements(). diff --git a/versioned_docs/version-4.1/apis/subsystems/form/index.md b/versioned_docs/version-4.1/apis/subsystems/form/index.md new file mode 100644 index 0000000000..721affb135 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/index.md @@ -0,0 +1,333 @@ +--- +title: Forms API +tags: + - API + - core_form + - form + - core +--- + +Form are created using the Form API. The Form API supports most standard HTML elements, including checkboxes, radio buttons, text boxes, and so on, adding additional accessibility and security features to them. + +## Highlights + +- Tested and optimised for use on major screen-readers like Dragon and JAWS. +- Table-less layout. +- Processes form data securely, with `required_param`, `optional_param`, and session key. +- Supports client-side validation. +- Facility to add Moodle help buttons to forms. +- Support for file repository using the [File API](../files/index.md) . +- Support for many custom Moodle specific and non-specific form elements. +- Facility for [repeated elements](./advanced/repeat-elements.md). +- Facility for form elements in advanced groups + +## Usage + +The Moodle forms API separates forms into different areas: + +1. a form definition, which extends the `moodleform` class; and +2. uses of that form. + +To create a form in Moodle, you create a class that defines the form, including every form element. Your class must extend the `moodleform` class and overrides the [definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#definition.28.29) member function to describe the form elements. + +
+An example of a form definition + +```php title="[path/to/plugin]/classes/form/myform.php" +// moodleform is defined in formslib.php +require_once("$CFG->libdir/formslib.php"); + +class simplehtml_form extends moodleform { + // Add elements to form. + public function definition() { + // A reference to the form is stored in $this->form. + // A common convention is to store it in a variable, such as `$mform`. + $mform = $this->_form; // Don't forget the underscore! + + // Add elements to your form. + $mform->addElement('text', 'email', get_string('email')); + + // Set type of element. + $mform->setType('email', PARAM_NOTAGS); + + // Default value. + $mform->setDefault('email', 'Please enter email'); + } + + // Custom validation should be added here. + function validation($data, $files) { + return []; + } +} +``` + +
+ +Once the form has been defined it can be instantiated elsewhere in Moodle, for example: + +```php title="[path/to/plugin]/myform.php + +// Instantiate the myform form from within the plugin. +$mform = new \plugintype_pluginname\form\myform(); + +// Form processing and displaying is done here. +if ($mform->is_cancelled()) { + // If there is a cancel element on the form, and it was pressed, + // then the `is_cancelled()` function will return true. + // You can handle the cancel operation here. +} else if ($fromform = $mform->get_data()) { + // When the form is submitted, and the data is successfully validated, + // the `get_data()` function will return the data posted in the form. +} else { + // This branch is executed if the form is submitted but the data doesn't + // validate and the form should be redisplayed or on the first display of the form. + + // Set anydefault data (if any). + $mform->set_data($toform); + + // Display the form. + $mform->display(); +} +``` + +If you wish to use the form within a block then you should consider using the render method, as demonstrated below: + +Note that the render method does the same as the display method, except returning the HTML rather than outputting it to the browser, as with above make sure you've included the file which contains the class for your Moodle form. + +```php +class block_yourblock extends block_base { + public function init(){ + $this->title = 'Your Block'; + } + + public function get_content(){ + $this->content = (object) [ + 'text' => '', + ]; + + $mform = new \plugintype_pluginname\form\myform(); + + if ($mform->is_cancelled()) { + // If there is a cancel element on the form, and it was pressed, + // then the `is_cancelled()` function will return true. + // You can handle the cancel operation here. + } else if ($fromform = $mform->get_data()) { + // When the form is submitted, and the data is successfully validated, + // the `get_data()` function will return the data posted in the form. + } else { + // This branch is executed if the form is submitted but the data doesn't + // validate and the form should be redisplayed or on the first display of the form. + + // Set anydefault data (if any). + $mform->set_data($toform); + + // Display the form. + $this->content->text = $mform->render(); + } + + return $this->content; + } +} +``` + +## Form elements + +Moodle provides a number of basic, and advanced, form elements. These are described in more detail below. + +### Basic form elements + +1. [button](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#button) +1. [checkbox](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#checkbox) +1. [radio](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#radio) +1. [select](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#select) +1. [multi-select](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#multi-select) +1. [password](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#password) +1. [hidden](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hidden) +1. [html](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#html) - div element +1. [static](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#static) - Display a static text. +1. [text](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text) +1. [textarea](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#textarea) +1. [header](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements) + +### Advanced form elements + + + + + +1. [Autocomplete](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#autocomplete) - A select box that allows you to start typing to narrow the list of options, or search for results. +1. [advcheckbox](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#advcheckbox) - Advance checkbox +1. [float](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#float) +1. [passwordunmask](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#passwordunmask) - A password element with option to show the password in plaintext. +1. [recaptcha](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#recaptcha) +1. [selectyesno](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectyesno) +1. [selectwithlink](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectwithlink) +1. [date_selector](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_selector) +1. [date_time_selector](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_time_selector) +1. [duration](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#duration) +1. [editor](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#editor) +1. [filepicker](https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filepicker) - upload single file +1. [filemanager](https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filemanager) - upload multiple files +1. [tags](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#tags) +1. [addGroup](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addGroup) +1. [modgrade](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modgrade) +1. [modvisible](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modvisible) +1. [choosecoursefile](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#choosecoursefile) +1. [grading](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#grading) +1. [questioncategory](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#questioncategory) + +### Custom form elements + +In addition to the standard form elements, you can register your own custom form elements, for example: + +```php +// Register a custom form element. +MoodleQuickForm::registerElementType( + // The custom element is named `course_competency_rule`. + // This is the element name used in the `addElement()` function. + 'course_competency_rule', + + // This is where it's definition is defined. + // This does not currently support class auto-loading. + "$CFG->dirroot/$CFG->admin/tool/lp/classes/course_competency_rule_form_element.php", + + // The class name of the element. + 'tool_lp_course_competency_rule_form_element' +); + +// Add an instance of the custom form element to your form. +$mform->addElement( + // The name of the custome lement. + 'course_competency_rule', + 'competency_rule', + get_string('uponcoursemodulecompletion', 'tool_lp'), + $options +); +``` + +For a real-life example, see: + +- [Custom element definition](https://github.com/moodle/moodle/blob/master/admin/tool/lp/classes/course_competency_rule_form_element.php) +- [Custom element usage](https://github.com/moodle/moodle/blob/master/admin/tool/lp/lib.php#L157-L161) + +## Commonly used functions + +### add_action_buttons() + +Add the standard 'action' buttons to the form - these are the standard Submit, and Cancel buttons on the form. + +```php +public function add_action_buttons( + bool $cancel = true, + ?string $submitlabel = null +); +``` + +- The `$cancel` parameter can be used to control whether a cancel button is shown. +- The `$submitlabel` parameter can be used to set the label for the submit button. The default value comes from the `savechanges` string. + +:::important + +The `add_action_buttons` function is defined on `moodlform` class, and not a part of `$this->_form`, for example: + +```php + public function definition() { + // Add your form elements here. + $this->_form->addElement(...); + + // When ready, add your action buttons. + $this->add_action_buttons(); + } +``` + +::: + +### setDefault() + +The [setDefault()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#setDefault_2) function can be used to set the default value for an element. + +### disabledIf() + +The [disabledIf()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#disabledIf) function can be used to conditionally _disable_ a group of elements, or and individual element depending on the state of other form elements. + +### hideIf() + +import { Since } from '@site/src/components'; + + + +The [hideif()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hideIf) function can be used to conditionally _hide_ a group of elements, or and individual element depending on the state of other form elements. + +### addRule() + +The [addRule()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addRule) function can be used to define both client-side, and server-side validation rules. For example, this can be used to validate that a text-field is required, and has a type of email. + +### addHelpButton() + +The [addHelpButton()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addHelpButton) function can be used to add a pop-up help button to a form element. + +### setType() + +The [setType()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#setType) function can be used to specify how submitted values are cleaned. The `PARAM_*` constants are used to specify the type of data that will be submitted. + +### disable_form_change_checker() + +Normally, if a user navigate away from any form and changes have been made, a popup will be shown to the user asking them to confirm that they wish to leave the page and that they may lose data. + +In some cases this is not the desired behaviour, in which case the [disable_form_change_checker()](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#disable_form_change_checker) function can be used to disable the form change checker. + +For example: + +```php +public function definition() { + // Your definition goes here. + + // Disable the form change checker for this form. + $this->_form->disable_form_change_checker(); +} +``` + +### Other features + +In some cases you may want to [group elements](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements) into collections. + +## Unit testing + +In order to test the processing of submitted form contents in unit tests, the Forms API has a `mock_submit()` function. + +This method makes the form behave as if the data supplied to it was submitted by the user via the web interface. The data still passes through all form validation, which means that `get_data() will return all of the parsed values, along with any defaults. + +
+Example usage + +```php +// Instantiate a form to submit. +$form = new qtype_multichoice_edit_form(...); + +// Fetch the data and then mock the submission of that data. +$questiondata = test_question_maker::get_question_data('multichoice', 'single'); +$form->mock_submit($questiondata); + +// The `get_data()` function will return the validated data, plus any defaults. +$actualfromform = $form->get_data(); + +// The resultant data can now be tested against the expected values. +$expectedfromform = test_question_maker::get_question_form_data('multichoice', 'single'); +$this->assertEquals($expectedfromform, $actualfromform); + +// The data can also be saved and tested in the context of the API. +save_question($actualfromform); +$actualquestiondata = question_load_questions(array($actualfromform->id)); +$this->assertEquals($questiondata, $actualquestiondata); +``` + +
+ +## See also + +- [Core APIs](../../../apis.md) +- [lib/formslib.php Usage](./usage.md) +- [lib/formslib.php Form Definition](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) +- [Designing usable forms](/general/development/policies/designing-usable-forms) +- [Fragment](https://docs.moodle.org/dev/Fragment) +- [MForm Modal](https://docs.moodle.org/dev/MForm_Modal) diff --git a/versioned_docs/version-4.1/apis/subsystems/form/usage.md b/versioned_docs/version-4.1/apis/subsystems/form/usage.md new file mode 100644 index 0000000000..2c4a0124b0 --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/form/usage.md @@ -0,0 +1,142 @@ +--- +title: Form Usage +tags: + - core_form + - form + - core + - API +documentationDraft: true +--- + +Moodle's Form API is an extension of the Pear HTML_QuickForm API, which is no longer supported. Some documentation for the upstream library is [available in the PEAR package page](http://pear.php.net/package/HTML_QuickForm), including a [short tutorial](http://pear.php.net/manual/en/package.html.html-quickform.tutorial.php). A [longer tutorial is also available](http://web.archive.org/web/20130630141100/http://www.midnighthax.com/quickform.php), courtesy of the Internet Archive. + +Moodle will attempt to provide a more complete tutorial in this documentation where possible. + +:::tip + +Some good examples of usage of the Forms API can be found at the following locations: + +- [Course edit form - definition](https://github.com/moodle/moodle/blob/master/course/edit_form.php) +- [Course edit form - usage](https://github.com/moodle/moodle/blob/master/course/edit.php) + +::: + +Whilst much of the API originates in the PEAR package, all interaction with the library should be via the `moodleform` class, which acts as a controlling wrapper to HTML_QuickForm. + +## Basic Usage in A Normal Page + +Generally the structure of a page with a form on it looks like this: + +```php +// You will process some page parameters at the top here and get the info about +// what instance of your module and what course you're in etc. Make sure you +// include hidden variable in your forms which have their defaults set in set_data +// which pass these variables from page to page. + +// Setup $PAGE here. + +// Instantiate the form that you defined. +$mform = new \plugintype_pluginname\form\myform(); +// Default 'action' for form is strip_querystring(qualified_me()). + +// Set the initial values, for example the existing data loaded from the database. +// This is typically an array of name/value pairs that match the +// names of data elements in the form. +// You can also use an object. +$mform->set_data($toform); + +if ($mform->is_cancelled()) { + // You need this section if you have a cancel button on your form. + // You use this section to handle what to do if your user presses the cancel button. + // This is often a redirect back to an older page. + // NOTE: is_cancelled() should be called before get_data(). + redirect($returnurl); + +} else if ($fromform = $mform->get_data()) { + // This branch is where you process validated data. + + // Typically you finish up by redirecting to somewhere where the user + // can see what they did. + redirect($nexturl); +} + +// If the formw as not cancelled, and data was not submitted, then display the form. +echo $OUTPUT->header(); +$mform->display(); +echo $OUTPUT->footer(); +``` + +You are encouraged to look at `lib/formslib.php` to see what additional functions and parameters are available. Available functions are well commented. + +## Defining Your Form Class + +The form class tells us about the structure of the form. + +In most cases you can place this in an auto-loadable class, in which case it should be placed in a folder named `form`, for example: + +```php title="mod/forum/classes/form/myform.php +_form->addElement( ... ); + + // Add the standard elements. + $this->standard_coursemodule_elements(); + + // Add the form actions. + $this->add_action_buttons(); + } +} +``` diff --git a/versioned_docs/version-4.1/apis/subsystems/group/index.md b/versioned_docs/version-4.1/apis/subsystems/group/index.md new file mode 100644 index 0000000000..c57a50d64d --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/group/index.md @@ -0,0 +1,140 @@ +--- +title: Groups API +tags: + - API + - Subsystem + - group + - grouping + - course +documentationDraft: true +--- + +Moodle [Groups](https://docs.moodle.org/en/Groups) are a way of expressing collections of users within a course. They may be defined by the teacher in the course participants page, or created automatically during a bulk user upload (for example, from a text file). + +A teacher can choose whether to use, or even to force, the use of groups for an entire course (from within the Course settings page), or for an individual activity (from within the Activity settings). + +Groups can be used in different modes: + +- None - groups are not used +- Separate - users can only see and interact with users in their own group +- Visible - users can see a list of the other groups and, depending on the activity, may be able to interact with them + +If enabled at the course level, the group mode will affect how course-wide reporting functions - for example, if a course-wide group mode of "Separate groups" is enabled, this is applied within the gradebook. + +Groups may be grouped together into named [Groupings](https://docs.moodle.org/en/Groupings). The course, and individual activities, can be configured to filter the groups shown to those in a specific Grouping. If a user is a member of multiple groups, then only those groups which belong to the selected grouping are shown. + +When a course or activity is in the 'Separate' groups mode, only users within that group can interact with, unless they hold the `moodle/site:accessallgroups` capability. By default, this capability is given to users who hold a role with the `editingteacher`, and `manager` archetypes. + +Most of these settings are handled by the core groups code and core groups API. If the activity module wants to implement group support, it need only use the Groups API to: + +- Find out the current settings for this instance of the activity +- Show group controls (for example group selection menus) when appropriate +- Explore memberships and structures of groups +- Modify it's own interface to hide/show data accordingly + +:::note + +Groups are typically only relevant to course features such as Activity modules, some blocks and reports. + +Some other core subsystems also need to be group-aware. + +::: + +## Group modes + +There are three different group modes, these modes allow for restrictions to be put in place for access and visibility. + +- No groups (`NOGROUPS` constant) - The course or activity has no groups. +- Separate groups (`SEPARATEGROUPS` constant) - Teachers and students can normally only see information relevant to that group. +- Visible groups (`VISIBLEGROUPS` constant) - Teachers and students are separated into groups but can still see all information. + +This is explained in more detail on the [Groups access control](https://docs.moodle.org/dev/Groups_access_control) page. + +## File locations + +The Groups API is currently defined in [lib/grouplib.php](https://github.com/moodle/moodle/blob/master/lib/grouplib.php). This contains global functions which have the `groups_` prefix, for example: `groups_get_group()`. + +## Examples + +### How to find and use the "current" group + +This is using an example from the module forums. + +```php +// Get the course module id from a post or get request. +$id = required_param('id', PARAM_INT); + +// Get the course module. +$cm = get_coursemodule_from_id('forum', $id, 0, false, MUST_EXIST) + +// Get the current group id. +$currentgroupid = groups_get_activity_group($cm); +// Get the current group name from the group id. +$currentgroupname = groups_get_group_name($currentgroupid); + +// Do as you please with your newly obtained group information. +``` + +### How to make sure that the current user can see a given item in your activity + +The following example: + +- fetches the course module record for the specified forum id +- checks whether it has a group mode specified (separate or visible groups) +- fetches the list of possible groups for that activity +- checks whether the forum discussion is in a valid group + +For this example we are going to check to see if groups are active and whether the user has access to the discussion. + +```php +// Get the course module and discussion id from a post or get request. +$id = required_param('id', PARAM_INT); +$discussionid = required_param('discussionid', PARAM_INT); + +// Get the course module. +$cm = get_coursemodule_from_id('forum', $id, 0, false, MUST_EXIST); + +// Get the group id for this discussion +$discussiongroup = $DB->get_record('forum_discussions', ['id' => $discussionid], groupid); + +// Check access. +if (groups_get_activity_groupmode($cm)) { + $groups = groups_get_activity_allowed_groups($cm); +} else { + $groups = []; +} +if (!in_array($discussiongroup->groupid, array_keys($groups))) { + print_error('groupnotamember', 'group'); +} + +// Continue on with group specific discussion +``` + +### How to display a group menu + +The following example will display the group selection dropdown using the `groups_print_activity_menu()` function. + +This function will show all groups that the user has access to for the current course module. + +After making a selection, the user will be redirected to the `$url` provided. + +```php +// Get the course module id from a post or get request +$id = required_param('id', PARAM_INT); + +// Get the course module +$cm = get_coursemodule_from_id('forum', $id, 0, false, MUST_EXIST); + +// Create a moodle_url. A URL is required so that if the user selects a different group, the page can be +// reloaded with the new groups information. +$url = new moodle_url('/mod/forum/view.php', ['id' => $cm->id]); + +// Print group information (A drop down box will be displayed if the user is a member of more than one group, +// or has access to all groups). +groups_print_activity_menu($cm, $url); +``` + +## Further reading + +- [Groups FAQ](https://docs.moodle.org/en/Groups_FAQ) +- [Groupings FAQ](https://docs.moodle.org/en/Groupings_FAQ) diff --git a/versioned_docs/version-4.1/apis/subsystems/output.md b/versioned_docs/version-4.1/apis/subsystems/output.md new file mode 100644 index 0000000000..42c7150bef --- /dev/null +++ b/versioned_docs/version-4.1/apis/subsystems/output.md @@ -0,0 +1,279 @@ +--- +title: Output API +tags: + - Output + - API +--- + + + +The Output API is responsible for visual aspects of Moodle content. This page explains how renderers, renderables, themes and templates all work together. + +## Page Output Journey + +Let's start with building a page that is part of an admin tool. + +```php title="/admin/tool/demo/index.php" +libdir.'/adminlib.php'); + +admin_externalpage_setup('tooldemo'); + +// Set up the page. +$title = get_string('pluginname', 'tool_demo'); +$pagetitle = $title; +$url = new moodle_url("/admin/tool/demo/index.php"); +$PAGE->set_url($url); +$PAGE->set_title($title); +$PAGE->set_heading($title); + +$output = $PAGE->get_renderer('tool_demo'); + +echo $output->header(); +echo $output->heading($pagetitle); + +$renderable = new \tool_demo\output\index_page('Some text'); +echo $output->render($renderable); + +echo $output->footer(); +``` + +:::info Setup of an admin page + +On admin pages, the `admin_externalpage_setup($sectionname);` function should be called to set up and perform permission checks, for example: + +```php title="admin/tool/demo/mypage.php" +require_once(__DIR__ . '/../../config.php'); +require_once("{$CFG->libdir}/adminlib.php"); +admin_externalpage_setup('example'); +``` + +::: + +Firstly, we set some general `$PAGE` properties. We load the title of the page from a language string (see [String API](https://docs.moodle.org/dev/String_API)). + +```php +// Set up the page. +$title = get_string('pluginname', 'tool_demo'); +$pagetitle = $title; +$url = new moodle_url("/admin/tool/demo/index.php"); +$PAGE->set_url($url); +$PAGE->set_title($title); +$PAGE->set_heading($title); +``` + +:::note What is $PAGE and where did it come from ? + +`$PAGE` is a global variable used to track the state of the page that is being returned. It is an instance of the `moodle_page` class defined in `lib/pagelib.php`. See [Page API](https://docs.moodle.org/dev/Page_API) for more information on the `$PAGE` variable. + +::: + +The most important properties stored in `$PAGE` are the page context, URL, layout, title and headings. `$PAGE` also gives access to some other important classes such as `$PAGE->requires`, which is an instance of the `page_requirements_manager` (`lib/outputrequirementslib.php`). The `page_requirements_manager` class lets us set dependencies on e.g. JavaScript and CSS to be inserted in the correct place in the page (The order in which things are inserted into the page is hugely important for performance). + +`$PAGE` also lets us load specific renderers for a plugin, or plugin and subtype. We will cover renderers in more detail next. + +```php +$output = $PAGE->get_renderer('tool_demo'); +``` + +This gets an instance of the `plugin_renderer_base` class that we use to create all output for our page. Themers can subclass this renderer to override specific render methods in order to customise Moodle's output. See [Output renderers](https://docs.moodle.org/dev/Output_renderers) for more information, and [Overriding a renderer](https://docs.moodle.org/dev/Overriding_a_renderer) for information about how themers can customise a renderer. + +:::important + +Some pages use the global variable `$OUTPUT` to generate their output. This is a generic renderer used for core pages etc, but plugins should always use a more specific plugin renderer. + +::: + +```php +echo $output->header(); +echo $output->heading($pagetitle); +``` + +This code prints the header of the page and adds one heading to the page at the top of the content region. Page headings are very important in Moodle and should be applied consistently. See [HTML Guidelines](https://docs.moodle.org/dev/HTML_Guidelines) for more information on how and where to use headings. + +```php +$renderable = new \tool_demo\output\index_page('Some text'); +echo $output->render($renderable); +``` + +This is the most interesting part of our page. We are creating an instance of a renderable and telling our renderer to render it. The renderable is usually more complex than this. It should hold all the data required for the renderer to display the page. This means we should perform all our logic such as database queries, page parameters and access checks in advance then pass the results as data to the renderable. The renderable then just takes that data and returns an HTML representation of it. + +```php +echo $output->footer(); +``` + +This prints the HTML for the bottom of the page. It is very important because it also prints out things that were added to the `page_requirements_manager` and that need to be printed in the footer; things like JavaScript includes, navigation tree setup, closing open containers tags etc. The reason all JavaScripts are added to the footer of the page is for performance. If you add JavaScript includes to the top of the page, or inline with the content, the browser must stop and execute the JavaScript before it can render the page. See https://developers.google.com/speed/docs/insights/BlockingJS for more information. + +### Renderable + +In the code above, we created a renderable. This is a class that you have to add to your plugin. It holds all the data required to display something on the page. Here is the renderable for this example: + +```php title="/admin/tool/demo/classes/output/index_page.php" +sometext = $sometext; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + $data = new stdClass(); + $data->sometext = $this->sometext; + return $data; + } +} +``` + +This class implements the renderable interface, which has no methods, and the templatable interface, which means that this class could be rendered with a template, so it must implement the `export_for_template` method. So in this example, the class accepts data via it's constructor, and stores that data in class variables. It does nothing else fancy with the data in this example (but it could). Note that the `export_for_template` function should only return simple types (arrays, stdClass, bool, int, float, string). + +Now let's look at the renderer for this plugin. + +```php title="admin/tool/demo/classes/output/renderer.php" +export_for_template($this); + return parent::render_from_template('tool_demo/index_page', $data); + } +} +``` + +The renderer exists to provide `render_` methods for all renderables used in the plugin. A theme designer can provide a custom version of this renderer that changes the behaviour of any of the render methods and so to customize their theme. In this example, the render method for the index page (`render_index_page`) does 2 things. It asks the renderable to export it's data so that it is suitable for passing as the context to a template, and then renders a specific template with this context. A theme designer could either manipulate the data in the render method (e.g. removing menu entries), or change the template (change the generated HTML) to customize the output. + +The template used in this plugin is located in the plugin's templates folder. The template can also be overridden by a theme designer. + +```xml title="admin/tool/demo/templates/index_page.mustache" +
+

Heading

+

{{sometext}}

+
+``` + +This is the mustache template for this demo. It uses some bootstrap classes directly to position and style the content on the page. `{{sometext}}` is replaced with the variable from the context when this template is rendered. For more information on templates see [Templates](../../guides/templates/index.md). + +## Output Functions + +This section tries to explain a bit how dynamic data should be sent from Moodle to the browser in an organised and standard way. + +:::important +Obviously it's possible to have your own output methods but, thinking that you are going to share your code (yep, this is an OpenSource project!) and in the collaborative way we try to build and maintain the system every day, it would be really better to follow the basic guidelines explained below. + +By using them you will be helping to have better, more secure and readable code. Spend some minutes trying to understand them, please! +::: + +Of course, these functions can be discussed, modified and new functions can arrive if there are some good reasons for it. Just discuss it in the [General developer forum](http://moodle.org/mod/forum/view.php?id=55). + +For each of the functions below we'll try to explain when they should be used, explaining the most important parameters supported and their meaning. Let's review them! + +### p() and s() + +```php +function s($var, $strip=false) +function p($var, $strip=false) +``` + +These functions share the same code, so they will be explained together. The only difference is that `s()` returns the string, while `p()` prints it directly. + +These functions should be used to: + +- print all the **values of form fields** like `` or `