108 lines
4.1 KiB
PHP
108 lines
4.1 KiB
PHP
<?php
|
|
// Read-only export of the Moodle quiz question bank as ONE JSON object on
|
|
// stdout, consumed by the Grist QuizBank push (SP3). Never writes to the DB.
|
|
// Run: docker cp ops/export-quiz-bank.php moodle:/tmp/ &&
|
|
// docker exec moodle php /tmp/export-quiz-bank.php > quizbank.json
|
|
//
|
|
// Moodle 4.5 schema chain (quiz_slots no longer carries questionid):
|
|
// quiz_slots.id -> question_references (component='mod_quiz',
|
|
// questionarea='slot', itemid=slot.id) -> questionbankentryid ->
|
|
// question_versions (latest version; qr.version NULL = "always latest")
|
|
// -> question. Multichoice flag in qtype_multichoice_options.single,
|
|
// choices in question_answers (fraction>0 = correct).
|
|
//
|
|
// Output shape:
|
|
// {courses: {"<courseid>": {quizzes: [{quiz_n, cmid, name, gradepass,
|
|
// questions: [{id, text, single, choices: [{id, text, correct,
|
|
// feedback}]}]}]}}}
|
|
//
|
|
// WARNING: the output contains the CORRECT flags and feedback — it must
|
|
// only ever land in Grist (API-key protected), NEVER in this public repo.
|
|
define('CLI_SCRIPT', true);
|
|
require('/var/www/html/config.php');
|
|
|
|
$quizmoduleid = $DB->get_field('modules', 'id', ['name' => 'quiz'], MUST_EXIST);
|
|
|
|
// Quizzes are named "Quiz N : ..." — name order is module order.
|
|
$quizzes = $DB->get_records_sql("
|
|
SELECT q.id, q.course, q.name, cm.id AS cmid
|
|
FROM {quiz} q
|
|
JOIN {course_modules} cm ON cm.instance = q.id AND cm.module = :mod
|
|
WHERE q.course > 1
|
|
ORDER BY q.course ASC, q.name ASC", ['mod' => $quizmoduleid]);
|
|
|
|
$out = ['courses' => []];
|
|
|
|
foreach ($quizzes as $quiz) {
|
|
$gradepass = $DB->get_field('grade_items', 'gradepass',
|
|
['itemmodule' => 'quiz', 'iteminstance' => $quiz->id]);
|
|
$gradepass = $gradepass ? (float)$gradepass : 0.0; // may be 0/NULL
|
|
|
|
$quizn = 0;
|
|
if (preg_match('/^Quiz\s+(\d+)/u', $quiz->name, $m)) {
|
|
$quizn = (int)$m[1];
|
|
}
|
|
|
|
$slotrows = $DB->get_records_sql("
|
|
SELECT qs.id AS slotid, qs.slot, qv.questionid
|
|
FROM {quiz_slots} qs
|
|
JOIN {question_references} qr
|
|
ON qr.component = 'mod_quiz'
|
|
AND qr.questionarea = 'slot'
|
|
AND qr.itemid = qs.id
|
|
JOIN {question_versions} qv
|
|
ON qv.questionbankentryid = qr.questionbankentryid
|
|
AND qv.version = (SELECT MAX(v.version)
|
|
FROM {question_versions} v
|
|
WHERE v.questionbankentryid
|
|
= qr.questionbankentryid)
|
|
WHERE qs.quizid = :quizid
|
|
ORDER BY qs.slot ASC", ['quizid' => $quiz->id]);
|
|
|
|
$questions = [];
|
|
foreach ($slotrows as $row) {
|
|
$question = $DB->get_record('question',
|
|
['id' => $row->questionid], '*', MUST_EXIST);
|
|
if ($question->qtype !== 'multichoice') {
|
|
fwrite(STDERR, "skip q{$question->id}: qtype "
|
|
. "{$question->qtype} (only multichoice supported)\n");
|
|
continue;
|
|
}
|
|
$opts = $DB->get_record('qtype_multichoice_options',
|
|
['questionid' => $question->id], '*', MUST_EXIST);
|
|
$answers = $DB->get_records('question_answers',
|
|
['question' => $question->id], 'id ASC');
|
|
|
|
$choices = [];
|
|
foreach ($answers as $a) {
|
|
$choices[] = [
|
|
'id' => (int)$a->id,
|
|
'text' => $a->answer,
|
|
'correct' => (float)$a->fraction > 0,
|
|
'feedback' => $a->feedback,
|
|
];
|
|
}
|
|
$questions[] = [
|
|
'id' => (int)$question->id,
|
|
'text' => $question->questiontext,
|
|
'single' => (int)$opts->single,
|
|
'choices' => $choices,
|
|
];
|
|
}
|
|
|
|
$courseid = (string)$quiz->course;
|
|
if (!isset($out['courses'][$courseid])) {
|
|
$out['courses'][$courseid] = ['quizzes' => []];
|
|
}
|
|
$out['courses'][$courseid]['quizzes'][] = [
|
|
'quiz_n' => $quizn,
|
|
'cmid' => (int)$quiz->cmid,
|
|
'name' => $quiz->name,
|
|
'gradepass' => $gradepass,
|
|
'questions' => $questions,
|
|
];
|
|
}
|
|
|
|
echo json_encode($out,
|
|
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), "\n";
|