mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-71696 core_question: Implement new database schema
This commit implements the new database structure for versioning in question. It also does the migration of current data to the new structure. Co-Authored-By: Safat Shahin <safatshahin@catalyst-au.net> Co-Authored-By: Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
This commit is contained in:
parent
c352b70022
commit
c34b89a3c2
@ -426,9 +426,11 @@ $string['privacy:metadata:database:question_attempts'] = 'The information about
|
||||
$string['privacy:metadata:database:question_attempts:flagged'] = 'An indication that the user has flagged this question within the attempt.';
|
||||
$string['privacy:metadata:database:question_attempts:responsesummary'] = 'A summary of the question response.';
|
||||
$string['privacy:metadata:database:question_attempts:timemodified'] = 'The time that the question attempt was updated.';
|
||||
$string['privacy:metadata:link:qbehaviour'] = 'The question subsystem makes use of the Question behaviours plugin type.';
|
||||
$string['privacy:metadata:link:qformat'] = 'The question subsystem makes use of the Question import/export formats plugin type for the purpose of importing and exporting questions in different formats.';
|
||||
$string['privacy:metadata:link:qtype'] = 'The question subsystem interacts with the Question types plugin type which contains the different types of questions.';
|
||||
$string['privacy:metadata:database:question_bank_entries'] = 'The details about a specific question bank entry.';
|
||||
$string['privacy:metadata:database:question_bank_entries:ownerid'] = 'The person who owns the question bank entry.';
|
||||
$string['privacy:metadata:link:qbehaviour'] = 'The Question subsystem makes use of the Question Behaviour plugintype.';
|
||||
$string['privacy:metadata:link:qformat'] = 'The Question subsystem makes use of the Question Format plugintype for the purpose of importing and exporting questions in different formats.';
|
||||
$string['privacy:metadata:link:qtype'] = 'The Question subsystem interacts with the Question Type plugintype which contains the different types of questions.';
|
||||
$string['questionbehaviouradminsetting'] = 'Question behaviour settings';
|
||||
$string['questionbehavioursdisabled'] = 'Question behaviours to disable';
|
||||
$string['questionbehavioursdisabledexplained'] = 'Enter a comma-separated list of behaviours you do not want to appear in the drop-down menu.';
|
||||
|
@ -1402,10 +1402,9 @@
|
||||
<INDEX NAME="contextididnumber" UNIQUE="true" FIELDS="contextid, idnumber"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="question" COMMENT="The questions themselves">
|
||||
<TABLE NAME="question" COMMENT="This table stores the definition of one version of a question.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="category" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="parent" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="questiontext" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
|
||||
@ -1417,26 +1416,83 @@
|
||||
<FIELD NAME="qtype" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="length" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
|
||||
<FIELD NAME="stamp" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="version" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="hidden" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="time question was created"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="time that question was last modified"/>
|
||||
<FIELD NAME="createdby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="userid of person who created this question"/>
|
||||
<FIELD NAME="modifiedby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="userid of person who last edited this question"/>
|
||||
<FIELD NAME="idnumber" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="category" TYPE="foreign" FIELDS="category" REFTABLE="question_categories" REFFIELDS="id"/>
|
||||
<KEY NAME="parent" TYPE="foreign" FIELDS="parent" REFTABLE="question" REFFIELDS="id" COMMENT="note that to make this recursive FK working someday, the parent field must be declared NULL"/>
|
||||
<KEY NAME="createdby" TYPE="foreign" FIELDS="createdby" REFTABLE="user" REFFIELDS="id" COMMENT="foreign (createdby) references user (id)"/>
|
||||
<KEY NAME="modifiedby" TYPE="foreign" FIELDS="modifiedby" REFTABLE="user" REFFIELDS="id" COMMENT="foreign (modifiedby) references user (id)"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="qtype" UNIQUE="false" FIELDS="qtype"/>
|
||||
<INDEX NAME="categoryidnumber" UNIQUE="true" FIELDS="category, idnumber"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="question_bank_entries" COMMENT="Each question bank entry. This table has one row for each question that appears in the question bank.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="questioncategoryid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="ID of the category this question is part of."/>
|
||||
<FIELD NAME="idnumber" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Unique identifier, useful especially for mapping to external entities."/>
|
||||
<FIELD NAME="ownerid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="userid of person who owns this question bank entry."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="questioncategoryid" TYPE="foreign" FIELDS="questioncategoryid" REFTABLE="question_categories" REFFIELDS="id"/>
|
||||
<KEY NAME="ownerid" TYPE="foreign" FIELDS="ownerid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="categoryidnumber" UNIQUE="true" FIELDS="questioncategoryid, idnumber"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="question_versions" COMMENT="A join table linking the different question version definitions in the question table to the question_bank_entires.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="questionbankentryid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="ID of the question bank entry this question version is part of."/>
|
||||
<FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Version number for the question where the first version is always 1."/>
|
||||
<FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The question ID."/>
|
||||
<FIELD NAME="status" TYPE="char" LENGTH="10" NOTNULL="false" DEFAULT="ready" SEQUENCE="false" COMMENT="If the question is ready, hidden or draft"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="questionbankentryid" TYPE="foreign" FIELDS="questionbankentryid" REFTABLE="question_bank_entries" REFFIELDS="id"/>
|
||||
<KEY NAME="questionid" TYPE="foreign" FIELDS="questionid" REFTABLE="question" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="question_references" COMMENT="Records where a specific question is used.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="usingcontextid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Context where question is used."/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Component (e.g. mod_quiz or core_question)"/>
|
||||
<FIELD NAME="questionarea" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false" COMMENT="Depending on the component, which area the question is used in (e.g. slot for quiz)."/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Plugin specific id (e.g. slotid for quiz) where its used."/>
|
||||
<FIELD NAME="questionbankentryid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="ID of the question bank entry this question is part of."/>
|
||||
<FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Version number for the question where NULL means use the latest ready version."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="usingcontextid" TYPE="foreign" FIELDS="usingcontextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
<KEY NAME="questionbankentryid" TYPE="foreign" FIELDS="questionbankentryid" REFTABLE="question_bank_entries" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="question_set_references" COMMENT="Records where groups of questions are used.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="usingcontextid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Context where question is used."/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Component (e.g. mod_quiz)"/>
|
||||
<FIELD NAME="questionarea" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false" COMMENT="Depending on the component, which area the question is used in (e.g. slot for quiz)."/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Plugin specific id (e.g. slotid for quiz) where its used."/>
|
||||
<FIELD NAME="questionscontextid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Context questions come from."/>
|
||||
<FIELD NAME="filtercondition" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Filter expression in json format"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="usingcontextid" TYPE="foreign" FIELDS="usingcontextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
<KEY NAME="questionscontextid" TYPE="foreign" FIELDS="questionscontextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="question_answers" COMMENT="Answers, with a fractional grade (0-1) and feedback">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
|
@ -3809,5 +3809,158 @@ privatefiles,moodle|/user/files.php';
|
||||
upgrade_main_savepoint(true, 2022012100.02);
|
||||
}
|
||||
|
||||
// Introduce question versioning to core.
|
||||
// First, create the new tables.
|
||||
if ($oldversion < 2022020200.01) {
|
||||
// Define table question_bank_entries to be created.
|
||||
$table = new xmldb_table('question_bank_entries');
|
||||
|
||||
// Adding fields to table question_bank_entries.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('questioncategoryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
|
||||
$table->add_field('idnumber', XMLDB_TYPE_CHAR, '100', null, null, null, null);
|
||||
$table->add_field('ownerid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
|
||||
// Adding keys to table question_bank_entries.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('questioncategoryid', XMLDB_KEY_FOREIGN, ['questioncategoryid'], 'question_categories', ['id']);
|
||||
$table->add_key('ownerid', XMLDB_KEY_FOREIGN, ['ownerid'], 'user', ['id']);
|
||||
|
||||
// Conditionally launch create table for question_bank_entries.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Create category id and id number index.
|
||||
$index = new xmldb_index('categoryidnumber', XMLDB_INDEX_UNIQUE, ['questioncategoryid', 'idnumber']);
|
||||
|
||||
// Conditionally launch add index categoryidnumber.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Define table question_versions to be created.
|
||||
$table = new xmldb_table('question_versions');
|
||||
|
||||
// Adding fields to table question_versions.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('questionbankentryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('version', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 1);
|
||||
$table->add_field('questionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
|
||||
$table->add_field('status', XMLDB_TYPE_CHAR, '10', null, XMLDB_NOTNULL, null, 'ready');
|
||||
|
||||
// Adding keys to table question_versions.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('questionbankentryid', XMLDB_KEY_FOREIGN, ['questionbankentryid'], 'question_bank_entries', ['id']);
|
||||
$table->add_key('questionid', XMLDB_KEY_FOREIGN, ['questionid'], 'question', ['id']);
|
||||
|
||||
// Conditionally launch create table for question_versions.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table question_references to be created.
|
||||
$table = new xmldb_table('question_references');
|
||||
|
||||
// Adding fields to table question_references.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('usingcontextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
|
||||
$table->add_field('questionarea', XMLDB_TYPE_CHAR, '50', null, null, null, null);
|
||||
$table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('questionbankentryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('version', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
|
||||
// Adding keys to table question_references.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('usingcontextid', XMLDB_KEY_FOREIGN, ['usingcontextid'], 'context', ['id']);
|
||||
$table->add_key('questionbankentryid', XMLDB_KEY_FOREIGN, ['questionbankentryid'], 'question_bank_entries', ['id']);
|
||||
|
||||
// Conditionally launch create table for question_references.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table question_set_references to be created.
|
||||
$table = new xmldb_table('question_set_references');
|
||||
|
||||
// Adding fields to table question_set_references.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('usingcontextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
|
||||
$table->add_field('questionarea', XMLDB_TYPE_CHAR, '50', null, null, null, null);
|
||||
$table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('questionscontextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
|
||||
$table->add_field('filtercondition', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
|
||||
// Adding keys to table question_set_references.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('usingcontextid', XMLDB_KEY_FOREIGN, ['usingcontextid'], 'context', ['id']);
|
||||
$table->add_key('itemid', XMLDB_KEY_FOREIGN, ['itemid'], 'quiz_slots', ['id']);
|
||||
$table->add_key('questionscontextid', XMLDB_KEY_FOREIGN, ['questionscontextid'], 'context', ['id']);
|
||||
|
||||
// Conditionally launch create table for question_set_references.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022020200.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2022020200.02) {
|
||||
// Next, split question records into the new tables.
|
||||
upgrade_migrate_question_table();
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022020200.02);
|
||||
}
|
||||
|
||||
// Finally, drop fields from question table.
|
||||
if ($oldversion < 2022020200.03) {
|
||||
// Define fields to be dropped from questions.
|
||||
$table = new xmldb_table('question');
|
||||
|
||||
$field = new xmldb_field('version');
|
||||
// Conditionally launch drop field version.
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
$field = new xmldb_field('hidden');
|
||||
// Conditionally launch drop field hidden.
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Define index categoryidnumber (not unique) to be dropped form question.
|
||||
$index = new xmldb_index('categoryidnumber', XMLDB_INDEX_UNIQUE, ['category', 'idnumber']);
|
||||
|
||||
// Conditionally launch drop index categoryidnumber.
|
||||
if ($dbman->index_exists($table, $index)) {
|
||||
$dbman->drop_index($table, $index);
|
||||
}
|
||||
|
||||
// Define key category (foreign) to be dropped form questions.
|
||||
$key = new xmldb_key('category', XMLDB_KEY_FOREIGN, ['category'], 'question_categories', ['id']);
|
||||
|
||||
// Launch drop key category.
|
||||
$dbman->drop_key($table, $key);
|
||||
|
||||
$field = new xmldb_field('idnumber');
|
||||
// Conditionally launch drop field idnumber.
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
$field = new xmldb_field('category');
|
||||
// Conditionally launch drop field category.
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022020200.03);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1274,3 +1274,137 @@ function upgrade_calendar_override_events_fix(stdClass $info, bool $output = tru
|
||||
upgrade_calendar_events_mtrace('', $output);
|
||||
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();
|
||||
}
|
||||
|
@ -114,8 +114,19 @@ completely removed from Moodle core too.
|
||||
Refer to upgrade.php to see transitioning from similar plugin criteria to core
|
||||
Refer to completion/upgrade.txt for additional information.
|
||||
* The method enable_plugin() has been added to the core_plugininfo\base class and it has been implemented by all the plugininfo
|
||||
classes extending it. When possible, the enable_plugin() method will store these changes into the config_log table, to let admins
|
||||
check when and who has enabled/disabled plugins.
|
||||
classes extending it. When possible, the enable_plugin() method will store these changes into the config_log table, to let admins
|
||||
check when and who has enabled/disabled plugins.
|
||||
* New tables are included as a part of https://docs.moodle.org/dev/Question_bank_improvements_for_Moodle_4.0
|
||||
- question_bank_entries -> Each question bank entry. This table has one row for each question that appears in the question bank.
|
||||
- question_versions -> Versions of the question. Store the data that defines how a particular version of the question works.
|
||||
- question_references -> Records where a specific question is used.
|
||||
- question_set_references -> Records where groups of questions are used (e.g.: Random questions).
|
||||
Also, some tables have been updated or removed:
|
||||
- question (fields migrated to the new tables)
|
||||
- quiz_slot (fields removed)
|
||||
- quiz_slot_tags (table removed)
|
||||
During the upgrade, data from the question table will be copied to the new tables. After this process,
|
||||
the data copied will be removed from question table quiz_slot and finally the the quiz_slot_tags table will be removed.
|
||||
* Final deprecation: The following functions along with associated tests have been removed:
|
||||
- core_grades_external::get_grades
|
||||
- core_grades_external::get_grade_item
|
||||
|
43
question/classes/local/bank/question_version_status.php
Normal file
43
question/classes/local/bank/question_version_status.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_question\local\bank;
|
||||
|
||||
/**
|
||||
* Class question_version_status contains the statuses for a question.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_version_status {
|
||||
|
||||
/**
|
||||
* Const if the question is ready to use.
|
||||
*/
|
||||
const QUESTION_STATUS_READY = 'ready';
|
||||
|
||||
/**
|
||||
* Const if the question is hidden.
|
||||
*/
|
||||
const QUESTION_STATUS_HIDDEN = 'hidden';
|
||||
|
||||
/**
|
||||
* const if the question is in draft.
|
||||
*/
|
||||
const QUESTION_STATUS_DRAFT = 'draft';
|
||||
}
|
@ -126,6 +126,10 @@ class provider implements
|
||||
// The 'question_statistics' table contains aggregated statistics about responses.
|
||||
// It does not contain any identifiable user data.
|
||||
|
||||
$items->add_database_table('question_bank_entries', [
|
||||
'ownerid' => 'privacy:metadata:database:question_bank_entries:ownerid',
|
||||
], 'privacy:metadata:database:question_bank_entries');
|
||||
|
||||
// The question subsystem makes use of the qtype, qformat, and qbehaviour plugin types.
|
||||
$items->add_plugintype_link('qtype', [], 'privacy:metadata:link:qtype');
|
||||
$items->add_plugintype_link('qformat', [], 'privacy:metadata:link:qformat');
|
||||
@ -336,12 +340,13 @@ class provider implements
|
||||
|
||||
// A user may have created or updated a question.
|
||||
// Questions are linked against a question category, which has a contextid field.
|
||||
$sql = "SELECT cat.contextid
|
||||
$sql = "SELECT qc.contextid
|
||||
FROM {question} q
|
||||
INNER JOIN {question_categories} cat ON cat.id = q.category
|
||||
WHERE
|
||||
q.createdby = :useridcreated OR
|
||||
q.modifiedby = :useridmodified";
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE q.createdby = :useridcreated
|
||||
OR q.modifiedby = :useridmodified";
|
||||
$params = [
|
||||
'useridcreated' => $userid,
|
||||
'useridmodified' => $userid,
|
||||
@ -363,9 +368,10 @@ class provider implements
|
||||
// Questions are linked against a question category, which has a contextid field.
|
||||
$sql = "SELECT q.createdby, q.modifiedby
|
||||
FROM {question} q
|
||||
JOIN {question_categories} cat
|
||||
ON cat.id = q.category
|
||||
WHERE cat.contextid = :contextid";
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid = :contextid";
|
||||
|
||||
$params = [
|
||||
'contextid' => $context->id
|
||||
@ -487,7 +493,8 @@ class provider implements
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
* @param \context $context The specific context to delete data for.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
@ -496,17 +503,19 @@ class provider implements
|
||||
// user. They are still exported in the list of a users data, but they are not removed.
|
||||
// The userid is instead anonymised.
|
||||
|
||||
$DB->set_field_select('question', 'createdby', 0,
|
||||
'category IN (SELECT id FROM {question_categories} WHERE contextid = :contextid)',
|
||||
[
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
$sql = 'SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid = ?';
|
||||
|
||||
$DB->set_field_select('question', 'modifiedby', 0,
|
||||
'category IN (SELECT id FROM {question_categories} WHERE contextid = :contextid)',
|
||||
[
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
$questions = $DB->get_records_sql($sql, [$context->id]);
|
||||
foreach ($questions as $question) {
|
||||
$question->createdby = 0;
|
||||
$question->modifiedby = 0;
|
||||
$DB->update_record('question', $question);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -523,15 +532,36 @@ class provider implements
|
||||
|
||||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$contextparams['createdby'] = $contextlist->get_user()->id;
|
||||
$DB->set_field_select('question', 'createdby', 0, "
|
||||
category IN (SELECT id FROM {question_categories} WHERE contextid {$contextsql})
|
||||
AND createdby = :createdby", $contextparams);
|
||||
$questiondata = $DB->get_records_sql(
|
||||
"SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid {$contextsql}
|
||||
AND q.createdby = :createdby", $contextparams);
|
||||
|
||||
foreach ($questiondata as $question) {
|
||||
$question->createdby = 0;
|
||||
$DB->update_record('question', $question);
|
||||
}
|
||||
|
||||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$contextparams['modifiedby'] = $contextlist->get_user()->id;
|
||||
$DB->set_field_select('question', 'modifiedby', 0, "
|
||||
category IN (SELECT id FROM {question_categories} WHERE contextid {$contextsql})
|
||||
AND modifiedby = :modifiedby", $contextparams);
|
||||
$questiondata = $DB->get_records_sql(
|
||||
"SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid {$contextsql}
|
||||
AND q.modifiedby = :modifiedby", $contextparams);
|
||||
|
||||
foreach ($questiondata as $question) {
|
||||
$question->modifiedby = 0;
|
||||
$DB->update_record('question', $question);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -554,12 +584,32 @@ class provider implements
|
||||
|
||||
$params = ['contextid' => $context->id];
|
||||
|
||||
$DB->set_field_select('question', 'createdby', 0, "
|
||||
category IN (SELECT id FROM {question_categories} WHERE contextid = :contextid)
|
||||
AND createdby {$createdbysql}", $params + $createdbyparams);
|
||||
$questiondata = $DB->get_records_sql(
|
||||
"SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid = :contextid
|
||||
AND q.createdby {$createdbysql}", $params + $createdbyparams);
|
||||
|
||||
$DB->set_field_select('question', 'modifiedby', 0, "
|
||||
category IN (SELECT id FROM {question_categories} WHERE contextid = :contextid)
|
||||
AND modifiedby {$modifiedbysql}", $params + $modifiedbyparams);
|
||||
foreach ($questiondata as $question) {
|
||||
$question->createdby = 0;
|
||||
$DB->update_record('question', $question);
|
||||
}
|
||||
|
||||
$questiondata = $DB->get_records_sql(
|
||||
"SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid = :contextid
|
||||
AND q.modifiedby {$modifiedbysql}", $params + $modifiedbyparams);
|
||||
|
||||
foreach ($questiondata as $question) {
|
||||
$question->modifiedby = 0;
|
||||
$DB->update_record('question', $question);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2022020200.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2022020200.03; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.0dev+ (Build: 20220202)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user