From bbe8d8c6fcf1884a9983a3ad6c2ca36a67f64d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20D=C3=A9niz?= Date: Mon, 7 Aug 2023 16:20:12 +0100 Subject: [PATCH] [docs] Migrate Plugin contribution docs --- .vscode/settings.json | 5 +- data/migratedPages.yml | 27 +++ docs/apis/_files/changes.mdx | 2 +- docs/apis/_files/readme.mdx | 2 +- docs/apis/_files/styles-css.mdx | 2 +- docs/apis/commonfiles/index.mdx | 2 +- .../plugin-codeprechecks-details.png | Bin 0 -> 4054 bytes .../plugin-codeprechecks-error.png | Bin 0 -> 2156 bytes .../plugin-codeprechecks-success.png | Bin 0 -> 1174 bytes .../_documentation/infobox_plugin.png | Bin 0 -> 15383 bytes .../_guardians/plugins-guardian-logo.png | Bin 0 -> 4278 bytes .../community/plugincontribution/adoption.md | 61 ++++++ .../community/plugincontribution/checklist.md | 173 ++++++++++++++++ .../plugincontribution/codeprechecks.md | 39 ++++ .../plugincontribution/documentation.md | 68 +++++++ .../community/plugincontribution/guardians.md | 25 +++ general/community/plugincontribution/index.md | 8 +- .../plugincontribution/pluginsdirectory.md | 12 ++ .../plugincontribution/pluginsdirectoryapi.md | 188 ++++++++++++++++++ .../plugincontribution/qaprechecks.md | 36 ++++ .../plugincontribution/thirdpartylibraries.md | 35 ++++ general/development/process/peer-review.md | 2 +- project-words.txt | 7 + 23 files changed, 684 insertions(+), 10 deletions(-) create mode 100644 general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-details.png create mode 100644 general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-error.png create mode 100644 general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-success.png create mode 100644 general/community/plugincontribution/_documentation/infobox_plugin.png create mode 100644 general/community/plugincontribution/_guardians/plugins-guardian-logo.png create mode 100644 general/community/plugincontribution/adoption.md create mode 100644 general/community/plugincontribution/checklist.md create mode 100644 general/community/plugincontribution/codeprechecks.md create mode 100644 general/community/plugincontribution/documentation.md create mode 100644 general/community/plugincontribution/guardians.md create mode 100644 general/community/plugincontribution/pluginsdirectory.md create mode 100644 general/community/plugincontribution/pluginsdirectoryapi.md create mode 100644 general/community/plugincontribution/qaprechecks.md create mode 100644 general/community/plugincontribution/thirdpartylibraries.md diff --git a/.vscode/settings.json b/.vscode/settings.json index f09d7f69ae..f83de6da82 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "stylelint.packageManager": "yarn", - "cSpell.enabled": true + "cSpell.enabled": true, + "cSpell.words": [ + "prechecks" + ] } diff --git a/data/migratedPages.yml b/data/migratedPages.yml index 7feb608e40..cfa7fb3013 100644 --- a/data/migratedPages.yml +++ b/data/migratedPages.yml @@ -1485,15 +1485,42 @@ Peer_reviewing: Plagiarism_API: - filePath: "/docs/apis/subsystems/plagiarism.md" slug: "/docs/apis/subsystems/plagiarism" +Plugin_QA_prechecks: +- filePath: "/general/community/plugincontribution/qaprechecks.md" + slug: "/general/community/plugincontribution/qaprechecks" +Plugin_code_prechecks: +- filePath: "/general/community/plugincontribution/codeprechecks.md" + slug: "/general/community/plugincontribution/codeprechecks" Plugin_contribution: - filePath: "/general/community/plugincontribution/index.md" slug: "/general/community/plugincontribution" +Plugin_contribution_checklist: +- filePath: "/general/community/plugincontribution/checklist" + slug: "/general/community/plugincontribution/checklist" +Plugin_documentation: +- filePath: "/general/community/plugincontribution/documentation.md" + slug: "/general/community/plugincontribution/documentation" Plugin_files: - filePath: "/docs/apis/commonfiles.md" slug: "/docs/apis/commonfiles" Plugin_types: - filePath: "/docs/apis/plugintypes/index.md" slug: "/docs/apis/plugintypes/" +Plugin_with_third_party_libraries: +- filePath: "/general/community/plugincontribution/thirdpartylibraries.md" + slug: "/general/community/plugincontribution/thirdpartylibraries" +Plugins_adoption_programme: +- filePath: "/general/community/plugincontribution/adoption.md" + slug: "/general/community/plugincontribution/adoption" +Plugins_directory: +- filePath: "/general/community/plugincontribution/pluginsdirectory.md" + slug: "/general/community/plugincontribution/pluginsdirectory" +Plugins_directory_API: +- filePath: "/general/community/plugincontribution/pluginsdirectoryapi.md" + slug: "/general/community/plugincontribution/pluginsdirectoryapi" +Plugins_guardians: +- filePath: "/general/community/plugincontribution/guardians.md" + slug: "/general/community/plugincontribution/guardians" Preference_API: - filePath: "/docs/apis/core/preference/index.md" slug: "/docs/apis/core/preference/" diff --git a/docs/apis/_files/changes.mdx b/docs/apis/_files/changes.mdx index d4386313c8..afcb40332e 100644 --- a/docs/apis/_files/changes.mdx +++ b/docs/apis/_files/changes.mdx @@ -1,5 +1,5 @@ -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: +If your plugin includes a changelog in its root directory, this will be used to automatically pre-fill the release notes field when uploading new versions of your plugin to the [Plugins directory](/general/community/plugincontribution/pluginsdirectory). This file can be in any of the following locations: - `CHANGES.md`: as a markdown file; or - `CHANGES.txt`: as a text file; or diff --git a/docs/apis/_files/readme.mdx b/docs/apis/_files/readme.mdx index 185121a363..42bb31fa21 100644 --- a/docs/apis/_files/readme.mdx +++ b/docs/apis/_files/readme.mdx @@ -1,4 +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 that you include any additional information for your plugin in a project readme file. Ideally this should act as an offline version of all information in your plugin's page in the [Plugins directory](/general/community/plugincontribution/pluginsdirectory). We recommend creating your readme file in either a `README.md`, or `README.txt` format. diff --git a/docs/apis/_files/styles-css.mdx b/docs/apis/_files/styles-css.mdx index e62a0dd592..5b5495f69b 100644 --- a/docs/apis/_files/styles-css.mdx +++ b/docs/apis/_files/styles-css.mdx @@ -1,7 +1,7 @@ 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) +- [Plugin contribution checklist#CSS styles](/general/community/plugincontribution/checklist#css-styles) - [CSS Coding Style](https://docs.moodle.org/dev/CSS_Coding_Style) :::tip Avoid custom styles where possible diff --git a/docs/apis/commonfiles/index.mdx b/docs/apis/commonfiles/index.mdx index 19c8ab7ee2..c887bb2a23 100644 --- a/docs/apis/commonfiles/index.mdx +++ b/docs/apis/commonfiles/index.mdx @@ -161,4 +161,4 @@ import extraLangDescription from '../_files/lang-extra.md'; - [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 +- [Checklist for plugin contributors](/general/community/plugincontribution/checklist) - read before submitting a plugin diff --git a/general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-details.png b/general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-details.png new file mode 100644 index 0000000000000000000000000000000000000000..33bbf3dc804d3dc1d81fc1c90347ce37f7183549 GIT binary patch literal 4054 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 IM6N<$f+^$QV*mgE literal 0 HcmV?d00001 diff --git a/general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-error.png b/general/community/plugincontribution/_codeprechecks/plugin-codeprechecks-error.png new file mode 100644 index 0000000000000000000000000000000000000000..c15a53c9f1a4248a8c867e986c594f1d987f2c0b GIT binary patch literal 2156 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 literal 0 HcmV?d00001 diff --git a/general/community/plugincontribution/_documentation/infobox_plugin.png b/general/community/plugincontribution/_documentation/infobox_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..5a3c6c01affe0813a5352da470273a3c79dc057c GIT binary patch literal 15383 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 lb)l{{|`|2#r6OI literal 0 HcmV?d00001 diff --git a/general/community/plugincontribution/_guardians/plugins-guardian-logo.png b/general/community/plugincontribution/_guardians/plugins-guardian-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f192a19f20d73066646a2d7416017d564f942639 GIT binary patch literal 4278 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 YKiz4z>% literal 0 HcmV?d00001 diff --git a/general/community/plugincontribution/adoption.md b/general/community/plugincontribution/adoption.md new file mode 100644 index 0000000000..150a9f40d8 --- /dev/null +++ b/general/community/plugincontribution/adoption.md @@ -0,0 +1,61 @@ +--- +title: Plugins adoption programme +sidebar_position: 9 +tags: + - Guidelines for contributors + - Plugins + - Plugin documentation +--- +The Plugins adoption programme is a process for making it clear that a plugin is orphaned and is looking for a new maintainer. The programme is one of the mechanisms helping to minimise the risks of relying on additional plugins. The programme helps to find new maintainers for plugins whose original authors can't work on the plugin fully any more. + +The programmes started in 2014 by announcing it in [a forum post](https://moodle.org/mod/forum/discuss.php?d=260354#p1128482) and since then, many plugins found their new maintainers since then through it. + +### Motivation + +Having an additional plugin installed at a Moodle site is always a risk. One of the essential aspects (apart from the code quality itself) that potential plugin users have to consider is how well the plugin is being maintained. Does the maintainer release regular updates and bug fixes? Is the plugin updated every six months for the new Moodle major release? Is there a place to report bugs and feature requests? And when reported, are they reflected? + +It's not that difficult to write a new Moodle plugin these days. Many students do that as a part of their school or thesis projects, for example. But can one rely on the author of plugin to provide sufficient (or at least some) support for it? To be a responsible maintainer of a plugin is much harder than to be an author of it. Many maintainers work on their plugins in their free time. And even if they are lucky enough to be paid for doing that, it's just time consuming (as everything). + +At certain moment, maintainers can realise they are not able to give enough love to their plugins any more. In the essay The Cathedral and the Bazaar, Eric Steven Raymond says + +:::note + +When you lose interest in a program, your last duty to it is to hand it off to a competent successor. + +::: + +And that is what Moodle plugins adoption programme is about. + +### Programme rules for plugin maintainers + +1. It is not a shame to give up on a plugin maintenance. Unmaintained plugin is worse than no plugin. We appreciate that you as the original author do not want to harm Moodle reputation just because your old code broke someone's site. +1. If you decide to offer your plugin for adoption, let the world know via posting into the [Plugins traffic forum](https://moodle.org/mod/forum/view.php?id=8149). +1. Your plugin will be put into a [special set in the Plugins directory](https://moodle.org/plugins/?q=set:maintainer-needed). +1. Once there is a volunteer who would like to take over the maintenance, please reply to the forum. It will help if the candidate proves their skills via a reference or a patch for existing issue etc. So we all know the plugin is passed over to good hands. +1. Finally, the successor is given the lead maintainer role for the plugin with all the permissions (edit the plugin record, release new versions etc). The previous maintainer will be still listed as the original author in the directory. + +### Applying to become a maintainer + +If you would like to become a new maintainer of a plugin that has been put up for the adoption, please reply to the relevant post in the [Plugins traffic forum](https://moodle.org/mod/forum/view.php?id=8149). It will help to demonstrate that you would be able to maintain the plugin - ideally with existing pull requests or other contributions to the plugin. + +### Notes + +- The `@author` tag in the phpDocs block of a file should never be changed even after the whole file is rewritten eventually. It's GPL legal statement, not a credits line. +- If the plugin was originally using Github as its repository, it is recommended to transfer the ownership of the repo. That way, all the reported issues in the github tracker, pull requests and all other information is kept. + +### Forced adoption + +As discussed in [MDLSITE-5354](https://tracker.moodle.org/browse/MDLSITE-5354), there are cases when a plugin becomes effectively orphaned due to maintainer's inactivity. In most cases, we can get in touch with the maintainer and agree on putting the plugin up for adoption. In rare cases when the maintainer can't be reached, the following procedure applies. + +- Given that the maintainer has not logged in to the moodle.org site for 90 or more days +- When the maintainer does not reply to an email sent to their address (obtained from their moodle.org user profile, last commit message and tracker account) within a period of 30 days +- And the maintainer does not reply to a personal message sent to them via moodle.org messaging features within a period of 30 days +- Then plugins directory curators can consider the plugin maintainer disappeared and the plugin orphaned. It is then allowed to put the plugin up for adoption on behalf of the maintainer or assign it to another maintainer. + +### Initiating forced adoption + +If you are aware of a plugin that seems abandoned and you would like to help and become a new maintainer of it: + +- Try to contact the current maintainer and let them know about this Plugins adoption programme. It is always better to have the plugin put for adoption by the current maintainers. +- If you do not get any response within a reasonable period, please publish your offer and intention via a new post in the [Plugins traffic forum](https://moodle.org/mod/forum/view.php?id=8149) to make the community aware of it. +- The plugins directory curators are subscribed to that forum and will be notified about your post there. They will help you with further process. diff --git a/general/community/plugincontribution/checklist.md b/general/community/plugincontribution/checklist.md new file mode 100644 index 0000000000..e041822e8b --- /dev/null +++ b/general/community/plugincontribution/checklist.md @@ -0,0 +1,173 @@ +--- +title: Plugin contribution checklist +sidebar_position: 2 +tags: + - Guidelines for contributors + - Plugins + - Plugin documentation +--- +Before approaching the [Moodle plugins directory](https://moodle.org/plugins) and submitting your plugin (or a new plugin version), you are encouraged to go through the checklists below and fix eventual issues with your plugin. Doing so will make the reviewer of your plugin happy :-) and may have impact on how long your plugin has to spend in the approval queue before it lands smoothly. + +## Meta-data + +### Plugin descriptions + +- Have a meaningful description of your plugin prepared in English. +- You will need a short concise description (just a sentence or two) for the short description field, and another elaborated one for the full description field. +- It is encouraged to have the same info at the plugin record page and in its `README` file. + +### Supported Moodle versions + +- New plugins submitted into the plugins directory must support at least one of the currently maintained Moodle version. +- See [Releases](../../releases.md) for the list of currently maintained Moodle versions (policy issue [MDL-47579](https://tracker.moodle.org/browse/MDL-47579)). + +### Code repository name + +- Provide a consistent experience for other Moodle developers and site administrators - follow the repository naming convention for Moodle plugins: `moodle-{plugintype}_{pluginname}`. + +### Source control URL + +- Facilitate sharing and further development of your open-source plugin - provide publicly accessible URL of your code repository. +- Github is a choice of most Moodle plugin developers. + +### Bug tracker URL + +- Encourage participation and have a place to report issues, bugs, make feature requests, or suggest other types of improvements. +- Both [Moodle tracker](../../development/tracker/guide.md) and [Github issues](https://guides.github.com/features/issues/) are common. +- See [Plugin contribution#Tracker](./index.md#tracker) if you want to use the Moodle tracker. + +### Documentation URL + +- Have a place where further documentation of your plugin will be located. +- [Moodle docs](../../community/plugincontribution/documentation) is preferred location, [Github wikis](https://guides.github.com/features/wikis/) or your own website will work, too. + +### Illustrative screenshots + +- Capture some screenshots of your plugin to help folks get an idea of what it looks like when installed. +- We will use these screenshots at more places in the plugins directory in the future. + +### Licensing + +- All files that implement the interface between the Moodle core and the plugin must be licensed under GNU GPL v3 (or later). +- Additional files contained in the plugin ZIP package (such as third party libraries used by the plugin, or included media) may eventually use other license as long as it is [GPL compatible](http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses). See [Plugin files#thirdpartylibs.xml](/docs/apis/commonfiles#thirdpartylibsxml) for how to do it. +- Note that binary files violate GPL unless the source code is also included or available (e.g. Java classes or Flash). + +### Subscription needed + +- If the plugin requires a third-party subscription based service, make sure the description states it very clearly. +- If the plugin requires some credentials to integrate with an external system (such as API keys etc), the description should provide clear information of where and how the users can obtain them. +- To allow the testing of the plugin functionality, please provide us with demo credentials (such as API keys etc) so that the approval team can use them to see the plugin in action. + +## Usability + +### Installation + +- Make sure the plugin installs smoothly from the ZIP package using the in-build plugin installation interface. +- If any non-standard post-installation steps are needed, make sure they are clearly listed in both plugin description and the `README` file. + +### Dependencies + +- If the plugin depends on another additional plugin, make sure it is clearly stated in the description and in the `README` file. +- Also declare the dependency explicitly in the plugin's [version.php](/docs/apis/commonfiles/version.php) file. +- Plugins must not require the admin to run **composer** or similar dependency manager tools. Many Moodle admins do not have access to their server shell and/or are not experienced with PHP building tools. Moodle plugins are supposed to be distributed in packages that work out of the box. + +### Functionality + +- Test the plugin functionality with full developer debugging enabled. +- Make sure the code does not throw unexpected PHP warnings, notices or even errors. + +### Cross-DB compatibility + +- Test the plugin with multiple database engines supported by Moodle. +- At very least, the plugin is supposed to work with MySQL and PostgreSQL unless reasons are clearly explained in the description and the `README` file (such as the plugin is a wrapper for third-party DB specific utility). +- [Data manipulation API](/docs/apis/core/dml) helps you to ensure cross-db compatibility. + +## Coding + +### Coding style + +- It is encouraged to follow [Moodle coding style](../../development/policies/codingstyle) and other [coding guidelines](../../development/policies.md). +- It's not always possible to achieve "all greens" in automated syntax checks (especially when third party libraries are involved) but you should aim to it. +- Consistent style helps others to read and understand your code (not only during the approval review). + +### English + +- Moodle is an international project. To facilitate sharing, reviews of and contributions to your code, all comments, variable names, function names etc. should be in English. + +### Boilerplate + +- All files should contain the common boilerplate at the beginning with explicit GPL license statement. +- See the section [Coding style#Files](../../development/policies/codingstyle/index.md#files) for the template. + +### Copyrights + +- All files should contain the `@copyright` tag with your name. +- If you are re-using someone else's file, keep the original copyrights reference to the previous author and add your name as a copyright holder. +- Both things should be clear: (1) that it is you to be blamed for the file code and (2) that your work is based on someone else's work. + +```php +/** + * @copyright 2019 John Smith + * @copyright based on work by 2010 Mary Stuart + */ +``` + +### CSS styles + +- All styles.css files from all plugins are concatenated, cached and served as a one big resource on all pages of the given Moodle installation. +- For that reason, plugins must use properly namespaced selectors so that their style sheets can be safely combined with others without affecting other pages and elements beyond the plugin's scope. +- Plugin specific CSS selectors are needed to make sure that your styling does not accidentally affect other parts of Moodle outside your plugin scope. +- For example, instead of the selector `.contentarea` it is better to use something like `.path-mod-mymodule .contentarea` as the `.path-\*` classes are automatically added by the Moodle core renderers to the HTML `` tag. + +### Namespace collisions + +- Check that all your DB tables, settings, functions, classes, constants and variables are named correctly. In most cases their names must start with the plugin type and plugin name, as in `block_yourname_something` (so called [frankenstyle](../../development/policies/codingstyle/frankenstyle.md) prefix). Modules are an exception to this rule as functions such as get_coursemodule_from_id() rely on there being no preface of 'mod'. +- Do not define own classes, functions, variables or constants in the top-level scope (global space) without the valid frankenstyle prefix. +- See [Coding style#Functions and Methods](../../development/policies/codingstyle/index.md#functions-and-methods) for details. + +### Settings storage + +- Check that your settings are stored in the table `config_plugins` and not in the main `config` table. +- This helps to avoid `$CFG` bloat and potential collisions. +- Use `get_config()` to pull the settings data out of the `config_plugins` table. +- In the file ```settings.php```, the setting names are supposed to be `plugintype_pluginname/settingname` (note the slash) and not `plugintype_pluginname_settingname` or even just `settingname`. +- If you eventually need to change the settings yourself, use `set_config()`. + +### Strings + +- Avoid hard-code texts in the code, always use `get_string()`. +- Just the English strings should ship with the plugin. All other translations are supposed to be submitted as contributions at [https://lang.moodle.org](https://lang.moodle.org) once your plugin is approved - see [Translating plugins](https://docs.moodle.org/dev/Translating_plugins). +- Your code must not rely on trailing and leading whitespace in strings. +- The string file must be considered as pure data file with the syntax `$string[]('id') = 'value';`. No other PHP syntax such as [concatenation](http://php.net/manual/en/language.operators.string.php), [heredoc and nowdoc](http://php.net/manual/en/language.types.string.php) is supported by the tools that we use when processing your strings (even if it may work in Moodle itself). +- The English language pack (lang/en/) in Moodle does not use "Capitalised Titles". + +### Privacy + +- Avoid gathering, storing, processing and sharing personal data unless they are needed for the plugin functionality. +- Inform about all the personal data that the plugin processes, via both description and the [Privacy API](/docs/apis/subsystems/privacy/). +- For plugins that integrate with an external system, privacy API implementation is required prior approval in the plugins directory (most notably the meta-data provider part). + +### Security + +- Never trust the user input. +- Do not access superglobals like `$_REQUEST` directly, use wrappers like required_param() with correct type declared to sanitize input. +- Always use placeholders in custom SQL queries (`?` or `:named`). +- Always check for the `sesskey` before taking an action on submitted data. +- Check for `require_login()`. +- Always check that the user has appropriate capabilities before displaying the widgets *and* before taking the actual action. +- Avoid using malicious functions like `call_user_func()`, `eval()`, `unserialize()` and so on, especially when they would be called with user-supplied data. + +## Approval blockers + +Examples of issues that will prevent your plugin from being approved: + +1. There is no public and transparent issue tracker where the community members can leave feedback, report bugs and suggest improvements. +1. Your SQL fails to work on PostgreSQL even when working on MySQL. +1. It integrates with an external system and does not have the privacy API correctly implemented. +1. It is an activity module and does not have the backup and restore API implemented. + +## See also + +- [Some common issues in submitted plugins](https://moodle.org/mod/forum/discuss.php?d=263614) post at the Plugins traffic forum at moodle.org +- [Moodle Coding Style](https://moodle.org/mod/forum/discuss.php?d=371967) forum thread at the General Developer Forum at moodle.org +- [Plugin files](/docs/apis/commonfiles) has a list and descriptions of files that work the same in all plugin types diff --git a/general/community/plugincontribution/codeprechecks.md b/general/community/plugincontribution/codeprechecks.md new file mode 100644 index 0000000000..feed7e6a46 --- /dev/null +++ b/general/community/plugincontribution/codeprechecks.md @@ -0,0 +1,39 @@ +--- +title: Plugin code prechecks +sidebar_position: 3 +tags: + - Guidelines for contributors + - Plugins + - Plugin documentation +--- +In the Moodle Plugins directory, uploaded plugins versions are automatically tested against a set of formal criteria. These tests typically do not check the actual plugin functionality, security or code correctness. They are more focused on formal aspects of the coding style. As such, they are most valuable for the plugin maintainers themselves. + +## Labels + +Plugin version with no detected errors or warnings has a label like this displayed: + +![Plugin code prechecks success](_codeprechecks/plugin-codeprechecks-success.png) + +If there are some formal errors or warnings detected, a label like this is displayed: + +![Plugin code prechecks error](_codeprechecks/plugin-codeprechecks-error.png) + +The first of the two numbers gives the total number of detected errors, the second number shows the number of warnings. If there are no errors but some warnings, the label is displayed in orange colour. In case of some errors, the label is displayed in red. + +Clicking the code prechecks label takes you to a page with details on particular tests executed. Individual labels are displayed for each of the test, with the same formatting rules as described above. + +![Plugin code prechecks details](_codeprechecks/plugin-codeprechecks-details.png) + +Finally, clicking some of these individual test labels takes you to a page with detailed raw output of the test system. + +## Test types + +- **phplint**: Checks the plugin source code for correct PHP syntax. +- **phpcs**: Checks the plugin against the [Moodle coding style](../../development/policies/codingstyle). +- **js**: Checks the plugin against the [JavaScript coding style](/docs/guides/javascript/). +- **css**: Checks the plugin against the [CSS coding style](https://docs.moodle.org/dev/CSS_Coding_Style). +- **phpdoc**: Checks that the plugin files, classes and functions are documented in the source code. +- **savepoint**: Reports issues detected with the handling of [upgrade savepoints](/docs/guides/upgrade/). +- **thirdparty**: Reports issues with the [thirdpartylibs.xml](/docs/apis/commonfiles#thirdpartylibsxml) file. +- **grunt**: Reports issues with [Grunt](https://docs.moodle.org/dev/Grunt) builds. +- **mustache** : Reports issues with [Mustache templates](/docs/guides/templates/). diff --git a/general/community/plugincontribution/documentation.md b/general/community/plugincontribution/documentation.md new file mode 100644 index 0000000000..f7c052822f --- /dev/null +++ b/general/community/plugincontribution/documentation.md @@ -0,0 +1,68 @@ +--- +title: Plugin documentation +sidebar_position: 6 +tags: + - Plugins + - Plugin documentation + - Guidelines for contributors +--- +Plugin developers, maintainers and users are welcome to include documentation about their plugin in the [English Moodle Docs](https://docs.moodle.org). Of course it is fine to have documentation elsewhere, such as the Github wiki, however one advantage of including documentation in the English Moodle Docs is that 'Moodle Docs for this page' links in Moodle (when logged in as a teacher or admin) can lead directly to your plugin documentation (as explained in the [Header and footer](https://docs.moodle.org/en/Header_and_footer) documentation). And, very important, it will then be very easy for translators of Moodle Docs to add translations for this information. + +## Where should the documentation go? + +To create a page for your documentation, type in the browser address bar: `https://docs.moodle.org/en/Plugin_name` (where *Plugin name* is the name of the plugin in the plugins directory). + +If your plugin has a page in Moodle, you can redirect this page to your documentation page as follows: + +1. Log in to your Moodle site as admin and go to the page for your plugin. +2. Follow the 'Help and documentation' link in the footer to *docs.moodle.org/en/mod/pluginname* (or similar). +3. Create this page and add a redirect by adding the text `#redirect [Plugin name](https://docs.moodle.org/dev/Plugin_name)`. + +## What should the documentation include? + +Copy and complete the following template code to obtain an infobox listing details of the plugin: + +``` +{{Infobox plugin +|type = Enter the plugin type e.g. activity, block, filter +|entry = Enter the plugins directory link +|tracker = Enter the bug tracker URL +|discussion = Enter the link to the forum or discussion thread +|maintainer = [Maintainer name](https://docs.moodle.org/User/Maintainer_name) +|float = Enter left or right to make the box float to that side (optional) +}} +``` + +![Example infobox: Stamp collection module](./_documentation/infobox_plugin.png) + +:::note + +If there is not yet a discussion thread about your plugin, please create one in the [General plugins forum](http://moodle.org/mod/forum/view.phpid=44). + +::: + +:::note + +Please make sure that the page linked in 'User:Maintainer name|Maintainer name' actually has your relevant details (profile), or a link to an existing profile in Moodle or elsewhere. + +::: + +The documentation may also include: + +- A features overview with screenshots or videos. +- Installation instructions + +Plugin documentation examples: [Stamp collection](https://docs.moodle.org/en/Stamp_collection_module), [Profile switches](https://docs.moodle.org/en/Profile_switches). + +## Which version of the user docs should the documentation be added to? + +Plugin documentation should be added to the most recent version wiki in which the plugin works, for example if the plugin works in Moodle 4.0, it should be added to the [Moodle 4.0 docs wiki](https://docs.moodle.org/400/en/). + +## I need help! + +If any of the above sounds too complicated, please don't worry - just email Moodle Docs wiki admin Helen ([helen@moodle.org](mailto:helen@moodle.org)) who will be happy to help you :-) (Restoring and redirecting pages etc. are quick and easy for a wiki admin to do!) + +## See also + +- [Wiki editing help](http://docs.moodle.org/en/Help:Editing) +- [MDL-34035](https://tracker.moodle.org/browse/MDL-34035) A way to have more help links relative to wwwroot diff --git a/general/community/plugincontribution/guardians.md b/general/community/plugincontribution/guardians.md new file mode 100644 index 0000000000..29999fad95 --- /dev/null +++ b/general/community/plugincontribution/guardians.md @@ -0,0 +1,25 @@ +--- +title: Plugins guardians +sidebar_position: 8 +tags: + - Guidelines for contributors + - Plugin documentation + - Plugins +--- +![thumb](./_guardians/plugins-guardian-logo.png) + +**Plugins guardians** are Moodle community members who volunteer to provide peer-reviews on plugins submitted into the [Plugins directory](../../community/plugincontribution/pluginsdirectory). Their peer-review is considerably taken into account when deciding on the plugin approval. + +## Mission + +These are the main principles and goals of the Plugins guardians programme: + +1. Help to build healthy and sustainable eco-system of Moodle plugins. +1. Protect the Moodle sites from dangerous, unreliable and generally badly written plugins. +1. Serve the plugin authors by providing them with honest feedback on their work, together with suggestions on how to learn to improve their code. + +These three come in this priority order. So for example, even not-so-well-written plugins can be approved as long as it is believed the community would benefit from them and there are signs that maintainers will improve them in the future. + +## How to join + +Moodle developers and experienced users are welcome to sign up for the Plugin guardians programme. Please email David Mudrák for more details. Please attach links to your Moodle development related work such as published plugins, Moodle patches, forum posts in developer forums etc. diff --git a/general/community/plugincontribution/index.md b/general/community/plugincontribution/index.md index a9f3a80fa6..6efbc6677e 100644 --- a/general/community/plugincontribution/index.md +++ b/general/community/plugincontribution/index.md @@ -5,7 +5,7 @@ tags: - Plugins - Plugin documentation --- -This page describes how to contribute your code into the [Plugins directory](https://docs.moodle.org/dev/Plugins_directory) to share it with the Moodle community. +This page describes how to contribute your code into the [Plugins directory](../../community/plugincontribution/pluginsdirectory) to share it with the Moodle community. ## Why @@ -33,12 +33,12 @@ Before submitting your work to the Plugins directory, you should make sure you h - **Plugin name** - See the [Frankenstyle](../../development/policies/codingstyle/frankenstyle.md) page for details. - **Repository** - You are expected to have the plugin code published and shared in a way that facilitates collaboration on further development. Ideally, you have the code available in a public Git repository. Most developers found [Github](https://github.com) a good place to host their code on. The [Repository](#repository) section below provides more details. - **Tracker** - You are expected to have a system where users can report issues, bugs and feature requests for the plugin. Again, many developers use [Github issues](https://guides.github.com/features/issues/) happily these days. You can also use the Moodle tracker if you prefer. See [Tracker](#tracker) section for more details. -- **Documentation** - The plugin should have a good documentation available. See [Plugin documentation](https://docs.moodle.org/dev/Plugin_documentation) for options. +- **Documentation** - The plugin should have a good documentation available. See [Plugin documentation](../../community/plugincontribution/documentation) for options. - **Screenshots** - Prepare good screenshots that illustrate your plugin's essential features. ## Sharing code in the Plugins directory -So you have written a new plugin and want to share it now in the [Plugins directory](https://docs.moodle.org/dev/Plugins_directory)? Great! Shortly, the workflow of publishing and maintaining your plugin in the Plugins directory looks like this: +So you have written a new plugin and want to share it now in the [Plugins directory](../../community/plugincontribution/pluginsdirectory)? Great! Shortly, the workflow of publishing and maintaining your plugin in the Plugins directory looks like this: ![Workflow of contributing a plugin into the Moodle plugins directory](_index/plugin-contribution-workflow.png)
@@ -47,7 +47,7 @@ Workflow of contributing a plugin into the Moodle plugins directory ([SVG versio
-1. You upload the initial plugin version for approval from the [Register a new plugin](https://moodle.org/plugins/registerplugin.php) link, available in the Navigation block in the blocks drawer on the right. To help the approval review go smoothly, please feel encouraged to review the [Plugin contribution checklist](https://docs.moodle.org/dev/Plugin_contribution_checklist) and follow all the guidelines there. +1. You upload the initial plugin version for approval from the [Register a new plugin](https://moodle.org/plugins/registerplugin.php) link, available in the Navigation block in the blocks drawer on the right. To help the approval review go smoothly, please feel encouraged to review the [Plugin contribution checklist](checklist) and follow all the guidelines there. 1. After you submit the plugin for approval, please brace yourself with patience. You will likely wait some weeks before you get initial review results. We generally try and provide the feedback sooner, but it is not always possible. The actual approval queue stats [are available](https://moodle.org/plugins/queue.php). 1. The plugin goes through the validation and approval process. 1. Almost all plugins are sent back as "needing more work" as a result of the initial review, and there is no reason to feel bad about that. It is natural part of the workflow. You may find particular issues reported so you have an opportunity to demonstrate your ability to co-operate with the reporter to resolve them. diff --git a/general/community/plugincontribution/pluginsdirectory.md b/general/community/plugincontribution/pluginsdirectory.md new file mode 100644 index 0000000000..f7e6a0608b --- /dev/null +++ b/general/community/plugincontribution/pluginsdirectory.md @@ -0,0 +1,12 @@ +--- +title: Plugins directory +sidebar_position: 7 +tags: + - Guidelines for contributors + - Plugin documentation + - Plugins +--- + +- Plugins developed and maintained by community members are listed in the [Plugins directory](https://moodle.org/plugins) at moodle.org. +- See the [Plugin contribution](../../community/plugincontribution) guidelines if you wish to contribute. +- In the User docs there is a [Contributed code category](https://docs.moodle.org/en/Category/Contributed_code) that has a list of community contributed code projects and documents. diff --git a/general/community/plugincontribution/pluginsdirectoryapi.md b/general/community/plugincontribution/pluginsdirectoryapi.md new file mode 100644 index 0000000000..e4dc137f0c --- /dev/null +++ b/general/community/plugincontribution/pluginsdirectoryapi.md @@ -0,0 +1,188 @@ +--- +title: Plugins directory API +sidebar_position: 10 +tags: + - Guidelines for contributors + - Plugins + - Plugin documentation +--- +The Plugins directory at http://moodle.org/plugins exposes some of its features via web services layer, allowing the community to develop custom tools and integrations with other services such as GitHub Actions or Travis CI. + +## Access token + +To use the web service described below, the caller (client) authenticates itself with an access token. In almost all cases, developers use their own personal token and let the scripts (clients) work on behalf of them. + +The easiest way to obtain the access token (and some other useful information) is to visit *Plugins > API access* page at moodle.org through the side Navigation block. + +The token can be alternatively obtained via the *Preferences > Security keys* or programmatically via `login/token.php` script at moodle.org (however, tokens obtain that way have very short expiration in contrast with the ones generated at the dedicated page). + +## Plugins maintenance service + +The Plugins maintenance service (`plugins_maintenance`) provides functions for the plugins maintainers. The service is declared as: + +```php +'Plugins maintenance' => [ + 'functions' => [ + 'local_plugins_get_maintained_plugins', + 'local_plugins_add_version', + ], + 'shortname' => 'plugins_maintenance', + 'requiredcapability' => 'local/plugins:editownplugins', + 'enabled' => true, + 'restrictedusers' => 0, + 'downloadfiles' => true, + 'uploadfiles' => true, +], +``` + +### Getting the list of maintained plugins + +The first external function `local_plugins_get_maintained_plugins` allows to read the list of all plugins and their recent versions the caller is maintainer of. It does not expect any parameters and its return structure is described as follows: + +```php +return new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'Internal plugin identifier'), + 'name' => new external_value(PARAM_TEXT, 'Name of the plugin'), + 'shortdescription' => new external_value(PARAM_TEXT, 'Short description'), + 'description' => new external_value(PARAM_RAW, 'Description'), + 'descriptionformat' => new external_format_value('description'), + 'frankenstyle' => new external_value(PARAM_PLUGIN, 'Full component frankenstyle name'), + 'type' => new external_value(PARAM_ALPHANUMEXT, 'Plugin type'), + 'websiteurl' => new external_value(PARAM_URL, 'Website URL'), + 'sourcecontrolurl' => new external_value(PARAM_URL, 'Source control URL'), + 'bugtrackerurl' => new external_value(PARAM_URL, 'Bug tracker URL'), + 'discussionurl' => new external_value(PARAM_URL, 'Discussion URL'), + 'timecreated' => new external_value(PARAM_INT, 'Timestamp of plugin submission'), + 'approved' => new external_value(PARAM_INT, 'Approval status'), + 'visible' => new external_value(PARAM_BOOL, 'Visibility status'), + 'aggdownloads' => new external_value(PARAM_INT, 'Stats aggregataion - downloads'), + 'aggfavs' => new external_value(PARAM_INT, 'Stats aggregataion - favourites'), + 'aggsites' => new external_value(PARAM_INT, 'Stats aggregataion - sites'), + 'statusamos' => new external_value(PARAM_INT, 'AMOS registration status'), + 'viewurl' => new external_value(PARAM_URL, 'View URL'), + 'currentversions' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'Internal version identifier'), + 'version' => new external_value(PARAM_INT, 'Version number'), + 'releasename' => new external_value(PARAM_TEXT, 'Release name'), + 'releasenotes' => new external_value(PARAM_RAW, 'Release notes'), + 'releasenotesformat' => new external_format_value('releasenotes'), + 'maturity' => new external_value(PARAM_INT, 'Maturity code'), + 'changelogurl' => new external_value(PARAM_URL, 'Change log URL'), + 'altdownloadurl' => new external_value(PARAM_URL, 'Alternative download URL'), + 'md5sum' => new external_value(PARAM_TEXT, 'MD5 hash of the ZIP content'), + 'vcssystem' => new external_value(PARAM_ALPHA, 'Version control system'), + 'vcssystemother' => new external_value(PARAM_TEXT, 'Name of the other version control system'), + 'vcsrepositoryurl' => new external_value(PARAM_URL, 'Version control system URL'), + 'vcsbranch' => new external_value(PARAM_TEXT, 'Name of the branch with this version'), + 'vcstag' => new external_value(PARAM_TEXT, 'Name of the tag with this version'), + 'timecreated' => new external_value(PARAM_INT, 'Timestamp of version release'), + 'approved' => new external_value(PARAM_INT, 'Approval status'), + 'visible' => new external_value(PARAM_BOOL, 'Visibility status'), + 'supportedmoodle' => new external_value(PARAM_TEXT, 'Comma separated list of support Moodle versions'), + 'downloadurl' => new external_value(PARAM_URL, 'Download URL'), + 'viewurl' => new external_value(PARAM_URL, 'View URL'), + 'smurfresult' => new external_value(PARAM_TEXT, 'Code prechecks results summary'), + ]) + ), + ]) +); +``` + +#### Example cURL client fetching the list of maintained plugins + +```bash +#!/bin/bash + +# Replace this with your own service access token. +TOKEN="d41d8cd98f00b204e9800998ecf8427e" + +CURL="curl -s" +ENDPOINT=https://moodle.org/webservice/rest/server.php +FUNCTION=local_plugins_get_maintained_plugins + +${CURL} ${ENDPOINT} --data-urlencode "wstoken=${TOKEN}" --data-urlencode "wsfunction=${FUNCTION}" \ + --data-urlencode "moodlewsrestformat=json" | jq +``` + +### Releasing a new version + +The second function `local_plugins_add_version` allows to release a new version to the plugin. The input parameters are described as: + +```php +return new external_function_parameters([ + // The pluginid or frankenstyle must be provided (in this order of precedence). + 'pluginid' => new external_value(PARAM_INT, 'Internal identifier of the plugin', VALUE_DEFAULT, null), + 'frankenstyle' => new external_value(PARAM_PLUGIN, 'Full component name of the plugin', VALUE_DEFAULT, null), + // ZIP can be specified by draft area itemid (with single file in it), content or URL (in this order of precedence). + 'zipdrafitemtid' => new external_value(PARAM_INT, 'Itemid of user draft area with uploaded ZIP', VALUE_DEFAULT, null), + 'zipcontentsbase64' => new external_value(PARAM_RAW, 'ZIP file contents encoded with MIME base64', VALUE_DEFAULT, null), + 'zipurl' => new external_value(PARAM_URL, 'ZIP file URL', VALUE_DEFAULT, null), + // Following params may be auto-detected from the ZIP content. + 'version' => new external_value(PARAM_INT, 'Version number', VALUE_DEFAULT, null), + 'releasename' => new external_value(PARAM_TEXT, 'Release name', VALUE_DEFAULT, null), + 'releasenotes' => new external_value(PARAM_RAW, 'Release notes', VALUE_DEFAULT, null), + 'releasenotesformat' => new external_format_value('releasenotes', VALUE_DEFAULT, FORMAT_MOODLE), + 'maturity' => new external_value(PARAM_INT, 'Maturity code', VALUE_DEFAULT, null), + 'supportedmoodle' => new external_value(PARAM_TEXT, 'Comma separated list of supported Moodle versions', + VALUE_DEFAULT, null), + // Other optional properties. + 'changelogurl' => new external_value(PARAM_URL, 'Change log URL', VALUE_DEFAULT, null), + 'altdownloadurl' => new external_value(PARAM_URL, 'Alternative download URL', VALUE_DEFAULT, null), + 'vcssystem' => new external_value(PARAM_ALPHA, 'Version control system', VALUE_DEFAULT, null), + 'vcssystemother' => new external_value(PARAM_TEXT, 'Name of the other version control system', VALUE_DEFAULT, null), + 'vcsrepositoryurl' => new external_value(PARAM_URL, 'Version control system URL', VALUE_DEFAULT, null), + 'vcsbranch' => new external_value(PARAM_TEXT, 'Name of the branch with this version', VALUE_DEFAULT, null), + 'vcstag' => new external_value(PARAM_TEXT, 'Name of the tag with this version', VALUE_DEFAULT, null), +]); +``` + +The API has been designed so that: + +- The actual ZIP can be taken from pre-uploaded file (standard way of using `webservice/upload.php`), or submitting the file contents directly, or providing an URL the ZIP should be fetched from. +- As many as possible parameters (such as list of supported Moodle versions, release name, release notes etc) default to the values specified in the ZIP itself. +- So it should be enough to specify just the plugin (either by numerical ID number of frankenstyle) and the ZIP with the new version. All other values are optional and can be used to override the auto-detected ones. + +When successful, the external function's response is described as: + +```php +return new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'Internal identifier of the newly created version'), + 'md5sum' => new external_value(PARAM_TEXT, 'MD5 hash of the ZIP content'), + 'timecreated' => new external_value(PARAM_INT, 'Timestamp of version release'), + 'downloadurl' => new external_value(PARAM_URL, 'Download URL'), + 'viewurl' => new external_value(PARAM_URL, 'View URL'), + 'warnings' => new external_multiple_structure( + new external_value(PARAM_RAW, 'Validation warnings') + ), +]); +``` + +#### Example CLI script to release a new version of a plugin + +```bash +#!/bin/bash + +# Replace this with your own service access token. +TOKEN="d41d8cd98f00b204e9800998ecf8427e" + +# Set the full component name of the plugin. +PLUGIN=mod_subcourse +# Path to the ZIP fle with the new version. +ZIP=version.zip + +CURL="curl -s" +HOST="https://moodle.org/" +ENDPOINT_REST="${HOST}/webservice/rest/server.php" +ENDPOINT_UPLOAD="${HOST}/webservice/upload.php" + +ITEMID=$(${CURL} -F data=@${ZIP} "${ENDPOINT_UPLOAD}?token=${TOKEN}" | jq --raw-output '.[0].itemid') + +${CURL} ${ENDPOINT_REST} --data-urlencode "wstoken=${TOKEN}" --data-urlencode "wsfunction=${FUNCTION}" --data-urlencode "moodlewsrestformat=json" \ + --data-urlencode "frankenstyle=${PLUGIN}" --data-urlencode "zipdrafitemtid=${ITEMID}" | jq +``` + +#### GitHub Actions + +A new workflow can be configured at GitHub Actions to automatically release a new version in the Plugins directory once a tag is pushed to the repository. Please see https://github.com/moodlehq/moodle-plugin-release for details. diff --git a/general/community/plugincontribution/qaprechecks.md b/general/community/plugincontribution/qaprechecks.md new file mode 100644 index 0000000000..a9d1fbe744 --- /dev/null +++ b/general/community/plugincontribution/qaprechecks.md @@ -0,0 +1,36 @@ +--- +title: Plugin QA prechecks +sidebar_position: 4 +tags: + - Guidelines for contributors + - Plugins + - Plugin documentation + - QA +--- +Plugin QA prechecks are for testing the functionality of plugins submitted for approval in the Moodle Plugins directory. Together with [code prechecks](../../community/plugincontribution/codeprechecks), they are part of the plugin [approval process](../../community/plugincontribution#sharing-code-in-the-plugins-directory). + +Moodle community members with experience in setting up a local Moodle test environment can help with plugin QA prechecks. If you would like to help, please contact David Mudrák. + +## QA environment setup + +To perform plugin QA prechecks, you need to have a test Moodle site (normally the latest stable version) installed locally. Your test site should have + +- Developer debugging enabled with debugging messages displayed in order to report all PHP notices, warnings and errors spotted during plugin installation and usage. +- `$CFG->prefix` set to a non-default value, such as "m_" or "mqa_". This allows to catch cases when the default "mdl_" prefix is hard-coded in PHP. + +In addition, if possible the site should run on the PostgreSQL database engine to catch potential MySQL-specific SQL statements in the code. + +## Process + +1. Choose a plugin needing an initial review from the [list of plugins awaiting approval](https://moodle.org/plugins/report/index.php?report=pendingapproval_plugins) (access restricted to members of the [Plugins guardians](../../community/plugincontribution/guardians) group). +1. To show that you are going to perform the QA prechecks, set yourself as the plugin approval issue assignee (CONTRIB-xxx as mentioned at the plugin page comments area). +1. Download and install it on your test site then perform the QA prechecks as listed below. +1. Add a comment to the plugin approval issue with your findings using the 'Plugin QA checklist' canned response. +1. If you detect any problems with the plugin, add a comment to the plugin page asking the plugin developer to look at the plugin approval issue. +1. Once everything is fine, add a comment to the plugin approval issue 'Congratulations, your plugin passes the metadata and usability checks. :-) Coding checks will be done soon.' + +## QA prechecks + +1. Does the plugin have all the appropriate metadata as described in the [Plugin contribution checklist](../../community/plugincontribution/checklist)? +1. Does the plugin install nicely and not break or otherwise negatively affect the site functionality (anti-regression test)? This also checks that all eventual dependencies are already available in the plugins directory. +1. If possible (e.g. if no complex integration with an external system is needed), test the actual functionality of the plugin to see it works as described (feature test). diff --git a/general/community/plugincontribution/thirdpartylibraries.md b/general/community/plugincontribution/thirdpartylibraries.md new file mode 100644 index 0000000000..c065a471f7 --- /dev/null +++ b/general/community/plugincontribution/thirdpartylibraries.md @@ -0,0 +1,35 @@ +--- +title: Plugin with third party libraries +sidebar_position: 5 +tags: + - Guidelines for contributors + - Plugins + - Plugin documentation +--- +This page describes the correct way to include third party libraries with your plugin. + +A third party library refers to any library where the latest version of the code is not maintained and hosted by Moodle. An example is "Mustache.php". Following this process means that all third party libraries are correctly listed in the page *Site administration > Development > Third party libraries*, they can be tracked and kept up to date - and we will not introduce conflicting versions of the same library in different places. + +## Instructions + +The process for including a third party library is the same for core code as it is for a plugin - there are a number of steps to follow. + +1. Check the license to make sure the library uses a GPLv3 compatible license - see the [list of compatible licenses](https://www.gnu.org/licenses/license-list.en.html). If a library is not compatible with GPLv3 then it cannot be distributed together with the plugin in one ZIP package and hosted in the Moodle Plugins directory. +1. Check the library is not already shipped by core - we don't want multiple versions of the same library. +1. Download the latest stable release of the code. +1. Perform any build steps required to get a distributable version of the library. This will vary depending on the library - but an example is running less to generate minified css files. +1. Put that library into a sub folder in your plugin. It is best to NOT use version numbers in the foldername ("jquery" not "jquery-1.7.3"). +1. Create or update the `lib/thirdpartylibs.xml` file for your plugin, according to the format described in the [documentation](/docs/apis/commonfiles#thirdpartylibsxml). +1. Create a [`readme_moodle.txt`](/docs/apis/commonfiles#readme_moodletxt) file in the new third party library folder containing detailed instructions on how to complete steps 3-6 above. This should list download urls, build instructions etc. +1. Note any creation, update or deletion of third party libraries in your plugins `upgrade.txt` or [CHANGES](/docs/apis/commonfiles#changes). +1. Run `grunt ignorefiles` to regenerate ignored files path + +## Exceptions: + +### JavaScript AMD modules + +JavaScript AMD modules cannot exist in a sub-folder - they must exist in a single .js file in the amd/src folder for your plugin. So - the process for AMD files is the same as above, except that the license and readme_moodle.txt file contents must be added as a JavaScript comment to the top of the libraries .js file. + +## See also + +- [Grunt](https://docs.moodle.org/dev/Grunt) - Information for installing and using Grunt diff --git a/general/development/process/peer-review.md b/general/development/process/peer-review.md index 6c115e0962..834f76b486 100644 --- a/general/development/process/peer-review.md +++ b/general/development/process/peer-review.md @@ -204,7 +204,7 @@ See also the [Commit cheat sheet](https://docs.moodle.org/dev/Commit_cheat_sheet ### Third party code -Does the change contain [third party code](https://docs.moodle.org/dev/Plugin_with_third_party_libraries)? If so, ensure that: +Does the change contain [third party code](../../community/plugincontribution/thirdpartylibraries)? If so, ensure that: - The code is licensed under a [GPL compatible license](http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses%7C). - The instructions for upgrading/importing the library and contained within a readme_moodle.txt file. diff --git a/project-words.txt b/project-words.txt index 52aba6f3a6..afc8685fd8 100644 --- a/project-words.txt +++ b/project-words.txt @@ -121,6 +121,7 @@ coursecatlib coursefiles courseid courseindex +coursemodule crtl datafield dayofweek @@ -220,6 +221,7 @@ multichoice multilang multilanguage myprofile +nowdoc oldmoduleid onlinetext opcache @@ -232,6 +234,7 @@ pgsql phpcs phpdoc phpdocs +phplint phpmailer phpstorm phptags @@ -243,6 +246,7 @@ plugininfo pluginname plugintype plugintypes +prechecks previewquestion privatefiles protectusernames @@ -258,6 +262,8 @@ riskbitmask ruleset saas safeoverride +savepoint +savepoints scormreport selfcompletion sesskey @@ -271,6 +277,7 @@ simpletest siteadmin siteidentifier sitepolicynotagreed +superglobals smallmessage splitview stepslib