Chapitre 6.6 — Les autres types de plugins en pratique
Partie 6 : Développer des plugins — Chapitre 6/7 Public : développeur ayant fait les trois tutoriels (6.3 local, 6.4 block, 6.5 mod). On élargit : cinq types en exemples courts mais complets, puis des survols honnêtes des types complexes. Version de référence : Moodle 5.2 (PHP 8.3+, racine web
public/).
⏱️ TL;DR
- Les trois tutoriels précédents ont ancré le socle commun (version, access, lang, DML, Output API, Mustache). Chaque autre type = ce socle + son contrat de fichiers spécifique.
tool_(admin tool) : une page d’admin + une tâche planifiée (cron). Contrat :db/tasks.php+ une classescheduled_task.filter_(filtre de texte) : transforme le texte affiché (glossaire auto-lié, emojis, LaTeX…). Contrat :classes/text_filter.phpavecfilter().format_(format de cours) : change la mise en page d’un cours. On hérite deformat_topicset on surcharge le strict nécessaire.availability_(condition de disponibilité) : « accessible seulement si… ». Contrat :classes/condition.php+ un composant JS de réglage.customfield_(type de champ personnalisé) : un nouveau type de champ pour cours/utilisateurs/catégories. Contrat :classes/field_controller.php+data_controller.php.- Survols :
qtype_(type de question — riche),repository_(dépôt de fichiers),tiny_(bouton d’éditeur TinyMCE). Pour chacun : à quoi s’attendre, fichiers clés, pièges.- Fil conducteur : identifier le point d’extension (la classe/fonction que le core appelle) et n’écrire que lui.
🎯 Objectifs
À la fin de ce chapitre, vous saurez :
- Écrire un admin tool avec une tâche planifiée et comprendre le cron de Moodle.
- Écrire un filtre de texte et savoir où il s’applique (et où il ne faut pas filtrer).
- Créer un format de cours par héritage minimal de
format_topics. - Écrire une condition de disponibilité et comprendre sa moitié JS.
- Créer un type de champ personnalisé.
- Savoir à quoi vous attendre avant d’ouvrir un
qtype_,repository_outiny_.
1. Méthode : trouver le point d’extension
Chaque type de plugin définit un ou deux points d’extension — la classe ou la fonction que le core appelle. Développer ce type, c’est essentiellement : (1) déclarer l’identité (version.php, lang/), (2) implémenter ce point d’extension, (3) éventuellement une page de réglages. Le reste est du remplissage.
Le tableau de survol :
| Type | Dossier | Point d’extension principal | Ce qu’il branche |
|---|---|---|---|
tool_ | admin/tool/ | page(s) + db/tasks.php | outil d’administration + cron |
filter_ | filter/ | classes/text_filter.php::filter() | transformation du texte affiché |
format_ | course/format/ | classes/output/* + lib.php | mise en page d’un cours |
availability_ | availability/condition/ | classes/condition.php | règle « accessible si… » |
customfield_ | customfield/field/ | classes/field_controller.php | nouveau type de champ |
qtype_ | question/type/ | plusieurs classes | type de question |
repository_ | repository/ | lib.php (classe repository) | source de fichiers |
tiny_ | lib/editor/tiny/plugins/ | JS TinyMCE + plugininfo | bouton d’éditeur |
💡 Pour un dev React : pensez « interface à implémenter ». Chaque type est un contrat (au sens TypeScript
interface) que le core consomme via polymorphisme. Vous fournissez l’implémentation ; le framework l’appelle au bon moment. Comme un plugin d’un bundler ou d’un ESLint : un objet exposant les bons hooks, découvert par convention.
2. Un admin tool avec tâche planifiée : tool_cleanupdrafts
Cas d’usage : un outil d’admin qui supprime chaque nuit les brouillons de plus de 30 jours (exemple fictif mais réaliste). Il montre deux briques essentielles : une page d’admin et une tâche planifiée (le cron de Moodle).
Arborescence :
public/admin/tool/cleanupdrafts/
├── version.php
├── settings.php
├── index.php
├── db/
│ └── tasks.php ← déclare la tâche planifiée
├── classes/
│ └── task/
│ └── cleanup_task.php ← la tâche elle-même
└── lang/en/tool_cleanupdrafts.phpversion.php (identité — component = 'tool_cleanupdrafts', dossier admin/tool/cleanupdrafts).
Déclarer la tâche — db/tasks.php :
<?php
// public/admin/tool/cleanupdrafts/db/tasks.php
defined('MOODLE_INTERNAL') || die();
$tasks = [
[
'classname' => 'tool_cleanupdrafts\task\cleanup_task',
'blocking' => 0,
// Planification cron : minute, heure, jour-du-mois, mois, jour-semaine.
'minute' => '30',
'hour' => '3', // tous les jours à 03h30
'day' => '*',
'month' => '*',
'dayofweek' => '*',
],
];La tâche — classes/task/cleanup_task.php :
<?php
// public/admin/tool/cleanupdrafts/classes/task/cleanup_task.php
namespace tool_cleanupdrafts\task;
defined('MOODLE_INTERNAL') || die();
class cleanup_task extends \core\task\scheduled_task {
/** Nom lisible affiché dans « Tâches planifiées ». */
public function get_name(): string {
return get_string('cleanuptask', 'tool_cleanupdrafts');
}
/** Le travail réel. */
public function execute(): void {
global $DB;
$cutoff = time() - (30 * DAYSECS); // 30 jours
$count = $DB->count_records_select('user_draft_example', 'timecreated < ?', [$cutoff]);
// mtrace() écrit dans la sortie du cron (logs) : traçabilité.
mtrace("cleanupdrafts: suppression de {$count} brouillons antérieurs à " . userdate($cutoff));
$DB->delete_records_select('user_draft_example', 'timecreated < ?', [$cutoff]);
}
}Trois choses à retenir sur le cron :
- Moodle exécute un cron unique (
php admin/cli/cron.php, lancé toutes les minutes par le système). Il dispatche vers toutes les tâches planifiées dues. Vous n’installez pas un cron par plugin : vous déclarez une tâche, le cron central l’appelle. get_name()etexecute()sont les deux méthodes obligatoires.execute()fait le travail ; il doit être idempotent et résilient (peut être relancé).mtrace()est leconsole.logdu cron : indispensable pour le débogage et le monitoring (Partie 11). Les tâches et leur historique se pilotent dans Administration du site → Serveur → Tâches planifiées, où l’admin peut ajuster la planification ou lancer la tâche à la demande.
⚠️ Piège :
db/tasks.phpn’est lu qu’à l’installation/upgrade. Après avoir ajouté ou modifié une planification, bumpezversion.php. Et souvenez-vous : la planification déclarée est la valeur par défaut ; l’admin peut la surcharger dans l’interface — ne présumez pas qu’elle tourne à 3h30 sur toutes les instances.
💡 Pour un dev React :
db/tasks.php+scheduled_task= l’équivalent d’un cron job déclaratif (comme les Vercel Cron ou unscheduleGitHub Actions), sauf que l’ordonnanceur est interne à Moodle. Il existe aussi desadhoc_task(tâches ponctuelles mises en file à la volée) — l’analogue d’une file de jobs (BullMQ) — utiles pour déporter un traitement lourd hors de la requête web.
3. Un filtre de texte : filter_shout
Cas d’usage (volontairement trivial pour isoler le mécanisme) : mettre en gras rouge tout mot entouré de *astérisques* dans les textes du site. Un filtre transforme le texte affiché partout où Moodle rend du contenu formaté.
public/filter/shout/
├── version.php
├── classes/
│ └── text_filter.php ← LE point d'extension
└── lang/en/filter_shout.php<?php
// public/filter/shout/classes/text_filter.php
namespace filter_shout;
defined('MOODLE_INTERNAL') || die();
class text_filter extends \core_filters\text_filter {
/**
* Transforme le texte. Appelé par le core pour chaque fragment formaté.
*
* @param string $text le texte (HTML) à filtrer
* @param array $options
* @return string le texte transformé
*/
public function filter($text, array $options = []): string {
// Optimisation : ne rien faire si le motif n'apparaît pas.
if (!is_string($text) || strpos($text, '*') === false) {
return $text;
}
// *mot* → <strong class="text-danger">mot</strong>
return preg_replace('/\*([^*]+)\*/', '<strong class="text-danger">$1</strong>', $text);
}
}Points clés :
- La classe
text_filter(namespace du plugin, base\core_filters\text_filter) et sa méthodefilter()sont le contrat, depuis la modernisation des filtres (Moodle 4.5+). Le core l’appelle sur chaque texte passé parformat_text(). - Optimisez l’entrée triviale : un
strposrapide qui court-circuite quand le motif est absent. Les filtres tournent sur énormément de texte ; un filtre lent ralentit tout le site. - Un filtre s’active et s’ordonne globalement (Administration → Plugins → Filtres → Gérer les filtres), et peut être activé/désactivé par contexte.
⚠️ Piège critique de sécurité : un filtre reçoit du HTML déjà nettoyé. Ne le re-nettoyez pas naïvement (double échappement → texte cassé), mais surtout n’introduisez jamais de HTML non sûr à partir de données non fiables. Ici on n’insère que des balises statiques autour d’un contenu déjà filtré, ce qui est sûr. Un filtre qui injecterait du contenu utilisateur brut créerait une faille XSS amplifiée (le filtre s’applique partout). Manipuler avec précaution — les filtres sont un vecteur XSS classique.
⚠️ Piège de correction : ne filtrez pas ce qui ne doit pas l’être.
preg_replacesur du HTML peut casser des attributs ou du contenu de balises (ex. une URL contenant*). Les filtres sérieux (comme le filtre glossaire du core) prennent grand soin d’éviter les zones sensibles (<a>,<script>, attributs). Pour un vrai filtre, inspirez-vous defilter/glossaryet des helpersfilter_prepare_....
4. Un format de cours minimal : format_cards
Cas d’usage : un format de cours identique au format thématique mais qui affiche les sections comme des cartes. La bonne stratégie pour un format « proche de l’existant » est d’hériter de format_topics et de ne surcharger que le rendu.
public/course/format/cards/
├── version.php
├── format.php ← point d'entrée du rendu (legacy, encore utilisé)
├── lib.php ← classe format_cards extends format_topics
├── classes/output/
│ └── ... ← renderer/section surchargés (approche moderne)
├── templates/
├── lang/en/format_cards.php
└── styles.css<?php
// public/course/format/cards/lib.php
defined('MOODLE_INTERNAL') || die();
// On hérite du format thématique : tout son comportement par défaut est repris.
require_once($CFG->dirroot . '/course/format/topics/lib.php');
class format_cards extends format_topics {
/**
* Exemple de personnalisation : options propres au format.
* (nombre de colonnes de cartes, par exemple)
*/
public function course_format_options($foreditform = false): array {
$options = parent::course_format_options($foreditform);
$options['cardcolumns'] = [
'default' => 3,
'type' => PARAM_INT,
];
if ($foreditform) {
$options['cardcolumns']['label'] = get_string('cardcolumns', 'format_cards');
$options['cardcolumns']['element_type'] = 'select';
$options['cardcolumns']['element_attributes'] = [[2 => '2', 3 => '3', 4 => '4']];
}
return $options;
}
}Le rendu réel (transformer les sections en cartes) se fait en surchargeant les classes d’output (classes/output/courseformat/...) ou, plus simplement, via le CSS (styles.css) et des templates Mustache surchargés. L’API des formats de cours a été modernisée (composants core_courseformat, réactif) : hériter de topics vous fait profiter gratuitement de l’index de cours, du drag-and-drop et de l’AJAX d’édition.
⚠️ Piège : n’écrivez pas un format de cours from scratch sauf besoin radicalement différent. Le format thématique moderne embarque énormément (réactivité, accessibilité, édition AJAX). Repartir de zéro, c’est réimplémenter — mal — des milliers de lignes. Héritez, surchargez le minimum. C’est le même conseil que pour les thèmes (Partie 8) : étendre, pas réécrire.
💡 Pour un dev React : un format de cours moderne est piloté par
core/reactive(le state-manager interne, cousin de Redux — Partie 7). En héritant deformat_topics, vous branchez votre format sur ce moteur réactif sans le réécrire — comme étendre un composant riche existant plutôt que de recréer sa logique d’état.
5. Une condition de disponibilité : availability_grade
Cas d’usage : « cette activité n’est accessible que si l’étudiant a au moins X à une autre activité ». Les conditions de disponibilité pilotent les restrictions d’accès (mode édition → « Ajouter une restriction »). Ce type a une particularité : il a une moitié PHP et une moitié JS obligatoires.
public/availability/condition/grade/
├── version.php
├── classes/
│ ├── condition.php ← logique serveur (le point d'extension)
│ └── frontend.php ← branche le composant de réglage
├── amd/src/
│ └── form.js ← UI de configuration de la condition
├── lang/en/availability_grade.php
└── ...<?php
// public/availability/condition/grade/classes/condition.php
namespace availability_grade;
defined('MOODLE_INTERNAL') || die();
class condition extends \core_availability\condition {
protected int $gradeitemid;
protected float $min;
public function __construct($structure) {
// $structure vient du JSON stocké dans la restriction.
$this->gradeitemid = (int) ($structure->gradeitem ?? 0);
$this->min = (float) ($structure->min ?? 0);
}
/** Sérialise la condition (stockée en JSON dans le cours). */
public function save() {
return (object) ['type' => 'grade', 'gradeitem' => $this->gradeitemid, 'min' => $this->min];
}
/**
* LE point d'extension : l'utilisateur satisfait-il la condition ?
*
* @param bool $not condition inversée ?
* @param \core_availability\info $info
* @param bool $grabthelot
* @param int $userid
* @return bool
*/
public function is_available($not, \core_availability\info $info, $grabthelot, $userid): bool {
$grade = $this->get_user_grade($userid); // helper : lit la note dans le carnet
$allow = ($grade !== null && $grade >= $this->min);
if ($not) {
$allow = !$allow;
}
return $allow;
}
/** Description affichée à l'étudiant (« Non disponible tant que… »). */
public function get_description($full, $not, \core_availability\info $info): string {
return get_string('requires_grade', 'availability_grade', $this->min);
}
protected function get_debug_string(): string {
return "grade#{$this->gradeitemid} >= {$this->min}";
}
}Le contrat :
is_available()est le cœur : renvoietrue/falseselon l’état de l’utilisateur. Gère$not(la restriction peut être inversée : « accessible sauf si… »).save()sérialise la condition ; Moodle stocke tout l’arbre de restrictions d’une activité en JSON dans le champavailabilitydu course module.get_description()produit le message vu par l’étudiant bloqué.- La classe
frontend.php+ le module AMDform.jsfournissent l’UI de configuration dans le formulaire d’activité. C’est obligatoire : sans la moitié JS, l’enseignant ne peut pas régler la condition. C’est ce qui distingueavailability_des autres types — il impose un composant JS.
⚠️ Piège :
is_available()peut être appelée très souvent (pour chaque activité, chaque affichage de cours). Elle doit être rapide et exploiter le cache que Moodle fournit ($info,$grabthelot). Une condition qui fait une requête lourde par activité écroule les pages de cours chargées.
📚 Aller plus loin : la référence est
availability/condition/dans le core (completion,date,grade,group,profile) — cinq conditions exemplaires à lire. Doc : moodledev.io/docs/5.2/apis/plugintypes/availability .
6. Un type de champ personnalisé : customfield_rating
Cas d’usage : un champ « note sur 5 étoiles » utilisable comme champ personnalisé de cours (ou d’utilisateur, de catégorie…). Les custom fields sont un sous-système transversal ; un plugin customfield_ ajoute un nouveau type de champ.
public/customfield/field/rating/
├── version.php
├── classes/
│ ├── field_controller.php ← définition/config du type de champ
│ └── data_controller.php ← stockage/rendu/formulaire d'une valeur
├── lang/en/customfield_rating.php
└── ...field_controllerdécrit le type : sa configuration (ex. « nombre max d’étoiles »), son nom, ses options d’admin.data_controllergère une valeur concrète : dans quelle colonne la stocker (intvalue,charvalue,value…), comment l’afficher, quel élément de formulaire proposer pour la saisir, comment la valider.
<?php
// public/customfield/field/rating/classes/data_controller.php (extrait)
namespace customfield_rating;
defined('MOODLE_INTERNAL') || die();
class data_controller extends \core_customfield\data_controller {
/** Colonne de stockage de la table de données des custom fields. */
public function datafield(): string {
return 'intvalue'; // une note entière
}
/** Élément de formulaire pour saisir la valeur. */
public function instance_form_definition(\MoodleQuickForm $mform): void {
$field = $this->get_field();
$max = (int) $field->get_configdata_property('maxstars') ?: 5;
$options = range(0, $max);
$mform->addElement('select', $this->get_form_element_name(),
$this->get_field()->get_formatted_name(), array_combine($options, $options));
$mform->setType($this->get_form_element_name(), PARAM_INT);
}
/** Rendu en lecture (ex. « ★★★☆☆ »). */
public function export_value() {
$value = (int) $this->get_value();
return str_repeat('★', $value) . str_repeat('☆', 5 - $value);
}
}💡 Pour un dev React :
field_controller= la définition d’un type de champ (comme un « field type » de CMS),data_controller= son composant contrôlé (rendu + input + validation d’une valeur). Le sous-système custom fields est le moteur de formulaires dynamiques de Moodle ; vous y ajoutez un widget réutilisable partout où les custom fields sont branchés (cours, utilisateurs, cohortes, activités récentes…).
7. Survols honnêtes des types complexes
Certains types demandent un chapitre à eux seuls. Voici, pour ne pas vous lancer à l’aveugle, à quoi vous attendre.
7.1 qtype_ — type de question
Le plus riche après mod_. Une question doit gérer : le formulaire d’édition (edit_<type>_form.php), la classe question (rendu, notation, réponses attendues), le renderer, l’import/export (formats GIFT, Moodle XML, Aiken…), le backup, et l’intégration à la banque de questions et au moteur question. Fichiers clés : questiontype.php, question.php, renderer.php, edit_*_form.php, classes/, backup/.
Piège : la notation (grading) d’un
qtypeinteractif (tentatives multiples, pénalités, feedback adaptatif) est subtile — c’est le moteurquestion(state machine des tentatives) qui pilote. Lisez d’abordqtype_shortanswer(simple) avantqtype_multichoice. Ne partez sur unqtypecustom que si aucun type existant ni H5P ne couvre le besoin.
7.2 repository_ — dépôt de fichiers
Branche une source de fichiers dans le sélecteur de fichiers (File picker) : Google Drive, OneDrive, URL, fichiers récents, serveur… Point d’extension : une classe repository dans lib.php avec get_listing(), get_file(), la gestion de l’authentification (souvent OAuth 2 via le sous-système core\oauth2) et le choix copie vs référence (lien vers le fichier distant).
Piège : la gestion des tokens OAuth, du rafraîchissement et des références de fichiers est la vraie difficulté, pas le listing. Réutilisez
core\oauth2; ne réinventez pas le flow d’auth.
7.3 tiny_ — plugin de l’éditeur TinyMCE
Ajoute un bouton/fonction à l’éditeur de texte TinyMCE 6 (l’éditeur par défaut depuis Moodle 4.5). C’est majoritairement du JavaScript (l’API de plugin de TinyMCE) plus un peu de PHP (classes/plugininfo.php pour déclarer le plugin, la config, les capabilities d’usage).
Piège : c’est le seul type de cette liste où le gros du travail est en JS moderne (API TinyMCE), pas en PHP. Prérequis : maîtriser la Partie 7 (AMD, build) et l’API de plugins TinyMCE. Bonne nouvelle pour vous : c’est le type le plus proche de votre zone de confort de dev front.
8. Récapitulatif : la grille de lecture
Quel que soit le type, la démarche est toujours la même :
L’étape 5 est la plus sous-estimée : pour chaque type, Moodle livre des plugins de ce type dans son propre code. Ils sont votre meilleure documentation, testés en production sur des dizaines de milliers de sites :
| Type | Lire dans le core |
|---|---|
tool_ | admin/tool/task, admin/tool/dataprivacy |
filter_ | filter/glossary, filter/mathjaxloader |
format_ | course/format/topics, course/format/weeks |
availability_ | availability/condition/grade, .../completion |
customfield_ | customfield/field/text, .../select, .../date |
qtype_ | question/type/shortanswer (puis multichoice) |
repository_ | repository/url, repository/googledocs |
tiny_ | lib/editor/tiny/plugins/media, .../link |
💡 Pour un dev React : c’est le réflexe « lire l’implémentation de référence d’une interface avant d’écrire la mienne ». Moodle est un monorepo où chaque type d’extension a plusieurs implémentations canoniques sous les yeux. Grep le code du core par le point d’extension (
class ... extends core_availability\condition) et vous trouvez tous les exemples existants.
✏️ Exercices
Exercice 1 — Cron qui ne tourne pas.
Vous avez ajouté une scheduled_task dans db/tasks.php, mais elle n’apparaît pas dans Tâches planifiées et ne s’exécute jamais. Deux causes probables ?
✅ Solution
(1) version.php non bumpé : db/tasks.php n’est (re)lu qu’à l’installation/upgrade ; sans bump, la tâche n’est pas enregistrée. (2) Le cron système n’est pas configuré : Moodle a besoin que php admin/cli/cron.php soit lancé régulièrement (cron OS toutes les minutes). Sans lui, aucune tâche ne tourne — vérifiez Administration → Serveur → Tâches → cron (Moodle avertit si le cron n’a pas tourné récemment). Bonus : purger les caches après le bump.
Exercice 2 — Filtre trop gourmand.
Un filtre applique preg_replace_callback coûteux sur tout texte, même sans motif. Impact et correctif ?
✅ Solution
Impact : le filtre s’exécute sur chaque fragment de texte de chaque page (contenus, noms, aide…), donc un coût inutile est multiplié par des milliers d’appels → pages lentes. Correctif : court-circuiter tôt — un test strpos/stripos rapide qui renvoie $text inchangé si le motif d’accroche est absent (comme au §3), et ne lancer le regex coûteux que sur les fragments concernés. Les filtres du core (glossaire) font systématiquement ce court-circuit.
Exercice 3 — Format de cours from scratch ?
Un client veut un format de cours « en accordéon ». Un dev propose de repartir d’un format_ vide. Que recommandez-vous ?
✅ Solution
Hériter de format_topics (ou format_weeks) et ne surcharger que le rendu (templates Mustache surchargés + CSS/JS pour l’accordéon). Repartir de zéro obligerait à réimplémenter l’index de cours, le drag-and-drop d’édition, la réactivité core/reactive, l’accessibilité et le backup — des milliers de lignes déjà écrites et testées. L’accordéon est une variation de présentation : elle relève du template/CSS, pas d’un moteur de format nouveau. Règle générale des formats : étendre, pas réécrire.
Exercice 4 — Condition sans UI.
Vous écrivez une availability_ : condition.php (avec is_available, save, get_description) est complet et correct, mais l’enseignant ne peut pas configurer la restriction dans le formulaire d’activité. Qu’avez-vous oublié ?
✅ Solution
La moitié JavaScript obligatoire : la classe classes/frontend.php (qui déclare le composant) et le module AMD (amd/src/form.js) qui rend l’UI de réglage de la condition dans le sélecteur « Ajouter une restriction ». Contrairement à la plupart des types, availability_ impose cette moitié front : sans elle, la condition existe côté serveur mais est inconfigurable. Reprendre le pattern de availability/condition/grade (frontend + form.js) et compiler l’AMD (grunt amd), puis purger les caches.
Exercice 5 — Choisir le bon type. Pour chaque besoin, quel type ? (a) « masquer une activité tant qu’une date n’est pas passée » ; (b) « auto-lier les termes d’un glossaire dans tous les textes » ; (c) « ajouter un champ “niveau CECRL” aux cours » ; (d) « nettoyer une table chaque nuit » ; (e) « ajouter un bouton “insérer un symbole” à l’éditeur ».
✅ Solution
(a) availability_ (condition de date — existe déjà : availability_date) ; (b) filter_ (existe : filter_glossary) ; (c) customfield_ appliqué aux cours (souvent le type select existant suffit — pas besoin d’un nouveau type sauf widget spécifique) ; (d) tool_ (ou n’importe quel plugin) avec une scheduled_task ; (e) tiny_ (plugin d’éditeur TinyMCE). Remarque transverse : dans plusieurs cas, un plugin du core existe déjà — le premier réflexe est de vérifier le plugin directory et le core avant d’écrire.
🧠 Quiz de révision
Q1. Quelle est la démarche commune pour aborder n’importe quel type de plugin ?
Réponse
version.php, lang/), implémenter ce point d’extension (plus l’éventuelle moitié JS obligatoire), lire un plugin du core du même type comme référence, puis installer/tester/purger les caches.Q2. Comment fonctionne le cron de Moodle et quel est le contrat d’une tâche planifiée ?
Réponse
admin/cli/cron.php, lancé chaque minute par l’OS) dispatche vers toutes les tâches dues. Un plugin déclare une tâche dans db/tasks.php et fournit une classe \core\task\scheduled_task avec get_name() et execute(). On ne crée pas un cron par plugin ; on branche une tâche sur l’ordonnanceur central. mtrace() sert à journaliser.Q3. Quel est le principal risque de sécurité d’un filtre, et le principal risque de performance ?
Réponse
Q4. Pourquoi hérite-t-on de format_topics plutôt que d’écrire un format de cours de zéro ?
Réponse
core/reactive, l’accessibilité et le backup. En héritant, on récupère tout cela gratuitement et on ne surcharge que le rendu (templates/CSS). Réécrire from scratch = réimplémenter des milliers de lignes déjà testées. Étendre, pas réécrire.Q5. Qu’est-ce qui rend le type availability_ particulier par rapport aux autres ?
Réponse
condition.php (serveur), il faut frontend.php + un module AMD (form.js) pour l’UI de configuration de la restriction. Sans cette moitié front, la condition est inconfigurable par l’enseignant. (Le type tiny_ est encore plus JS-centré.)Chapitre suivant : 07 — Cycle de vie et distribution d’un plugin — versionner correctement, gérer les dépendances et environment.xml, la stratégie de branches git multi-versions de Moodle, distribuer (git/zip/composer), déployer en production (CLI upgrade, mode maintenance), et une introduction à la publication sur moodle.org.