Combien de leçons un apprenant a-t-il terminées sur les douze que compte le module ? Cette question, en apparence anodine, suffisait à justifier un plugin LMS complet dans la première version du cahier des charges — avec son propre système de cours, de quiz, de certificats, alors que le client n’avait besoin, dans un premier temps, que d’un indicateur visuel de progression sur une plateforme déjà construite sur mesure.
La solution retenue tient en une métadonnée par utilisateur et par cours, et un bloc dynamique qui la lit pour afficher une barre de progression. Pas de tables supplémentaires en base, pas de nouvel écran d’administration à apprendre : juste une donnée simple, lue et affichée au bon endroit.
Modéliser la progression sans usine à gaz
Chaque cours est un article du type de contenu cours, avec une liste de leçons stockée en ACF ou en champ répétable maison. La progression d’un apprenant se résume à un tableau de leçons terminées, stocké en métadonnée utilisateur, indexée par identifiant de cours :
update_user_meta( $user_id, 'progression_cours_42', array( 'lecons_terminees' => array( 3, 4, 7 ) ) );
Ce choix de stockage évite de créer une table personnalisée pour un besoin qui reste, à ce stade, de faible volume : quelques dizaines de cours, quelques centaines d’apprenants.
Le bloc de barre de progression
Le bloc s’insère dans le gabarit de la page cours, avec un seul attribut : l’identifiant du cours concerné, généralement pré-rempli automatiquement via le contexte de bloc quand il est utilisé dans un gabarit de type de contenu.
{
"apiVersion": 2,
"name": "wpmoderne/progression-cours",
"title": "Progression du cours",
"usesContext": [ "postId" ],
"render_callback": "wpmoderne_render_progression_cours"
}

Calculer et afficher le pourcentage
Le render_callback compare le nombre de leçons terminées au nombre total de leçons du cours, sans appel réseau supplémentaire — une seule lecture de métadonnée suffit, la liste des leçons étant déjà chargée avec l’article :
function wpmoderne_render_progression_cours( $attributes, $content, $bloc ) {
$cours_id = $bloc->context['postId'];
$user_id = get_current_user_id();
if ( ! $user_id ) {
return '<p>Connectez-vous pour suivre votre progression.</p>';
}
$lecons_total = count( get_field( 'lecons', $cours_id ) );
$progression = get_user_meta( $user_id, "progression_cours_{$cours_id}", true );
$lecons_faites = count( $progression['lecons_terminees'] ?? array() );
$pourcentage = $lecons_total ? round( ( $lecons_faites / $lecons_total ) * 100 ) : 0;
return sprintf(
'<div class="wpm-progression" role="progressbar" aria-valuenow="%1$d" aria-valuemin="0" aria-valuemax="100">
<div class="wpm-progression__barre" style="width:%1$d%%"></div>
<span>%1$d %% terminé (%2$d/%3$d leçons)</span>
</div>',
$pourcentage, $lecons_faites, $lecons_total
);
}
Les attributs role="progressbar" et aria-valuenow assurent qu’un lecteur d’écran restitue correctement l’information, un point souvent oublié sur ce type de composant purement visuel à l’origine.
Marquer une leçon comme terminée
La mise à jour de la progression se fait via une route REST appelée depuis la page de leçon, au moment où l’apprenant clique sur « Leçon terminée » :
- Vérification que l’utilisateur est bien inscrit au cours avant toute mise à jour, pour éviter qu’un identifiant de cours arbitraire ne modifie une progression non autorisée.
- Ajout de l’identifiant de leçon à la liste existante, sans dupliquer si la leçon était déjà marquée comme terminée.
- Retour de la nouvelle progression au front, pour mise à jour immédiate de la barre sans rechargement de page.
Ce que ce bloc ne couvre pas
Le moteur de quiz, qui suppose une notation, des tentatives multiples et un score par question, relève d’un développement bien plus conséquent. La gestion des paiements pour l’accès aux cours, elle, dépend d’une intégration de passerelle de paiement distincte, avec ses propres enjeux de sécurité, qui ne concernent en rien l’affichage de la progression traité ici.
En résumé
Face à une extension LMS complète, un bloc de progression basé sur une simple métadonnée répond au besoin réel sans imposer une architecture surdimensionnée. Ce type de choix, guidé par le volume et la complexité réels du projet plutôt que par la peur de manquer une fonctionnalité future, évite souvent des mois de maintenance inutile.