Skip to content

Commit

Permalink
Merge branch 'master_MDL-73808-question-bank-upgrade-bkp' of https://…
Browse files Browse the repository at this point in the history
  • Loading branch information
stronk7 committed Mar 27, 2022
2 parents efda17d + 6045e6b commit c3f1c89
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 138 deletions.
176 changes: 172 additions & 4 deletions lib/db/upgrade.php
Expand Up @@ -3926,14 +3926,182 @@ function xmldb_main_upgrade($oldversion) {
}

if ($oldversion < 2022020200.02) {
// Next, split question records into the new tables.
upgrade_migrate_question_table();
// Define a new temporary field in the question_bank_entries tables.
// Creating temporary field questionid to populate the data in question version table.
// This will make sure the appropriate question id is inserted the version table without making any complex joins.
$table = new xmldb_table('question_bank_entries');
$field = new xmldb_field('questionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_TYPE_INTEGER);
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

$transaction = $DB->start_delegated_transaction();
upgrade_set_timeout(3600);
// Create the data for the question_bank_entries table with, including the new temporary field.
$sql = <<<EOF
INSERT INTO {question_bank_entries}
(questionid, questioncategoryid, idnumber, ownerid)
SELECT id, category, idnumber, createdby
FROM {question} q
EOF;

// Inserting question_bank_entries data.
$DB->execute($sql);

$transaction->allow_commit();

// Main savepoint reached.
upgrade_main_savepoint(true, 2022020200.02);
}

// Finally, drop fields from question table.
if ($oldversion < 2022020200.03) {
$transaction = $DB->start_delegated_transaction();
upgrade_set_timeout(3600);
// Create the question_versions using that temporary field.
$sql = <<<EOF
INSERT INTO {question_versions}
(questionbankentryid, questionid, status)
SELECT
qbe.id,
q.id,
CASE
WHEN q.hidden > 0 THEN 'hidden'
ELSE 'ready'
END
FROM {question_bank_entries} qbe
INNER JOIN {question} q ON qbe.questionid = q.id
EOF;

// Inserting question_versions data.
$DB->execute($sql);

$transaction->allow_commit();

// Dropping temporary field questionid.
$table = new xmldb_table('question_bank_entries');
$field = new xmldb_field('questionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_TYPE_INTEGER);
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}

// Main savepoint reached.
upgrade_main_savepoint(true, 2022020200.03);
}

if ($oldversion < 2022020200.04) {
$transaction = $DB->start_delegated_transaction();
upgrade_set_timeout(3600);
// Create the base data for the random questions in the set_references table.
// This covers most of the hard work in one go.
$concat = $DB->sql_concat("'{\"questioncategoryid\":\"'", 'q.category', "'\",\"includingsubcategories\":\"'",
'qs.includingsubcategories', "'\"}'");
$sql = <<<EOF
INSERT INTO {question_set_references}
(usingcontextid, component, questionarea, itemid, questionscontextid, filtercondition)
SELECT
c.id,
'mod_quiz',
'slot',
qs.id,
qc.contextid,
$concat
FROM {question} q
INNER JOIN {quiz_slots} qs on q.id = qs.questionid
INNER JOIN {course_modules} cm ON cm.instance = qs.quizid AND cm.module = :quizmoduleid
INNER JOIN {context} c ON cm.id = c.instanceid AND c.contextlevel = :contextmodule
INNER JOIN {question_categories} qc ON qc.id = q.category
WHERE q.qtype = :random
EOF;

// Inserting question_set_references data.
$DB->execute($sql, [
'quizmoduleid' => $DB->get_field('modules', 'id', ['name' => 'quiz']),
'contextmodule' => CONTEXT_MODULE,
'random' => 'random',
]);

$transaction->allow_commit();

// Main savepoint reached.
upgrade_main_savepoint(true, 2022020200.04);
}

if ($oldversion < 2022020200.05) {
$transaction = $DB->start_delegated_transaction();
upgrade_set_timeout(3600);

// Count all the slot tags to be migrated (for progress bar).
$total = $DB->count_records('quiz_slot_tags');
$pbar = new progress_bar('migratequestiontags', 1000, true);
$i = 0;
// Updating slot_tags for random question tags.
// Now fetch any quiz slot tags and update those slot details into the question_set_references.
$slottags = $DB->get_recordset('quiz_slot_tags', [], 'slotid ASC');

$tagstrings = [];
$lastslot = null;
$runinsert = function (int $lastslot, array $tagstrings) use ($DB) {
$conditiondata = $DB->get_field('question_set_references', 'filtercondition',
['itemid' => $lastslot, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
$condition = json_decode($conditiondata);
$condition->tags = $tagstrings;
$DB->set_field('question_set_references', 'filtercondition', json_encode($condition),
['itemid' => $lastslot, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
};

foreach ($slottags as $tag) {
upgrade_set_timeout(3600);
if ($lastslot && $tag->slotid != $lastslot) {
if (!empty($tagstrings)) {
// Insert the data.
$runinsert($lastslot, $tagstrings);
}
// Prepare for the next slot id.
$tagstrings = [];
}

$lastslot = $tag->slotid;
$tagstrings[] = "{$tag->tagid},{$tag->tagname}";
// Update progress.
$i++;
$pbar->update($i, $total, "Migrating question tags - $i/$total.");
}
if ($tagstrings) {
$runinsert($lastslot, $tagstrings);
}
$slottags->close();

$transaction->allow_commit();
// Main savepoint reached.
upgrade_main_savepoint(true, 2022020200.05);
}

if ($oldversion < 2022020200.06) {
$transaction = $DB->start_delegated_transaction();
upgrade_set_timeout(3600);
// Create question_references record for each question.
// Except if qtype is random. That case is handled by question_set_reference.
$sql = "INSERT INTO {question_references}
(usingcontextid, component, questionarea, itemid, questionbankentryid)
SELECT c.id, 'mod_quiz', 'slot', qs.id, qv.questionbankentryid
FROM {question} q
JOIN {question_versions} qv ON q.id = qv.questionid
JOIN {quiz_slots} qs ON q.id = qs.questionid
JOIN {modules} m ON m.name = 'quiz'
JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = qs.quizid
JOIN {context} c ON c.instanceid = cm.id AND c.contextlevel = " . CONTEXT_MODULE . "
WHERE q.qtype <> 'random'";

// Inserting question_references data.
$DB->execute($sql);

$transaction->allow_commit();
// Main savepoint reached.
upgrade_main_savepoint(true, 2022020200.06);
}

// Finally, drop fields from question table.
if ($oldversion < 2022020200.07) {
// Define fields to be dropped from questions.
$table = new xmldb_table('question');

Expand Down Expand Up @@ -3976,7 +4144,7 @@ function xmldb_main_upgrade($oldversion) {
}

// Main savepoint reached.
upgrade_main_savepoint(true, 2022020200.03);
upgrade_main_savepoint(true, 2022020200.07);
}

if ($oldversion < 2022021100.01) {
Expand Down
134 changes: 0 additions & 134 deletions lib/db/upgradelib.php
Expand Up @@ -1275,140 +1275,6 @@ function upgrade_calendar_override_events_fix(stdClass $info, bool $output = tru
return $return;
}

/**
* Split question table in 2 new tables:
*
* question_bank_entries
* question_versions
*
* Move the random questions records to the following table:
* question_set_reference
*
* Move the question related records from quiz_slots table to:
* question_reference
*
* Move the tag related data from quiz_slot_tags to:
* question_references
*
* For more information: https://moodle.org/mod/forum/discuss.php?d=417599#p1688163
*/
function upgrade_migrate_question_table(): void {
global $DB;

// Maximum size of array.
$maxlength = 30000;

// Array of question_versions objects.
$questionversions = [];

// Array of question_set_references objects.
$questionsetreferences = [];

// The actual update/insert done with multiple DB access, so we do it in a transaction.
$transaction = $DB->start_delegated_transaction();

// Count all questions to be migrated (for progress bar).
$total = $DB->count_records('question');
$pbar = new progress_bar('migratequestions', 1000, true);
$i = 0;
// Get all records in question table, we dont need the subquestions, just regular questions and random questions.
$questions = $DB->get_recordset('question');
foreach ($questions as $question) {
upgrade_set_timeout(60);
// Populate table question_bank_entries.
$questionbankentry = new \stdClass();
$questionbankentry->questioncategoryid = $question->category;
$questionbankentry->idnumber = $question->idnumber;
$questionbankentry->ownerid = $question->createdby;
// Insert a question_bank_entries record here as the id is required to populate other tables.
$questionbankentry->id = $DB->insert_record('question_bank_entries', $questionbankentry);

// Create question_versions records to be added.
$questionversion = new \stdClass();
$questionversion->questionbankentryid = $questionbankentry->id;
$questionversion->questionid = $question->id;
$questionstatus = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
if ((int)$question->hidden === 1) {
$questionstatus = \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN;
}
$questionversion->status = $questionstatus;
$questionversions[] = $questionversion;

// Insert the records if the array limit is reached.
if (count($questionversions) >= $maxlength) {
$DB->insert_records('question_versions', $questionversions);
$questionversions = [];
}

// Create question_set_references records to be added.
// Only if the question type is random and the question is used in a quiz.
if ($question->qtype === 'random') {
$quizslots = $DB->get_records('quiz_slots', ['questionid' => $question->id]);
foreach ($quizslots as $quizslot) {
$questionsetreference = new \stdClass();
$cm = get_coursemodule_from_instance('quiz', $quizslot->quizid);
$questionsetreference->usingcontextid = context_module::instance($cm->id)->id;
$questionsetreference->component = 'mod_quiz';
$questionsetreference->questionarea = 'slot';
$questionsetreference->itemid = $quizslot->id;
$catcontext = $DB->get_field('question_categories', 'contextid', ['id' => $question->category]);
$questionsetreference->questionscontextid = $catcontext;
// Migration of the slot tags and filter identifiers from slot table to filtercondition.
$filtercondition = new stdClass();
$filtercondition->questioncategoryid = $question->category;
$filtercondition->includingsubcategories = $quizslot->includingsubcategories;
$tags = $DB->get_records('quiz_slot_tags', ['slotid' => $quizslot->id]);
$tagstrings = [];
foreach ($tags as $tag) {
$tagstrings [] = "{$tag->id},{$tag->name}";
}
if (!empty($tagstrings)) {
$filtercondition->tags = $tagstrings;
}
$questionsetreference->filtercondition = json_encode($filtercondition);

$questionsetreferences[] = $questionsetreference;

// Insert the records if the array limit is reached.
if (count($questionsetreferences) >= $maxlength) {
$DB->insert_records('question_set_references', $questionsetreferences);
$questionsetreferences = [];
}
}
}
// Update progress.
$i++;
$pbar->update($i, $total, "Migrating questions - $i/$total.");
}
$questions->close();

// Insert the remaining question_versions records.
if ($questionversions) {
$DB->insert_records('question_versions', $questionversions);
}

// Insert the remaining question_set_references records.
if ($questionsetreferences) {
$DB->insert_records('question_set_references', $questionsetreferences);
}

// Create question_references record for each question.
// Except if qtype is random. That case is handled by question_set_reference.
$sql = "INSERT INTO {question_references}
(usingcontextid, component, questionarea, itemid, questionbankentryid)
SELECT c.id, 'mod_quiz', 'slot', qs.id, qv.questionbankentryid
FROM {question} q
JOIN {question_versions} qv ON q.id = qv.questionid
JOIN {quiz_slots} qs ON q.id = qs.questionid
JOIN {modules} m ON m.name = 'quiz'
JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = qs.quizid
JOIN {context} c ON c.instanceid = cm.id AND c.contextlevel = " . CONTEXT_MODULE . "
WHERE q.qtype <> 'random'";
$DB->execute($sql);

$transaction->allow_commit();
}

/**
* Add a new item at the end of the usermenu.
*
Expand Down

0 comments on commit c3f1c89

Please sign in to comment.