peuple la banque de quiz avec du vrai contenu, rangé par thème

Un quiz.gift par séquence (01 à 07, le 08 existait déjà), lu par un
nouveau service LecteurGift et synchronisé par quiz:synchroniser-banque
(même esprit que labs:synchroniser) vers un propriétaire technique
jamais archivé (BanqueOfficielle), pour ne pas reproduire la dette du
quiz partagé qui disparaît à la purge RGPD. /banque-quiz regroupe
maintenant par thème comme /bibliotheque.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-13 16:14:36 +02:00
parent 33531de44d
commit a41951d4e6
21 changed files with 628 additions and 32 deletions

View file

@ -0,0 +1,37 @@
<?php
namespace App\Services\Quiz;
use App\Models\Classe;
use App\Models\Etablissement;
use App\Models\User;
use Illuminate\Support\Str;
/**
* Propriétaire des quiz synchronisés depuis paillasse-contenus/ (voir
* quiz:synchroniser-banque) : une classe qui n'est jamais archivée, pour
* que ce contenu officiel ne disparaisse pas à la purge RGPD (90 jours)
* comme le ferait un quiz partagé depuis une classe de prof — voir
* docs/dette.md.
*/
class BanqueOfficielle
{
public const UAI = 'PAILLASSE-BANQUE';
public static function classe(): Classe
{
$etablissement = Etablissement::firstOrCreate(
['uai' => self::UAI],
['nom' => 'Bibliothèque Paillasse'],
);
$prof = User::firstOrCreate(
['email' => 'banque-officielle@paillasse.interne'],
['name' => 'Bibliothèque Paillasse', 'password' => Str::random(40), 'etablissement_id' => $etablissement->id],
);
return Classe::firstOrCreate(
['user_id' => $prof->id, 'etablissement_id' => $etablissement->id, 'nom' => 'Banque officielle'],
);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace App\Services\Quiz;
use App\Models\QuestionQuiz;
use Illuminate\Support\Facades\File;
/**
* Lit un fichier quiz.gift (format GIFT, voir paillasse-contenus/README.md).
* Seuls les types QCM et vrai/faux sont supportés par la banque (schéma de
* questions_quiz) : un type numérique ({#N}, utilisable dans Moodle) est
* ignoré avec un avertissement plutôt que d'inventer une réponse.
*/
class LecteurGift
{
/**
* @return array{questions: array<int, array{enonce: string, type: string, reponse_vrai_faux: ?bool, choix: array<int, array{texte: string, est_correct: bool}>}>, avertissements: array<int, string>}
*/
public static function lire(string $chemin): array
{
$texte = preg_replace('/^[ \t]*\/\/.*$/m', '', File::get($chemin));
$questions = [];
$avertissements = [];
preg_match_all('/::(.*?)::(.*?)\{(.*?)\}/s', $texte, $blocs, PREG_SET_ORDER);
foreach ($blocs as $bloc) {
$enonce = trim($bloc[2]);
$contenu = trim($bloc[3]);
if ($contenu === 'T' || $contenu === 'F') {
$questions[] = [
'enonce' => $enonce,
'type' => QuestionQuiz::VRAI_FAUX,
'reponse_vrai_faux' => $contenu === 'T',
'choix' => [],
];
continue;
}
if (str_starts_with($contenu, '#')) {
$avertissements[] = "question numérique ignorée (type non supporté par la banque) : {$enonce}";
continue;
}
$choix = [];
foreach (preg_split('/\r?\n/', $contenu) as $ligne) {
$ligne = trim($ligne);
if ($ligne === '') {
continue;
}
$choix[] = [
'texte' => trim(substr($ligne, 1)),
'est_correct' => $ligne[0] === '=',
];
}
$questions[] = [
'enonce' => $enonce,
'type' => QuestionQuiz::QCM,
'reponse_vrai_faux' => null,
'choix' => $choix,
];
}
return ['questions' => $questions, 'avertissements' => $avertissements];
}
}