Skip to Content
MoodlePartie 6 — Développer des pluginsLes autres types de plugins en pratique

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 classe scheduled_task.
  • filter_ (filtre de texte) : transforme le texte affiché (glossaire auto-lié, emojis, LaTeX…). Contrat : classes/text_filter.php avec filter().
  • format_ (format de cours) : change la mise en page d’un cours. On hérite de format_topics et 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_ ou tiny_.

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 :

TypeDossierPoint d’extension principalCe qu’il branche
tool_admin/tool/page(s) + db/tasks.phpoutil d’administration + cron
filter_filter/classes/text_filter.php::filter()transformation du texte affiché
format_course/format/classes/output/* + lib.phpmise en page d’un cours
availability_availability/condition/classes/condition.phprègle « accessible si… »
customfield_customfield/field/classes/field_controller.phpnouveau type de champ
qtype_question/type/plusieurs classestype de question
repository_repository/lib.php (classe repository)source de fichiers
tiny_lib/editor/tiny/plugins/JS TinyMCE + plugininfobouton 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.php

version.php (identité — component = 'tool_cleanupdrafts', dossier admin/tool/cleanupdrafts).

Déclarer la tâchedb/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âcheclasses/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() et execute() sont les deux méthodes obligatoires. execute() fait le travail ; il doit être idempotent et résilient (peut être relancé).
  • mtrace() est le console.log du 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.php n’est lu qu’à l’installation/upgrade. Après avoir ajouté ou modifié une planification, bumpez version.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 un schedule GitHub Actions), sauf que l’ordonnanceur est interne à Moodle. Il existe aussi des adhoc_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éthode filter() sont le contrat, depuis la modernisation des filtres (Moodle 4.5+). Le core l’appelle sur chaque texte passé par format_text().
  • Optimisez l’entrée triviale : un strpos rapide 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_replace sur 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 de filter/glossary et des helpers filter_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 de format_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 : renvoie true/false selon 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 champ availability du course module.
  • get_description() produit le message vu par l’étudiant bloqué.
  • La classe frontend.php + le module AMD form.js fournissent 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 distingue availability_ 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_controller décrit le type : sa configuration (ex. « nombre max d’étoiles »), son nom, ses options d’admin.
  • data_controller gè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 qtype interactif (tentatives multiples, pénalités, feedback adaptatif) est subtile — c’est le moteur question (state machine des tentatives) qui pilote. Lisez d’abord qtype_shortanswer (simple) avant qtype_multichoice. Ne partez sur un qtype custom 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 :

TypeLire 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

Identifier le type (arbre de décision), trouver le point d’extension (la classe/fonction que le core appelle), copier l’identité (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

Un cron central (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

Sécurité : introduire du HTML non sûr (XSS amplifiée, car le filtre s’applique partout) ou casser l’échappement existant. Performance : s’exécuter lourdement sur tout texte — d’où l’obligation de court-circuiter quand le motif d’accroche est absent.

Q4. Pourquoi hérite-t-on de format_topics plutôt que d’écrire un format de cours de zéro ?

Réponse

Le format thématique moderne embarque l’index de cours, le drag-and-drop d’édition, la réactivité 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

Il impose une moitié JavaScript : au-delà de 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.